公司的同事離職了,接下來的日子可能會忙碌,能完善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