腾讯从QQ2013版起开始在聊天记录里添加了历史记录查看功能,个人聊天窗口可以点击最上边的‘查看历史消息’,而群组里的未读消息可以通过滚动鼠标中键或者拖动滚动条加载更多消息,那这个用wpf怎么实现呢?

我用Scrollviewer和RichTextBox做了一个简陋尝试,真的是太陋了,大家戴好眼镜了哈。现在开始:

首先是前台的陋XAML:

<Window x:Class="testFlowDocument.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="" Width="" Loaded="Window_Loaded">
<Grid>
<ScrollViewer x:Name="sv_richtextbox" Background="Transparent" PreviewMouseLeftButtonUp="sv_richtextbox_PreviewMouseLeftButtonUp"
PreviewMouseWheel="sv_richtextbox_PreviewMouseWheel" VerticalScrollBarVisibility="Auto" ScrollChanged="sv_richtextbox_ScrollChanged">
<RichTextBox IsReadOnly="True" x:Name="RichTextBoxMessageHistory" BorderBrush="#B7D9ED"
Margin="3,3,3,0" Background="Silver" /> </ScrollViewer>
<Button Name="previousadd" Content="前加" Height="" Width="" VerticalAlignment="Bottom" HorizontalAlignment="Left" Click="previousadd_Click"></Button>
<Button Name="clearadd" Content="清空" Height="" Width="" VerticalAlignment="Bottom" Click="clearadd_Click"></Button>
<Button Name="add20" Content="加20条" Height="" Width="" VerticalAlignment="Bottom" Margin="0,0,110,0" HorizontalAlignment="Right" Click="add20_Click"></Button>
<Button Name="lastadd" Content="后加" Height="" Width="" VerticalAlignment="Bottom" HorizontalAlignment="Right" Click="lastadd_Click"></Button>
</Grid>
</Window>

在基本布局里添加了一个Scrollviewer包含RichTextBox,另外添加了4个Button控件来添加简单数据。previousadd往最上端插入数据,lastadd从底部添加数据。add20快速添加20条数据使之出现滚动条。

好了,下面是后台陋CS实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes; namespace testFlowDocument
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
} int i = ;
private void previousadd_Click(object sender, RoutedEventArgs e)
{
addmessage();
} /// <summary>
/// 插入数据
/// </summary>
void addmessage(int pagesize)
{
for (int j = ; j < pagesize; j++)
{
i++;
vScrollposition = sv_richtextbox.ExtentHeight;
Paragraph pggethistoryNo = new Paragraph();
pggethistoryNo.Background = Brushes.LightBlue;
pggethistoryNo.Margin = new Thickness(, , , ); TextBlock tblockgethistoryNo = new TextBlock();
tblockgethistoryNo.Text = i.ToString();
tblockgethistoryNo.Foreground = Brushes.Black;
pggethistoryNo.Inlines.Add(tblockgethistoryNo); if (RichTextBoxMessageHistory.Document.Blocks != null && RichTextBoxMessageHistory.Document.Blocks.Count > )
{//判断是否存在数据了
RichTextBoxMessageHistory.Document.Blocks.InsertBefore(RichTextBoxMessageHistory.Document.Blocks.FirstBlock, pggethistoryNo);
}
else
{//若不存在,第一条要加入而非插入
RichTextBoxMessageHistory.Document.Blocks.Add(pggethistoryNo);
}
isEnd = false;
}
} bool isEnd = false;//是否滚动到底部
double vScrollposition = ;//当前接收到的所有文本内容高度(包括历史消息) private void sv_richtextbox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e.ViewportHeightChange > )
{
if (isEnd == true)
{//判断是否是从底部添加数据
if (sv_richtextbox.ScrollableHeight == sv_richtextbox.VerticalOffset)
{//判断滚动条是否在最底部
sv_richtextbox.ScrollToEnd();
}
}
else
{//定位到上次位置
double changevScrollHeight = sv_richtextbox.ExtentHeight - vScrollposition;
if (changevScrollHeight > )
{
sv_richtextbox.ScrollToVerticalOffset(e.ViewportHeightChange + changevScrollHeight);
return;
}
sv_richtextbox.ScrollToVerticalOffset(e.ViewportHeightChange);
return;
}
}
} private void lastadd_Click(object sender, RoutedEventArgs e)
{
i++;
Paragraph pggethistoryNo = new Paragraph();
pggethistoryNo.Background = Brushes.LightGreen;
pggethistoryNo.Margin = new Thickness(, , , ); TextBlock tblockgethistoryNo = new TextBlock();
tblockgethistoryNo.Text = i.ToString();
tblockgethistoryNo.Foreground = Brushes.Black;
pggethistoryNo.Inlines.Add(tblockgethistoryNo); RichTextBoxMessageHistory.Document.Blocks.Add(pggethistoryNo);
isEnd = true;
} private void clearadd_Click(object sender, RoutedEventArgs e)
{
RichTextBoxMessageHistory.Document.Blocks.Clear();
} private void Window_Loaded(object sender, RoutedEventArgs e)
{
RichTextBoxMessageHistory.Document.Blocks.Clear();
} private void add20_Click(object sender, RoutedEventArgs e)
{
addmessage();
} private void sv_richtextbox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > )
{
isAddMessage();
}
} private void sv_richtextbox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
isAddMessage();
} void isAddMessage()
{
double offi = sv_richtextbox.VerticalOffset;
if (offi == )
{
double Maxinum = sv_richtextbox.ScrollableHeight;
if (Maxinum == )
return;
vScrollposition = sv_richtextbox.ExtentHeight;
addmessage();
RichTextBoxMessageHistory.Focus();
}
}
}
}

向RichTextBox控件追加内容,可以用Document.Blocks.Add(Block item)方法。

而向RichTextBox插入内容,用的是Document.Blocks.InsertBefore(Block nextSibling, Block newItem)方法,其中nextSibling指的是将要被插入的位置,newItem指的是将要插入的新内容。而获取历史聊天记录后,我们可以用此方法往最上端插入数据。所以,此处我们可以写作 RichTextBoxMessageHistory.Document.Blocks.InsertBefore(RichTextBoxMessageHistory.Document.Blocks.FirstBlock, pggethistoryNo);其中pggethistoryNo是新定义的内容;

其实今天的主角是‘拖动滚动条和滚动鼠标键加载数据’,而幕后的英雄是ScrollChanged事件。当我们拖动滚动条和滚动鼠标键加载出新数据时,会有一个滚动条定位的问题,有人说收到新消息时应该跳到新消息处虽新的聊天自动往下滚动,即总在最底端;有人说当你正在看历史消息时如果突然来了一条消息就跳到最底端那还得再重新找刚才的位置,让人很抓狂;还有人说当拖动加载出新消息时如果滚动条呆在新加载出内容的顶端,还得再去手动找刚才读到的位置也是一件烦人眼珠子的事。能不能做一件完美的事情同时满足三者呢?有时候猜不到结局就勇敢的去做吧~~

1、自动滚到最底部: sv_richtextbox.ScrollToEnd();

2,3、定位在某位置: sv_richtextbox.ScrollToVerticalOffset(double offset);

如何判断是从最上边插入的还是从最下边添加的呢?我们设置了参数isEnd来判断,true表示滚到最下端。如何判断添加新消息时滚动条是否在最下边呢?用sv_richtextbox.ScrollableHeight == sv_richtextbox.VerticalOffset判断。当滚动条有变化(位置或大小)时ScrollChanged事件会捕获到,我们就在该事件里做判断。

需要特别注意:很多人说自己在Scrollviewer中鼠标事件无效,提醒一下,在Scrollviewer控件中捕获不到MouseUp等事件,但可以捕获到PreviewMouseUp等事件。

附两张陋图:

 

本文博客园地址:http://www.cnblogs.com/jying/p/3223431.html

到此为止,我要说的说完了,谢谢大家捧场。。

个人小站欢迎来踩:驾校教练评价平台 | 为爱豆砌照片墙

WPF 制作聊天窗口获取历史聊天记录的更多相关文章

  1. WPF制作的小型笔记本

    WPF制作的小型笔记本-仿有道云笔记 楼主所在的公司不允许下载外部资源, 不允许私自安装应用程序, 平时记录东西都是用记事本,时间久了很难找到以前记的东西. 平时在家都用有道笔记, 因此就模仿着做了一 ...

  2. XMPP系列(四)---发送和接收文字消息,获取历史消息功能

    今天开始做到最主要的功能发送和接收消息.获取本地历史数据. 先上到目前为止的效果图:              首先是要在XMPPFramework.h中引入数据存储模块: //聊天记录模块的导入 # ...

  3. python量化之路:获取历史某一时刻沪深上市公司股票代码及上市时间

    最近开始玩股票量化,由于想要做完整的股票回测,因此股票的上市和退市信息就必不可少.因为我们回测的时候必须要知道某一日期沪深股票的成分包含哪些对吧.所以我们要把沪深全部股票的上市时间.退市时间全部都爬下 ...

  4. WPF编程,获取句柄将外部程序嵌入到WPF界面。

    原文:WPF编程,获取句柄将外部程序嵌入到WPF界面. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/details ...

  5. VS编程,WPF中,获取鼠标相对于当前屏幕坐标的一种方法

    原文:VS编程,WPF中,获取鼠标相对于当前屏幕坐标的一种方法 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/det ...

  6. VS编程,WPF中,获取鼠标相对于当前程序窗口的坐标的一种方法

    原文:VS编程,WPF中,获取鼠标相对于当前程序窗口的坐标的一种方法 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/ ...

  7. WPF制作表示透明区域的马赛克画刷

    最近在用WPF制作一款软件,需要像ps一样表示透明区域,于是制作了一个马赛克背景的style.实现比较简单,那么过程和思路就不表了,直接上代码 <DrawingBrush TileMode=&q ...

  8. WPF制作的小时钟

    原文:WPF制作的小时钟 周末无事, 看到WEB QQ上的小时钟挺可爱的, 于是寻思着用WPF模仿着做一个. 先看下WEB QQ的图: 打开VS, 开始动工. 建立好项目后, 面对一个空荡荡的页面, ...

  9. WPF制作Logo,很爽,今后在应用程序中加入Logo轻松,省事!

    原文:WPF制作Logo,很爽,今后在应用程序中加入Logo轻松,省事! 这是效果: XAML代码:<Viewbox Width="723.955078" Height=&q ...

随机推荐

  1. turn.js 图书翻页效果

    今天用turn.js 做图书的翻页效果遇到问题: 图片路径总是出错 调了一天,总算调出来了 我用的thinkphp,其他的不知道是不是一样 三 个地方要改动: 1.后台查出地址 注意的地方:1.地址要 ...

  2. C语言编程心得

    记录这些是为了日后自己想查阅以前经验的方便,同时若能给其他网友带来一些帮助,就更好了~ C语言,自己经常遇到的问题: 1.段错误 段错误一般是由于访问了不存在的地址造成的,具体的原因有文件路径不存在, ...

  3. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(十三)之附加功能-自定义皮肤

    前言 本篇要讲的算是一个layim代码功能扩充.在原来的laim中已经有自带的换肤功能,而且在skin配置中,你可以添加自己想要的皮肤图片路径.这些内容在接下来都不会涉及,本篇要讲的是自定义皮肤功能, ...

  4. Spring使用环境变量控制配置文件加载

    项目中需要用到很多配置文件,不同环境的配置文件是不一样的,因此如果只用一个配置文件,势必会造成配置文件混乱,这里提供一种利用环境变量控制配置文件加载的方法,如下: 一.配置环境变量 如果是window ...

  5. Dos命令完成文件拷贝

    Dos命令初阶--文件拷贝 1.XCOPY命令 可以在cmd中录入:XCOPY /? 即可查看帮助 帮助: XCOPY Microsoft Windows [版本 6.2.9200] (c) 2012 ...

  6. HackerRank "Minimum Penalty Path"

    It is about how to choose btw. BFS and DFS. My init thought was to DFS - TLE\MLE. And its editorial ...

  7. android学习笔记56——Service

    Service四大组件之一,需要在AndroidMainfest.xml中添加相关配置,运行于后台,不与用户进行交换,没有UI... 配置时可通过<intent-filter.../>元素 ...

  8. python学习-day15:局部变量与全局变量、嵌套函数、递归

    一.全局变量与局部变量 在子程序中定义的变量称为局部变量, 在程序的一开始定义的变量称为全局变量. 全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序.当全局变量与局部变量同名时:在定义局部 ...

  9. Hadoop学习14--Hadoop之一点点理解yarn

    yarn是一个分布式的资源管理系统. 它诞生的原因是原来的MapReduce框架的一些不足: 1.JobTracker单点故障隐患 2.JobTracker承担的任务太多,维护Job状态,Job的ta ...

  10. oracle控制文件丢失恢复

    在学习群里有个同学误删除了控制文件,于是我也把自己数据库的控制文件删除了,看看能不能进行恢复,以下是整个实验的过程~~在做之前,先看看控制文件的备份方式:1.生成可以重建控制文件的脚本.2.备份二进制 ...