Please come and watch: WeChat client developed by WPF

Please come and watch: WeChat client developed by WPF

To make WPF WeChat DEMO, you use a converter to convert colors, display and concealment; rewrite the style of the control, such as Button, RadioButton, ListBox; and then in MVVM mode, the usage of Bind. I feel that this DEOM should be very helpful to beginners.

最后更新 5/20/2022 7:14 AM
眾尋
预计阅读 12 分钟
分类
WPF
标签
.NET WPF MVVM

This article is shared twice in an original way with the authorization of the original author. Welcome to reprint and share.

Original author: Zhong Xun

Original link: https://www.example.com


When colleagues in the company leave, they may be busy in the next few days and have less time to improve the DEMO. Therefore, record the simple DEMO you made first and wait for continuous improvement.

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

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

WeChat is divided into two parts, one is login and the other is the main body. Based on this, WPF is mainly implemented in these two forms.

1. Login module

  1. The login part is divided into two pages: QR code and getting user avatar (because it is provided for WEB, there is no login button for the client, and you can only log in by scanning the code)

After the program is started, the QR code is first obtained through a request, and then a new thread is started to continuously retrieve the login status in a loop.

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");
        }
    }
}

Because it is an MVVM, broadcast is needed to switch the operation page, that is, whether the control filled in the middle of the login form is a QR code or an avatar.

  1. You can see that my screenshot above contains part of the background. This is automatically cut out when using Snagit (recommended this screenshot tool, which is very easy to use) to take a screenshot. Because the size of the form itself is so large, the extra part is transparent and used to make the effect of sliding the QR code.

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

  1. When the code is scanned successfully and you click login on the mobile phone, you will jump to the main page. There is no asynchronous waiting processing here, so please wait patiently for friends with a large number of users (it will be added later).

After you log in successfully, the main form and system tray will appear. The main form contains recent contacts and address book. There are many solutions on the system tray online. You can find them yourself.

After successfully logging in, I found a problem, that is, I have two WeChat signals, one of which has data after logging in, and the other has no data.

I tracked the code and found that the returned Json was empty, which means there was no return value. I tested the code from God next week and found that it was also empty. I don't know what the situation was. Some of my colleague's were also empty. I haven't delved into this. I will see the problem after I have basically improved the functions.

2. Main Form Module

  1. The layout part of the main form is very simple. It is separated by Grid and has three columns. The controls above are as shown in the figure.

Most of it is nothing. Maybe everyone is confused about why my chat form is ListBox. For this thing, I think I have my own development habits and can implement many controls, just a panel.

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

  1. In the chat list, there will be a small red dot with numbers on unread messages. This is written in Button. The overall composition of the Item is Image (avatar), Button (unread), TextBlock (nickname, time and chat content)
<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>

  1. The chat content part uses ScrollingListBox, which is inherited from ListBox, but the OnItemsChanged property inside is rewritten to ensure that you can always scroll to the last line
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);
        }
    }
}

The style part is to rewrite the control template using Image (avatar), path (triangle part), textbox (content part)

<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也没有用,大家可以自行尝试。

  1. If the sent content is empty, a ToolTip will appear. The ToolTiye here is also a Button with a rewritten style for easy positioning. After all, even if it is maximized, the position will remain unchanged.

The address book part is similar to a chat list. However, since it needs to be grouped, that is, a combination of A, B..., the Object type used is used. During the clicking process, is used to determine whether it is a WeChatUser. If yes, it is converted for further processing.

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

When the list is clicked and the conversion is successful, the user's information is displayed, and whether to display it is determined by whether the content is not empty

<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>

Click the Send Message button to jump back to the chat page, and then add your current friends to the first item in the chat.

3. Summary

To make WPF WeChat DEMO, you use a converter to convert colors, display and concealment; rewrite the style of the control, such as Button, RadioButton, ListBox; and then in MVVM mode, the usage of Bind. I feel that this DEOM should be very helpful to beginners.

However, there are still many bugs and imperfections in this DEMO. For example, the system tray has not yet flashed, and now it can only send text, which maximizes the problem.

The flashing of the system tray can be controlled by using Timer and Opacity. For example, if a message has not been read, it will be displayed and concealed at the time interval.

Later, the TextBox will be replaced by RichTextBox, so that pictures and emoji can be sent.

The problem of maximizing is that I have never thought of a good solution. Maximizing will occupy the entire screen without leaving the status bar empty. The online method is to reset the Width and Height, but in this case, I have to record the original size and position. I have never found a way to rewrite WindowState.Maximized, but it seems that I can't rewrite it, so I am quite confused. I hope that some great god can provide some solution ideas after reading my code. Thanks a lot.

源码在这:https://github.com/yanchao891012/WPF_WeChat/

Keep Exploring

延伸阅读

更多文章