公司的同事离职了,接下来的日子可能会忙碌,能完善DEMO的时间也会少了,因此,把做的简易DEMO整体先记录一下,等后续不断的完善。

参考两位大神的日志:WEB版微信协议部分功能分析【完全开源】微信客户端.NET版

尤其是周见智大神的DEMO,因为好多和微信的服务端交互,都借鉴了大神的源码,帮助巨大,可以说我相当于做了一个翻版,只是用WPF开发的而已,外观上不同,但是实际交互上是差不多的。

微信分为两个部分,一个是登录,一个是主体,基于此,WPF也主要是这两个窗体来实现。

一、登录模块

1、登录部分分为二维码和获取用户头像两个页面(因为是给予WEB的,所以没有客户端的登录按钮,只能通过扫码来登录)

在程序启动以后,先通过请求获取到二维码,然后,在启动一个新的线程,不断的循环检索登录状态。

  1. private void LoopLoginCheck()
  2. {
  3. object login_result = null;
  4. //循环判断手机扫描二维码结果
  5. while (true)
  6. {
  7. login_result = ls.LoginCheck();
  8. //已扫描 未登录
  9. if (login_result is ImageSource)
  10. {
  11. HeadImageSource = login_result as ImageSource;
  12. //广播,通知到LoginUC页面,切换
  13. Messenger.Default.Send<object>(null, "ShowLoginInfoUC");
  14. }
  15. //已完成登录
  16. if (login_result is string)
  17. {
  18. //访问登录跳转URL
  19. ls.GetSidUid(login_result as string);
  20.  
  21. //广播,隐藏登录页面,打开主页面
  22. Messenger.Default.Send<object>(null, "HideLoginUC");
  23.  
  24. thread.Abort();
  25. break;
  26. }
  27. ////超时
  28. if (login_result is int)
  29. {
  30. //QRCodeImageSource = ls.GetQRCode();
  31. //返回二维码页面
  32. Messenger.Default.Send<object>(null, "ShowQRCodeUC");
  33. }
  34. }
  35. }

循环检索状态

因为是MVVM,所以,需要用广播来进行操作页面的切换,即填充到登录窗体中间的控件是二维码,还是头像。

2、大家可以看到我上面的截图部分包含了一部分的背景,这个是用Snagit(推荐这个截图工具,很好用)截图时,自动截出的,因为窗体本身的大小就是那么大,多余出来的部分是透明的,用来做二维码滑动出现的效果部分。

当处于二维码状态时划过,则出现动画,头像状态下则没有动画,是设置了Image的Visibility属性来控制的,滑动效果可以看我的另一篇博客微信 二维码鼠标滑动 图像显隐效果

3、当扫码成功,并且在手机端点击登录以后,则跳转到主页面,此处没有加异步等待处理,所以,用户量大的朋友,请耐心等待(后期会加上)。

登录成功以后,就会出现主窗体和系统托盘,主窗体包含最近联系人和通讯录,系统托盘网上很多解决方案,可以自行查找。

登录成功现在发现了一个问题,就是我有两个微信号,其中一登录以后是有数据的,另一个则没有数据。

跟踪代码,发现返回的Json是空的,也就是说没有返回值,试验了下周大神的代码,发现也是空的,不清楚什么情况,我同事的有的也是空的,这个一直没有深究,等把功能基本都完善以后再看看问题所在。

二、主窗体模块

1、主窗体的布局部分很简单,采用了Grid进行分隔,三列,上面的控件如图所示

大部分到没什么,可能大家比较疑惑的是我的聊天窗体为什么是ListBox,这个东西的话,我认为,自己有自己的开发习惯,很多控件都可以实现,panel就可以。

RadioButton的样式是用path画的,可以看我另一篇博客微信聊天和通讯录按钮样式

2、聊天列表里,未读的消息上会有带数字的小红点,这个是用Button写的,Item的整体组成是Image(头像)、Button(未读数)、TextBlock(昵称、时间和聊天内容)

  1. <Style x:Key="ListBoxItemChatStyle" TargetType="{x:Type ListBoxItem}">
  2. <Setter Property="Template">
  3. <Setter.Value>
  4. <ControlTemplate TargetType="ListBoxItem">
  5. <Border>
  6. <StackPanel x:Name="sp" Orientation="Horizontal" Height="{Binding Converter={StaticResource objectToHeight}}" Background="{Binding Converter={StaticResource objectToColor}}">
  7. <Grid>
  8. <Image Source="{Binding Icon}" Width="40" Height="40" Margin="10"/>
  9. <Button Foreground="White" Visibility="{Binding UnReadCount,Converter={StaticResource countToVisibility}}" Content="{Binding UnReadCount}" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,5" Style="{StaticResource CirButtonStyle}"/>
  10. </Grid>
  11. <Grid Width="176">
  12. <Grid.RowDefinitions>
  13. <RowDefinition/>
  14. <RowDefinition/>
  15. </Grid.RowDefinitions>
  16. <TextBlock Grid.Row="0" Text="{Binding ShowName}" FontSize="15" HorizontalAlignment="Left" Margin="5,10,0,0"/>
  17. <TextBlock Grid.Row="0" Text="{Binding LastTime}" FontSize="15" HorizontalAlignment="Right" Margin="0,10,5,0"/>
  18. <TextBlock Grid.Row="1" Text="{Binding LastMsg}" FontSize="12" HorizontalAlignment="Left" Margin="5,0,0,0"/>
  19. </Grid>
  20. </StackPanel>
  21. </Border>
  22. <ControlTemplate.Triggers>
  23. <Trigger Property="IsMouseOver" Value="true">
  24. <Setter Property="Background" Value="#FFE2E4E6" TargetName="sp"/>
  25. </Trigger>
  26. <Trigger Property="IsSelected" Value="true">
  27. <Setter Property="Background" Value="#FFCACDD3" TargetName="sp"/>
  28. </Trigger>
  29. </ControlTemplate.Triggers>
  30. </ControlTemplate>
  31. </Setter.Value>
  32. </Setter>
  33. </Style>

聊天列表内容样式

3、聊天内容部分用的是ScrollingListBox,继承自ListBox,但是重写了里面的OnItemsChanged属性,保证可以时刻滚动到最后一行

  1. public class ScrollingListBox : ListBox
  2. {
  3. protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  4. {
  5. if (e.NewItems!=null)
  6. {
  7. int newItemCount = e.NewItems.Count;
  8.  
  9. if (newItemCount > )
  10. this.ScrollIntoView(e.NewItems[newItemCount - ]);
  11.  
  12. base.OnItemsChanged(e);
  13. }
  14. }
  15. }

ScrollingListBox

样式部分是重写控件模板用的是Image(头像),path(三角部分),textbox(内容部分)

  1. <Style x:Key="ChatListBoxStyle" TargetType="{x:Type ListBox}">
  2. <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
  3. <Setter Property="BorderBrush" Value="{StaticResource ListBorder}"/>
  4. <Setter Property="BorderThickness" Value="0"/>
  5. <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
  6. <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden"/>
  7. <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden"/>
  8. <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
  9. <Setter Property="ScrollViewer.PanningMode" Value="Both"/>
  10. <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
  11. <Setter Property="VerticalContentAlignment" Value="Center"/>
  12. <Setter Property="ItemContainerStyle">
  13. <Setter.Value>
  14. <Style TargetType="ListBoxItem">
  15. <Setter Property="Focusable" Value="False"/>
  16. <Setter Property="Template">
  17. <Setter.Value>
  18. <ControlTemplate>
  19. <StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Top" FlowDirection="{Binding FlowDir}" Margin="15,5">
  20. <Image Grid.Column="1" Source="{Binding Image}" Height="35" Width="35" VerticalAlignment="Top"/>
  21. <Path Grid.Column="2" StrokeThickness="1" Stroke="{Binding TbColor}" Data="M12,13 L5,18 L12,23Z" Fill="{Binding TbColor}" Margin="0" SnapsToDevicePixels="True"/>
  22. <TextBox Grid.Column="3" MaxWidth="355" TextWrapping="Wrap" FontSize="15" BorderBrush="{Binding TbColor}" Background="{Binding TbColor}" IsReadOnly="True" BorderThickness="0" Style="{StaticResource ChatTextBoxStyle}" FlowDirection="LeftToRight" Text="{Binding Message}"/>
  23. </StackPanel>
  24. </ControlTemplate>
  25. </Setter.Value>
  26. </Setter>
  27. </Style>
  28. </Setter.Value>
  29. </Setter>
  30. </Style>

聊天窗体样式

需要注意的是:此处必须要重写控件模板,而不能重写数据模板,虽然,很多情况下控件模板和数据模板可以得到的效果相同,但是此处,如果写数据模板的话,则自己发的信息不会在右侧,就算设置FlowDirection也没有用,大家可以自行尝试。

4、如果发送内容是空的情况下,则会有一个ToolTip出现,此处的TooLTipye也是重写了样式的Button,好定位,毕竟就算是最大化,位置也是不变的。

通讯录部分,和聊天列表差不多,不过,由于需要进行分组,也就是A、B……这种组合,所以用的Object类型,在点选过程中,通过is来进行判别是不是WeChatUser,如果是,则进行转换,来进一步处理。

大家可以看到上面那个好友是同程旅游顾问<span ……其实它是一个emoji,只是现在我还没有做到那一部分,如果做到的话,则进行转换,如果谁有好的emoji处理方式希望告知,谢谢了。

  1. <Style x:Key="ListBoxItemFriendStyle" TargetType="{x:Type ListBoxItem}">
  2. <Setter Property="Template">
  3. <Setter.Value>
  4. <ControlTemplate TargetType="ListBoxItem">
  5. <Border>
  6. <StackPanel x:Name="sp" Orientation="Horizontal" Height="{Binding Converter={StaticResource objectToHeight}}" Background="{Binding Converter={StaticResource objectToColor}}">
  7. <TextBlock Text="{Binding }" FontWeight="Black" Visibility="{Binding Converter={StaticResource modelToVisibility}}" FontSize="15" Margin="10,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
  8. <Image Source="{Binding Icon}" Width="40" Height="40" Margin="10"/>
  9. <TextBlock Text="{Binding ShowName}" FontSize="15" HorizontalAlignment="Left" Margin="5,10,0,0"/>
  10. </StackPanel>
  11. </Border>
  12. <ControlTemplate.Triggers>
  13. <Trigger Property="IsMouseOver" Value="true">
  14. <Setter Property="Background" Value="#FFE2E4E6" TargetName="sp"/>
  15. </Trigger>
  16. <Trigger Property="IsSelected" Value="true">
  17. <Setter Property="Background" Value="#FFCACDD3" TargetName="sp"/>
  18. </Trigger>
  19. </ControlTemplate.Triggers>
  20. </ControlTemplate>
  21. </Setter.Value>
  22. </Setter>
  23. </Style>

通讯录列表样式

当点选列表以后,并且转换成功的情况下,则显示出用户的信息,通过内容是否未空,来判别是否要显示

  1. <Grid Grid.Row="1" Grid.RowSpan="2" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{Binding ElementName=rb_friend,Path=IsChecked,Converter={StaticResource boolToVisibility}}" Margin="0,50,0,0">
  2. <Grid.RowDefinitions>
  3. <RowDefinition Height="Auto"/>
  4. <RowDefinition Height="Auto"/>
  5. <RowDefinition Height="Auto"/>
  6. <RowDefinition Height="Auto"/>
  7. <RowDefinition Height="Auto"/>
  8. <RowDefinition Height="Auto"/>
  9. <RowDefinition Height="Auto"/>
  10. </Grid.RowDefinitions>
  11. <Image Source="{Binding FriendInfo.Icon}" Grid.Row="0" Height="124" Width="124" HorizontalAlignment="Center"/>
  12. <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Center">
  13. <TextBlock Text="{Binding FriendInfo.NickName}" FontSize="30" Foreground="Black" FontWeight="Bold"/>
  14. <Image Visibility="{Binding FriendInfo.Sex,Converter={StaticResource parameterToVisibility},ConverterParameter=2}" Source="/Image/female.png"/>
  15. <Image Visibility="{Binding FriendInfo.Sex,Converter={StaticResource parameterToVisibility},ConverterParameter=1}" Source="/Image/male.png"/>
  16. </StackPanel>
  17. <TextBlock Text="{Binding FriendInfo.Signature}" Foreground="#FF919191" Grid.Row="2" HorizontalAlignment="Center"/>
  18. <StackPanel Orientation="Horizontal" Visibility="{Binding FriendInfo.RemarkName,Converter={StaticResource epmtyToVisibility}}" Margin="10" Grid.Row="3" HorizontalAlignment="Center">
  19. <TextBlock Text="备 注" Margin="0,0,10,0" FontSize="15"/>
  20. <TextBlock Text="{Binding FriendInfo.RemarkName}" FontSize="15"/>
  21. </StackPanel>
  22. <StackPanel Orientation="Horizontal" Visibility="{Binding FriendInfo.Province,Converter={StaticResource epmtyToVisibility}}" Margin="10" Grid.Row="4" HorizontalAlignment="Center">
  23. <TextBlock Text="地区" Margin="0,0,10,0" FontSize="15"/>
  24. <TextBlock Text="{Binding FriendInfo.Province}" Margin="0,0,2,0" FontSize="15"/>
  25. <TextBlock Text="{Binding FriendInfo.City}" FontSize="15"/>
  26. </StackPanel>
  27. <Button Content="发消息" Width="166" Height="37" Grid.Row="5" Command="{Binding FriendSendComamnd}" Margin="0,50,0,0" Style="{StaticResource FriSendButtonStyle}"/>
  28. <Grid Grid.Row="0" Grid.RowSpan="7" Background="WhiteSmoke" Visibility="{Binding FriendInfo,Converter={StaticResource nullToVisibility}}"/>
  29. </Grid>

用户信息页面

点击发消息按钮,则跳转回聊天页面,然后,将当前的好友加入到聊天的第一项。

三、总结

做WPF微信DEMO,用到了转换器,转换颜色,转换显隐;重写了控件的样式,例如Button、RadioButton、ListBox;然后MVVM模式下,Bing的用法,感觉这个DEOM对于初学者来说应该会有很大的帮助。

不过这个DEMO的BUG和不完善的地方还有很多,例如系统托盘还没有做闪烁,现在只能发送文字,最大化的问题。

系统托盘闪烁可以用Timer和Opacity来进行控制,比如来未读消息了,则在进行时间间隔的控制显隐。

后期会把TextBox换成RichTextBox,这样可以发送图片和emoji。

最大化问题,是我一直还没有想到好的解决办法,最大化的情况下会占据整个屏幕,而不把状态栏空出来,网上的办法都是重新设置Width和Height,但是这样的话,就要记录原来的大小和位置,一直没有找到可以重写WindowState.Maximized的方法,好像是不能重写,所以比较纠结,希望哪位大神看完我的代码以后,能够给提供一下解决思路,谢谢了。

源码:GitHub

WPF 微信 MVVM的更多相关文章

  1. WPF 微信 MVVM 【续】修复部分用户无法获取列表

    看过我WPF 微信 MVVM这篇文章的朋友,应该知道我里面提到了我有一个小号是无法获取列表的,始终也没找到原因. 前两天经过GitHub上h4dex大神的指导,知道了原因,是因为微信在登录以后,web ...

  2. WPF 微信 MVVM 【续】发送部分QQ表情

    今天主要记录的就是发送QQ表情, WPF 微信 MVVM里写了,后期为了发送QQ表情,需要把TextBox替换为RichTextBox,接下来就说说替换的过程. 一.支持Binding的RichTex ...

  3. 从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器

    从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器 之前时间一直在使用Caliburn.Micro这种应用了MVVM模式的WPF框架做开发,是时候总结一下了. Calibu ...

  4. CleanAOP实战系列--WPF中MVVM自动更新

    CleanAOP实战系列--WPF中MVVM自动更新 作者: 立地 邮箱: jarvin_g@126.com QQ: 511363759 CleanAOP介绍:https://github.com/J ...

  5. WPF Prism MVVM 中 弹出新窗体. 放入用户控件

    原文:WPF Prism MVVM 中 弹出新窗体. 放入用户控件 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/qq_37214567/artic ...

  6. WPF之MVVM模式(3)

    有种想写一个MVVM框架的冲动!!! 1.Model中的属性应不应该支持OnPropertyChanged事件? 不应该.应该有ViewModel对该属性进行封装,由ViewModel提供OnProp ...

  7. 在WPF的MVVM框架中获取下拉选择列表中的选中项

    文章概述: 本演示介绍怎样在WPF的MVVM框架中.通过数据绑定的方式获取下拉列表中的选中项.程序执行后的效果例如以下图所看到的: 相关下载(代码.屏幕录像):http://pan.baidu.com ...

  8. 【WPF】MVVM模式的3种command

    原文:[WPF]MVVM模式的3种command 1.DelegateCommand 2.RelayCommand 3.AttachbehaviorCommand 因为MVVM模式适合于WPF和SL, ...

  9. 查杀进程小工具——WPF和MVVM初体验

    最近因为工作需要,研究了一下桌面应用程序.在winform.WPF.Electron等几种技术里,最终选择了WPF作为最后的选型.WPF最吸引我的地方,就是MVVM模式了.MVVM模式完全把界面和业务 ...

随机推荐

  1. Asp.Net Mvc 使用WebUploader 多图片上传

    来博客园有一个月了,哈哈.在这里学到了很多东西.今天也来试着分享一下学到的东西.希望能和大家做朋友共同进步. 最近由于项目需要上传多张图片,对于我这只菜鸟来说,以前上传图片都是直接拖得控件啊,而且还是 ...

  2. 渗透测试工具BurpSuite做网站的安全测试(基础版)

    渗透测试工具BurpSuite做网站的安全测试(基础版) 版权声明:本文为博主原创文章,未经博主允许不得转载. 学习网址: https://t0data.gitbooks.io/burpsuite/c ...

  3. 《Django By Example》第二章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:翻译完第一章后,发现翻译第二章的速 ...

  4. 【开源】.Net Aop(静态织入)框架 BSF.Aop

    BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费): 实现前后Aop切面和INotifyPropertyChanged注入方式. 开源地址: ...

  5. Spring中Bean的作用域、生命周期

                                   Bean的作用域.生命周期 Bean的作用域 Spring 3中为Bean定义了5中作用域,分别为singleton(单例).protot ...

  6. [原] KVM 虚拟化原理探究(6)— 块设备IO虚拟化

    KVM 虚拟化原理探究(6)- 块设备IO虚拟化 标签(空格分隔): KVM [toc] 块设备IO虚拟化简介 上一篇文章讲到了网络IO虚拟化,作为另外一个重要的虚拟化资源,块设备IO的虚拟化也是同样 ...

  7. JavaWeb——Listener

    一.基本概念 JavaWeb里面的listener是通过观察者设计模式进行实现的.对于观察者模式,这里不做过多介绍,大概讲一下什么意思. 观察者模式又叫发布订阅模式或者监听器模式.在该模式中有两个角色 ...

  8. [译]处理文本数据(scikit-learn 教程3)

    原文网址:http://scikit-learn.org/stable/tutorial/text_analytics/working_with_text_data.html 翻译:Tacey Won ...

  9. 页面布局class常见命名规范

    头:header 内容:content/container 尾:footer 导航:nav 侧栏:sidebar 栏目:column 页面外围控制整体布局宽度:wrapper 左右中:left rig ...

  10. 解决使用IE8打开ADFS 3.0登录页面

    系统上线前一天,发现客户竟然有XP系统和2003系统,这些系统都不能访问外网.测试时,客户端是IE8,打开我们系统ADFS的登录页面,一直在Loading,无法打开,也不报错.后来通过fiddler跟 ...