腾讯从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

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

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

最新文章

  1. vue.js 接收url参数
  2. September 9th 2016 Week 37th Friday
  3. BZOJ 2956 模积和
  4. umount移动硬盘遇到device is busy问题
  5. Android中的事件分发和处理
  6. android100 自定义内容提供者
  7. 8. 冒泡法排序和快速排序(基于openCV)
  8. docker 创建镜像
  9. [LeetCode] 036. Valid Sudoku (Easy) (C++)
  10. 如何编译Apache Hadoop2.2.0源代码
  11. php刷新当前页面
  12. Spring学习(1)----入门学习(附spring-framework下载地址)
  13. 在Notepad++中添加运行快捷键
  14. linux_通配符
  15. 浅谈AndroidGPU过度绘制、GPU呈现模式分析及相关优化
  16. 实例分析ASP.NET在MVC5中使用MiniProfiler监控MVC性能的方法 
  17. 流式大数据计算实践(7)----Hive安装
  18. 解析/proc/net/dev
  19. 修改hosts文件用来观看coursera视频
  20. 如何永久删除git仓库中敏感文件的提交记录

热门文章

  1. Android--&gt;Genymotion虚拟机(模拟器)的配置
  2. ExtAspNet和FineUI未将对象引用设置到对象的实例
  3. windows ftp 连接serv_U 管理员
  4. arcgis server 10.2安装后,忘记Manager的用户名和密码
  5. EXCEL datatable 根据列名自动写入到相应属性、字段或列中
  6. MapReduce、Hbase接口API实践
  7. Java为什么会引入及如何使用Unsafe
  8. How to configure Veritas NetBackup (tm) to write Unified and Legacy log files to a different directory
  9. Flex中使用CSS控制页面样式
  10. JS-改变页面的颜色(二)