請來圍觀:WPF開發的微信客戶端

請來圍觀:WPF開發的微信客戶端

做WPF微信DEMO,用到了轉換器,轉換顏色,轉換顯隱;重寫了控制項的樣式,例如Button、RadioButton、ListBox;然後MVVM模式下,Bind的用法,感覺這個DEMO對於初學者來說應該會有很大的幫助。

最後更新 2022/5/20 上午7:14
眾尋
預計閱讀 10 分鐘
分類
WPF
標籤
.NET WPF MVVM

本文經原作者授權以原創方式二次分享,歡迎轉載、分享。

原文作者:眾尋

原文連結:https://www.cnblogs.com/ZXdeveloper/p/6058206.html


公司的同事離職了,接下來的日子可能會忙碌,能完善 DEMO 的時間也會少了,因此,把做的簡易 DEMO 整體先記錄一下,等後續不斷地完善。

參考兩位大神的日誌:WEB 版微信協議部分功能分析【完全開源】微信客戶端.NET 版

尤其是周見智大神的 DEMO,因為好多和微信的服務端互動,都借鑒了大神的原始碼,幫助巨大,可以說我相當於做了一個翻版,只是用 WPF 開發的而已,外觀上不同,但是實際互動上是差不多的。

微信分為兩個部分,一個是登入,一個是主體,基於此,WPF 也主要是這兩個表單來實現。

一、登入模組

1、登入部分分為二維碼和獲取使用者頭像兩個頁面(因為是給予 WEB 的,所以沒有客戶端的登入按鈕,只能透過掃碼來登入)

在程式啟動以後,先透過請求獲取到二維碼,然後,在啟動一個新的執行緒,不斷地循環檢索登入狀態。

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,所以,需要用廣播來進行操作頁面的切換,即填充到登入表單中間的控制項是二維碼,還是頭像。

2、大家可以看到我上面的截圖部分包含了一部分的背景,這個是用 Snagit(推薦這個截圖工具,很好用)截圖時,自動截出的,因為表單本身的大小就是那麼大,多餘出來的部分是透明的,用來做二維碼滑動出現的效果部分。

當處於二維碼狀態時滑過,則出現動畫,頭像狀態下則沒有動畫,是設定了 Image 的 Visibility 屬性來控制的,滑動效果可以看我的另一篇部落格微信 二維碼滑鼠滑動 影像顯隱效果

3、當掃碼成功,並且在手機端點擊登入以後,則跳轉到主頁面,此處沒有加非同步等待處理,所以,使用者量大的朋友,請耐心等待(後期會加上)。

登入成功以後,就會出現主表單和系統托盤,主表單包含最近聯絡人和通訊錄,系統托盤網上很多解決方案,可以自行查找。

登入成功現在發現了一個問題,就是我有兩個微信號,其中一登入以後是有資料的,另一個則沒有資料。

追蹤程式碼,發現回傳的 Json 是空的,也就是說沒有回傳值,試驗了下周大神的程式碼,發現也是空的,不清楚什麼情況,我同事的有的也是空的,這個一直沒有深究,等把功能基本都完善以後再看看問題所在。

二、主表單模組

1、主表單的佈局部分很簡單,採用了 Grid 進行分隔,三列,上面的控制項如圖所示

大部分到沒什麼,可能大家比較疑惑的是我的聊天表單為什麼是 ListBox,這個東西的話,我認為,自己有自己的開發習慣,很多控制項都可以實現,panel 就可以。

RadioButton 的樣式是用 path 畫的,可以看我另一篇部落格微信聊天和通訊錄按鈕樣式

2、聊天列表裡,未讀的訊息上會有帶數字的小紅點,這個是用 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>

3、聊天內容部分用的是 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也沒有用,大家可以自行嘗試。

4、如果發送內容是空的情況下,則會有一個 ToolTip 出現,此處的 TooLTipye 也是重寫了樣式的 Button,好定位,畢竟就算是最大化,位置也是不變的。

通訊錄部分,和聊天列表差不多,不過,由於需要進行分組,也就是 A、B……這種組合,所以用的 Object 類型,在點選過程中,透過 is 來進行判別是不是 WeChatUser,如果是,則進行轉換,來進一步處理。

大家可以看到上面那個好友是 同程旅遊顧問<span …… 其實它是一個 emoji,只是現在我還沒有做到那一部分,如果做到的話,則進行轉換,如果誰有好的 emoji 處理方式希望告知,謝謝了。

當點選列表以後,並且轉換成功的情況下,則顯示出使用者的資訊,透過內容是否為空,來判別是否要顯示

<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 模式下,Bind 的用法,感覺這個 DEOM 對於初學者來說應該會有很大的幫助。

不過這個 DEMO 的 BUG 和不完善的地方還有很多,例如系統托盤還沒有做閃爍,現在只能發送文字,最大化的問題。

系統托盤閃爍可以用 Timer 和 Opacity 來進行控制,比如來未讀訊息了,則在進行時間間隔的控制顯隱。

後期會把 TextBox 換成 RichTextBox,這樣可以發送圖片和 emoji。

最大化問題,是我一直還沒有想到好的解決辦法,最大化的情況下會佔據整個螢幕,而不把狀態列空出來,網上的辦法都是重新設定 Width 和 Height,但是這樣的話,就要記錄原來的大小和位置,一直沒有找到可以重寫 WindowState.Maximized 的方法,好像是不能重寫,所以比較糾結,希望哪位大神看完我的程式碼以後,能夠給提供一下解決思路,謝謝了。

原始碼在這:https://github.com/yanchao891012/WPF_WeChat/

繼續探索

延伸閱讀

更多文章