公司的同事离职了,接下来的日子可能会忙碌,能完善DEMO的时间也会少了,所以,把作的简易DEMO总体先记录一下,等后续不断的完善。html
参考两位大神的日志:WEB版微信协议部分功能分析、【彻底开源】微信客户端.NET版git
尤为是周见智大神的DEMO,由于好多和微信的服务端交互,都借鉴了大神的源码,帮助巨大,能够说我至关于作了一个翻版,只是用WPF开发的而已,外观上不一样,可是实际交互上是差很少的。github
微信分为两个部分,一个是登陆,一个是主体,基于此,WPF也主要是这两个窗体来实现。微信
一、登陆部分分为二维码和获取用户头像两个页面(由于是给予WEB的,因此没有客户端的登陆按钮,只能经过扫码来登陆)app
在程序启动之后,先经过请求获取到二维码,而后,在启动一个新的线程,不断的循环检索登陆状态。异步
private void LoopLoginCheck() { object login_result = null; //循环判断手机扫描二维码结果 while (true) { login_result = ls.LoginCheck(); //已扫描 未登陆 if (login_result is ImageSource) { HeadImageSource = login_result as ImageSource; //广播,通知到LoginUC页面,切换 Messenger.Default.Send<object>(null, "ShowLoginInfoUC"); } //已完成登陆 if (login_result is string) { //访问登陆跳转URL ls.GetSidUid(login_result as string); //广播,隐藏登陆页面,打开主页面 Messenger.Default.Send<object>(null, "HideLoginUC"); thread.Abort(); break; } ////超时 if (login_result is int) { //QRCodeImageSource = ls.GetQRCode(); //返回二维码页面 Messenger.Default.Send<object>(null, "ShowQRCodeUC"); } } }
由于是MVVM,因此,须要用广播来进行操做页面的切换,即填充到登陆窗体中间的控件是二维码,仍是头像。ide
二、你们能够看到我上面的截图部分包含了一部分的背景,这个是用Snagit(推荐这个截图工具,很好用)截图时,自动截出的,由于窗体自己的大小就是那么大,多余出来的部分是透明的,用来作二维码滑动出现的效果部分。工具
当处于二维码状态时划过,则出现动画,头像状态下则没有动画,是设置了Image的Visibility属性来控制的,滑动效果能够看个人另外一篇博客微信 二维码鼠标滑动 图像显隐效果。oop
三、当扫码成功,而且在手机端点击登陆之后,则跳转到主页面,此处没有加异步等待处理,因此,用户量大的朋友,请耐心等待(后期会加上)。布局
登陆成功之后,就会出现主窗体和系统托盘,主窗体包含最近联系人和通信录,系统托盘网上不少解决方案,能够自行查找。
登陆成功如今发现了一个问题,就是我有两个微信号,其中一登陆之后是有数据的,另外一个则没有数据。
跟踪代码,发现返回的Json是空的,也就是说没有返回值,试验了下周大神的代码,发现也是空的,不清楚什么状况,我同事的有的也是空的,这个一直没有深究,等把功能基本都完善之后再看看问题所在。
一、主窗体的布局部分很简单,采用了Grid进行分隔,三列,上面的控件如图所示
大部分到没什么,可能你们比较疑惑的是个人聊天窗体为何是ListBox,这个东西的话,我认为,本身有本身的开发习惯,不少控件均可以实现,panel就能够。
RadioButton的样式是用path画的,能够看我另外一篇博客微信聊天和通信录按钮样式
二、聊天列表里,未读的消息上会有带数字的小红点,这个是用Button写的,Item的总体组成是Image(头像)、Button(未读数)、TextBlock(昵称、时间和聊天内容)
<Style x:Key="ListBoxItemChatStyle" TargetType="{x:Type ListBoxItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListBoxItem"> <Border> <StackPanel x:Name="sp" Orientation="Horizontal" Height="{Binding Converter={StaticResource objectToHeight}}" Background="{Binding Converter={StaticResource objectToColor}}"> <Grid> <Image Source="{Binding Icon}" Width="40" Height="40" Margin="10"/> <Button Foreground="White" Visibility="{Binding UnReadCount,Converter={StaticResource countToVisibility}}" Content="{Binding UnReadCount}" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,5" Style="{StaticResource CirButtonStyle}"/> </Grid> <Grid Width="176"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{Binding ShowName}" FontSize="15" HorizontalAlignment="Left" Margin="5,10,0,0"/> <TextBlock Grid.Row="0" Text="{Binding LastTime}" FontSize="15" HorizontalAlignment="Right" Margin="0,10,5,0"/> <TextBlock Grid.Row="1" Text="{Binding LastMsg}" FontSize="12" HorizontalAlignment="Left" Margin="5,0,0,0"/> </Grid> </StackPanel> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="Background" Value="#FFE2E4E6" TargetName="sp"/> </Trigger> <Trigger Property="IsSelected" Value="true"> <Setter Property="Background" Value="#FFCACDD3" TargetName="sp"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
三、聊天内容部分用的是ScrollingListBox,继承自ListBox,可是重写了里面的OnItemsChanged属性,保证能够时刻滚动到最后一行
public class ScrollingListBox : ListBox { protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.NewItems!=null) { int newItemCount = e.NewItems.Count; if (newItemCount > 0) this.ScrollIntoView(e.NewItems[newItemCount - 1]); base.OnItemsChanged(e); } } }
样式部分是重写控件模板用的是Image(头像),path(三角部分),textbox(内容部分)
<Style x:Key="ChatListBoxStyle" TargetType="{x:Type ListBox}"> <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <Setter Property="BorderBrush" Value="{StaticResource ListBorder}"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden"/> <Setter Property="ScrollViewer.CanContentScroll" Value="true"/> <Setter Property="ScrollViewer.PanningMode" Value="Both"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="ItemContainerStyle"> <Setter.Value> <Style TargetType="ListBoxItem"> <Setter Property="Focusable" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Top" FlowDirection="{Binding FlowDir}" Margin="15,5"> <Image Grid.Column="1" Source="{Binding Image}" Height="35" Width="35" VerticalAlignment="Top"/> <Path Grid.Column="2" StrokeThickness="1" Stroke="{Binding TbColor}" Data="M12,13 L5,18 L12,23Z" Fill="{Binding TbColor}" Margin="0" SnapsToDevicePixels="True"/> <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}"/> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </Setter.Value> </Setter> </Style>
须要注意的是:此处必需要重写控件模板,而不能重写数据模板,虽然,不少状况下控件模板和数据模板能够获得的效果相同,可是此处,若是写数据模板的话,则本身发的信息不会在右侧,就算设置FlowDirection也没有用,你们能够自行尝试。
四、若是发送内容是空的状况下,则会有一个ToolTip出现,此处的TooLTipye也是重写了样式的Button,好定位,毕竟就算是最大化,位置也是不变的。
通信录部分,和聊天列表差很少,不过,因为须要进行分组,也就是A、B……这种组合,因此用的Object类型,在点选过程当中,经过is来进行判别是否是WeChatUser,若是是,则进行转换,来进一步处理。
你们能够看到上面那个好友是同程旅游顾问<span ……其实它是一个emoji,只是如今我尚未作到那一部分,若是作到的话,则进行转换,若是谁有好的emoji处理方式但愿告知,谢谢了。
<Style x:Key="ListBoxItemFriendStyle" TargetType="{x:Type ListBoxItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListBoxItem"> <Border> <StackPanel x:Name="sp" Orientation="Horizontal" Height="{Binding Converter={StaticResource objectToHeight}}" Background="{Binding Converter={StaticResource objectToColor}}"> <TextBlock Text="{Binding }" FontWeight="Black" Visibility="{Binding Converter={StaticResource modelToVisibility}}" FontSize="15" Margin="10,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Center"/> <Image Source="{Binding Icon}" Width="40" Height="40" Margin="10"/> <TextBlock Text="{Binding ShowName}" FontSize="15" HorizontalAlignment="Left" Margin="5,10,0,0"/> </StackPanel> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="Background" Value="#FFE2E4E6" TargetName="sp"/> </Trigger> <Trigger Property="IsSelected" Value="true"> <Setter Property="Background" Value="#FFCACDD3" TargetName="sp"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
当点选列表之后,而且转换成功的状况下,则显示出用户的信息,经过内容是否未空,来判别是否要显示
<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"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Image Source="{Binding FriendInfo.Icon}" Grid.Row="0" Height="124" Width="124" HorizontalAlignment="Center"/> <StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Center"> <TextBlock Text="{Binding FriendInfo.NickName}" FontSize="30" Foreground="Black" FontWeight="Bold"/> <Image Visibility="{Binding FriendInfo.Sex,Converter={StaticResource parameterToVisibility},ConverterParameter=2}" Source="/Image/female.png"/> <Image Visibility="{Binding FriendInfo.Sex,Converter={StaticResource parameterToVisibility},ConverterParameter=1}" Source="/Image/male.png"/> </StackPanel> <TextBlock Text="{Binding FriendInfo.Signature}" Foreground="#FF919191" Grid.Row="2" HorizontalAlignment="Center"/> <StackPanel Orientation="Horizontal" Visibility="{Binding FriendInfo.RemarkName,Converter={StaticResource epmtyToVisibility}}" Margin="10" Grid.Row="3" HorizontalAlignment="Center"> <TextBlock Text="备 注" Margin="0,0,10,0" FontSize="15"/> <TextBlock Text="{Binding FriendInfo.RemarkName}" FontSize="15"/> </StackPanel> <StackPanel Orientation="Horizontal" Visibility="{Binding FriendInfo.Province,Converter={StaticResource epmtyToVisibility}}" Margin="10" Grid.Row="4" HorizontalAlignment="Center"> <TextBlock Text="地区" Margin="0,0,10,0" FontSize="15"/> <TextBlock Text="{Binding FriendInfo.Province}" Margin="0,0,2,0" FontSize="15"/> <TextBlock Text="{Binding FriendInfo.City}" FontSize="15"/> </StackPanel> <Button Content="发消息" Width="166" Height="37" Grid.Row="5" Command="{Binding FriendSendComamnd}" Margin="0,50,0,0" Style="{StaticResource FriSendButtonStyle}"/> <Grid Grid.Row="0" Grid.RowSpan="7" Background="WhiteSmoke" Visibility="{Binding FriendInfo,Converter={StaticResource nullToVisibility}}"/> </Grid>
点击发消息按钮,则跳转回聊天页面,而后,将当前的好友加入到聊天的第一项。
作WPF微信DEMO,用到了转换器,转换颜色,转换显隐;重写了控件的样式,例如Button、RadioButton、ListBox;而后MVVM模式下,Bing的用法,感受这个DEOM对于初学者来讲应该会有很大的帮助。
不过这个DEMO的BUG和不完善的地方还有不少,例如系统托盘尚未作闪烁,如今只能发送文字,最大化的问题。
系统托盘闪烁能够用Timer和Opacity来进行控制,好比来未读消息了,则在进行时间间隔的控制显隐。
后期会把TextBox换成RichTextBox,这样能够发送图片和emoji。
最大化问题,是我一直尚未想到好的解决办法,最大化的状况下会占据整个屏幕,而不把状态栏空出来,网上的办法都是从新设置Width和Height,可是这样的话,就要记录原来的大小和位置,一直没有找到能够重写WindowState.Maximized的方法,好像是不能重写,因此比较纠结,但愿哪位大神看完个人代码之后,可以给提供一下解决思路,谢谢了。
源码:GitHub