WPF 制作聊天窗口获取历史聊天记录


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

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

首先是前台的陋XAML:

"testFlowDocument.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
    
        "sv_richtextbox" Background="Transparent" PreviewMouseLeftButtonUp="sv_richtextbox_PreviewMouseLeftButtonUp"
                   PreviewMouseWheel="sv_richtextbox_PreviewMouseWheel"      VerticalScrollBarVisibility="Auto" ScrollChanged="sv_richtextbox_ScrollChanged">
             "True" x:Name="RichTextBoxMessageHistory" BorderBrush="#B7D9ED" 
                          Margin="3,3,3,0" Background="Silver" />
             
        
        
        
        
        
    

 在基本布局里添加了一个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
{
    /// 
    /// MainWindow.xaml 的交互逻辑
    /// 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }


        int i = 0;
        private void previousadd_Click(object sender, RoutedEventArgs e)
        {
            addmessage(1);
        }

        /// 
        /// 插入数据
        /// 
        void addmessage(int pagesize)
        {
            for (int j = 0; j < pagesize; j++)
            {
                i++;
                vScrollposition = sv_richtextbox.ExtentHeight;
                Paragraph pggethistoryNo = new Paragraph();
                pggethistoryNo.Background = Brushes.LightBlue;
                pggethistoryNo.Margin = new Thickness(0, 5, 60, 0);

                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 > 0)
                {//判断是否存在数据了
                    RichTextBoxMessageHistory.Document.Blocks.InsertBefore(RichTextBoxMessageHistory.Document.Blocks.FirstBlock, pggethistoryNo);
                }
                else
                {//若不存在,第一条要加入而非插入
                    RichTextBoxMessageHistory.Document.Blocks.Add(pggethistoryNo);
                }
                isEnd = false;
            }
        }

        bool isEnd = false;//是否滚动到底部
        double vScrollposition = 0;//当前接收到的所有文本内容高度(包括历史消息)
     
        private void sv_richtextbox_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            if (e.ViewportHeightChange > 0)
            {
                if (isEnd == true)
                {//判断是否是从底部添加数据
                    if (sv_richtextbox.ScrollableHeight == sv_richtextbox.VerticalOffset)
                    {//判断滚动条是否在最底部
                        sv_richtextbox.ScrollToEnd();
                    }
                }
                else
                {//定位到上次位置
                    double changevScrollHeight = sv_richtextbox.ExtentHeight - vScrollposition;
                    if (changevScrollHeight > 0)
                    {
                        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(60, 5, 0, 0);

            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(20);
        }

        private void sv_richtextbox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.Delta > 0)
            {
                isAddMessage();
            }
        }

        private void sv_richtextbox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            isAddMessage();
        }

        void isAddMessage()
        {
            double offi = sv_richtextbox.VerticalOffset;
            if (offi == 0)
            {
                double Maxinum = sv_richtextbox.ScrollableHeight;
                if (Maxinum == 0)
                    return;
                vScrollposition = sv_richtextbox.ExtentHeight;
                addmessage(10);
                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

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

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

相关