腾讯从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. Python 决策树的构造

    上一节我们学习knn,kNN的最大缺点就是无法给出数据的内在含义,而使用决策树处理分类问题,优势就在于数据形式非常容易理解. 决策树的算法有很多,有CART.ID3和C4.5等,其中ID3和C4.5都 ...

  2. linux平台编译安装Poco C++

    1.到官网下载poco-1.4.6p4-all.tar.gz,地址为http://pocoproject.org/download/index.html 2.同时下载说明文档 3.根据说明文档执行安装 ...

  3. Quartus 编译错误

    1.Error (10028): Can't resolve multiple constant drivers for net ** 这种错误一般是由于定义的reg寄存器在多个always中进行赋值 ...

  4. JavaBean用JSP调用和使用JSP动作标签的区别

    javabean的类可以用jsp动作标签实例化并使用. <!-- 下面这句是对Javabean类person的引用,引用的实例是p2 --> <jsp:useBean id=&quo ...

  5. Windows下RCNN的使用

    RCNN 一种把目标图像分割转化为CNN分类问题进行目标检测的方法. 以下转自魏晋的知乎回答   Ross B. Girshick的RCNN使用region proposal(具体用的是Selecti ...

  6. Flume+Kafka+Strom基于伪分布式环境的结合使用

    目录: 一.Flume.Kafka.Storm是什么,如何安装? 二.Flume.Kafka.Storm如何结合使用? 1) 原理是什么? 2) Flume和Kafka的整合  3) Kafka和St ...

  7. MyEclipse Spring 学习总结二 Bean的生命周期

    文件结构可以参考上一节 Bean的生命周期有方法有:init-method,destroy-method ApplicationContext.xml 文件配置如下: <?xml version ...

  8. HackerRank "New Year Chaos"

    Two tricks here: 1. Counting no. of inversed pairs - using Merge Sort, nothing special 2. How to che ...

  9. phpnow修改默认站点根目录的方法

    本文转载自:http://blog.csdn.net/andy_eeipla/article/details/7832082 对于phpnow,经测试,修改Apache-20\conf\extra\h ...

  10. Hadoop数据读写原理

    数据流 MapReduce作业(job)是客户端执行的单位:它包括输入数据.MapReduce程序和配置信息.Hadoop把输入数据划分成等长的小数据发送到MapReduce,称之为输入分片.Hado ...