WPF使用TouchSocket实现Tcp client

4 篇文章 0 订阅
2 篇文章 0 订阅

前言

在该篇的Demo中,您可以找到以下内容:
1、TouchSocket的使用;
2、CommunityToolkit.Mvvm的使用;
3、AvalonDock可拖拽停靠控件的使用;
4、Visual Studio的Dark主题页面的实现;
5、MVVM框架开发使用;
6、RichTextBox控件的使用;

Demo下载地址: WPF Demo项目代码

1、页面展示

在这里插入图片描述

2、主页面UI代码

<Window x:Class="WPF_Demo_V2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo_V2"
        mc:Ignorable="d"
        Background="#1F1F1F"
        MouseLeftButtonDown="Window_MouseLeftButtonDown"
        WindowStartupLocation="CenterScreen"
        WindowState="Normal"
        xmlns:avalon="https://github.com/Dirkster99/AvalonDock"
        Title="WPF Demo V0.0.1" Height="768" Width="1024" >
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="./Resource/Dictionary/DarkStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>

            <!--<TextBlock Text="&#xe6ab;" FontFamily="./Resource/Font/#iconfont" TextAlignment="Center" Foreground="{DynamicResource Foreground}" FontSize="30" Background="{DynamicResource WindowBrush}"/>-->

            <Menu Grid.Column="1" Background="{DynamicResource WindowBrush}" Foreground="{DynamicResource Foreground}" HorizontalAlignment="Left" VerticalAlignment="Top">
                <MenuItem Header="File" >
                    <MenuItem Header="New"/>
                    <MenuItem Header="Open" />
                </MenuItem>
                <MenuItem Header="Edit" >
                    <MenuItem Header="Undo"/>
                    <MenuItem Header="Cut"/>
                    <MenuItem Header="Copy"/>
                </MenuItem>
                <MenuItem Header="View" >
                    <MenuItem Header="Explorer"/>
                    <MenuItem Header="Open"/>
                </MenuItem>
                <MenuItem Header="Tool" >
                    <MenuItem Header="Extentions"/>
                    <MenuItem Header="Options"/>
                </MenuItem>
                <MenuItem Header="Help" >
                    <MenuItem Header="Welcome"/>
                    <MenuItem Header="Support"/>
                    <MenuItem Header="About"/>
                </MenuItem>
            </Menu>
        </Grid>

        <avalon:DockingManager Grid.Row="1">
            <avalon:DockingManager.Theme>
                <avalon:Vs2013DarkTheme/>
            </avalon:DockingManager.Theme>
            <avalon:LayoutRoot>
                <avalon:LayoutPanel Orientation="Horizontal">
                    <avalon:LayoutAnchorablePaneGroup DockWidth="150" FloatingWidth="240">
                        <avalon:LayoutAnchorablePane x:Name="panelLeft">
                            <avalon:LayoutAnchorable Title="Tool"></avalon:LayoutAnchorable>
                            <avalon:LayoutAnchorable Title="Team"></avalon:LayoutAnchorable>
                        </avalon:LayoutAnchorablePane>
                    </avalon:LayoutAnchorablePaneGroup>
                    <avalon:LayoutPanel Orientation="Vertical">
                        <avalon:LayoutDocumentPaneGroup>
                            <avalon:LayoutDocumentPane x:Name="panelTop">
                                <avalon:LayoutDocument Title="TCP Server">
                                    <Frame Source="./View/TcpServerPage.xaml"/>
                                </avalon:LayoutDocument>
                                <avalon:LayoutDocument Title="TCP Client">
                                    <Frame Source="./View/TcpClientPage.xaml"/>
                                </avalon:LayoutDocument>
                                <avalon:LayoutDocument Title="MainWindow.xaml"></avalon:LayoutDocument>
                                <avalon:LayoutDocument Title="MainWindow.xaml.cs"></avalon:LayoutDocument>
                            </avalon:LayoutDocumentPane>
                        </avalon:LayoutDocumentPaneGroup>
                        <avalon:LayoutAnchorablePaneGroup DockMinHeight="30" DockHeight="100" FloatingHeight="180">
                            <avalon:LayoutAnchorablePane x:Name="panelBottom">
                                <avalon:LayoutAnchorable Title="Output"></avalon:LayoutAnchorable>
                                <avalon:LayoutAnchorable Title="Exception"></avalon:LayoutAnchorable>
                                <avalon:LayoutAnchorable Title="Error"></avalon:LayoutAnchorable>
                            </avalon:LayoutAnchorablePane>
                        </avalon:LayoutAnchorablePaneGroup>
                    </avalon:LayoutPanel>
                    <avalon:LayoutAnchorablePaneGroup DockMinWidth="50" DockWidth="150" FloatingWidth="270">
                        <avalon:LayoutAnchorablePane x:Name="panelRight" DockWidth="240" FloatingWidth="240">
                            <avalon:LayoutAnchorable Title="Solution"></avalon:LayoutAnchorable>
                            <avalon:LayoutAnchorable Title="Property"></avalon:LayoutAnchorable>
                        </avalon:LayoutAnchorablePane>
                    </avalon:LayoutAnchorablePaneGroup>
                </avalon:LayoutPanel>
            </avalon:LayoutRoot>
        </avalon:DockingManager>

        <Border Grid.Row="2" Background="{DynamicResource SliderBackground}" Height="20">

        </Border>
    </Grid>
</Window>

2、TCP client的UI代码

<Page x:Class="WPF_Demo_V2.View.TcpClientPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:WPF_Demo_V2.View"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="TcpClientPage">

    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../Resource/Dictionary/DarkStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="5*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.5*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="0.5*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Label Grid.Column="0" Margin="5" Content="服务器IP:" HorizontalContentAlignment="Right" Foreground="{DynamicResource Foreground}"/>
            <TextBox Grid.Column="1" Margin="5" Text="{Binding Ip}" Foreground="{DynamicResource Foreground}" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" Background="{DynamicResource SliderBackground}"/>
            <Label Grid.Column="2"  Margin="5" Content="Port:"  HorizontalContentAlignment="Right" Foreground="{DynamicResource Foreground}" />
            <TextBox Grid.Column="3" Margin="5" Text="{Binding Port}" Foreground="{DynamicResource Foreground}" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" Background="{DynamicResource SliderBackground}"/>
            <Button Grid.Column="4" Margin="40,5" BorderBrush="{DynamicResource Green}"  Content="{Binding BtnStartOrStop}" Command="{Binding StartOrStopClientCommand}" Background="{DynamicResource SliderBackground}" Foreground="{DynamicResource Foreground}"/>
        </Grid>
        <RichTextBox Grid.Row="1" x:Name="myRichTextBox" Background="{DynamicResource WindowBackground}" IsReadOnly="True" Foreground="{DynamicResource Foreground}">
            <RichTextBox.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="清空" Command="{Binding ClearRichTextData}"/>
                </ContextMenu>
            </RichTextBox.ContextMenu>
            <FlowDocument>
                <Paragraph x:Name="progressPara"></Paragraph>
            </FlowDocument>
        </RichTextBox>
        <!--<TextBox  Text="{Binding ServerLog}" IsReadOnly="True" Name="txtLog" AcceptsReturn="True" TextWrapping="Wrap" Background="{DynamicResource WindowBackground}" Foreground="{DynamicResource Foreground}"/>-->
        <Grid Grid.Row="2" Margin="0,2" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="3.5*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <TextBox Grid.Column="0" Text="{Binding ServerSendMsg}" x:Name="txtMsg"  AcceptsReturn="True" TextWrapping="Wrap" Background="{DynamicResource WindowBackground}" Foreground="{DynamicResource Foreground}" IsEnabled="{Binding IsEnable}"/>
            <Grid Grid.Column="1" >
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <Button Grid.Row="0" Content="发送消息" Margin="40,5" Command="{Binding SendMsgCommand}" BorderBrush="{DynamicResource Green}" Background="{DynamicResource SliderBackground}" Foreground="{DynamicResource Foreground}" IsEnabled="{Binding SendMagIsEnable}"/>
                <StackPanel Grid.Row="1" Orientation="Horizontal">
                    <TextBox Width="60" Margin="2" Text="{Binding AutoSendTimes}" VerticalAlignment="Center" HorizontalContentAlignment="Right"  Background="{DynamicResource WindowBackground}" Foreground="{DynamicResource Foreground}" IsEnabled="{Binding IsEnable}"/>
                    <TextBlock Text="ms" VerticalAlignment="Center" Background="{DynamicResource WindowBackground}" Foreground="{DynamicResource Foreground}"/>
                    <CheckBox VerticalAlignment="Center" Style="{DynamicResource CheckBoxStyle}" Content="定时发送" Width="80"  Background="{DynamicResource WindowBackground}" Foreground="{DynamicResource Foreground}" IsChecked="{Binding AutoSendIsCheck}"/>
                </StackPanel>

            </Grid>
        </Grid>
    </Grid>
</Page>

3、Tcp client后台代码实现

using System;
using System.Collections.Generic;
using System.DirectoryServices.ActiveDirectory;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Timers;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using CommunityToolkit.Mvvm;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using TouchSocket.Core;
using TouchSocket.Sockets;

namespace WPF_Demo_V2.ViewModel
{
    partial class TcpClientViewModel:ObservableObject
    {
        #region 变量
        private TcpClient tcpClient;

        private System.Timers.Timer timer;
        public Action<Brush, string> UpdataRichText { get; set; }
        public Action<Brush, string, Brush, string> UpdataRichTextLine { get; set; }
        public Action<bool> ClearRichText { get; set; }

        bool isStart = false;

        bool isAuto = false;
        #endregion

        #region 构造函数
        public TcpClientViewModel()
        {
            tcpClient = new TcpClient();

        }
        #endregion

        #region 属性
        [ObservableProperty]
        //[NotifyCanExecuteChangedFor(nameof())]
        public string? ip = "192.168.2.120";//CommunityToolkit.MVVM 属性用法1 小写首字母

        [ObservableProperty]
        public string? _Port = "2300";    //CommunityToolkit.MVVM 属性用法2 下划线+大写首字母

        [ObservableProperty]
        public string _BtnStartOrStop = "连接";

        [ObservableProperty]
        public string? _ServerSendMsg;

        [ObservableProperty]
        public string? _AutoSendTimes = "1000";//100ms

        [ObservableProperty]
        public bool? _SendMagIsEnable = false;

        [ObservableProperty]
        public bool? _IsEnable = true;

        [ObservableProperty]
        private bool _AutoSendIsCheck = false;

        /// <summary>
        /// AutoSendIsCheck属性已修改触发的事件,使用partial关键字自动补全该事件,可选
        /// </summary>
        /// <param name="value"></param>
        partial void OnAutoSendIsCheckChanged(bool value)
        {
            isAuto = value;
            IsEnable = !value;
            if (isAuto) {
                timer?.Dispose();
                timer = new System.Timers.Timer { Interval = Convert.ToUInt32(AutoSendTimes), AutoReset = true, Enabled = true };
                timer.Elapsed += (s, e) => Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    SendMsg();
                }));
            }
            else
            {
                if (timer != null)
                {
                    timer?.Dispose();
                }
            }
        }

        /// <summary>
        /// AutoSendIsCheck属性将要修改触发的事件,使用partial关键字自动补全该事件,可选
        /// </summary>
        /// <param name="value"></param>
        partial void OnAutoSendIsCheckChanging(bool value)
        {

        }
        #endregion

        #region 方法
        #region 开始/停止服务
        [RelayCommand]
        private void StartOrStopClient()      //CommunityToolkit.MVVM 命令command用法:Binding部分对应StartOrStopServerCommand 必须包含<MethodName>Command
        {
            if (!isStart)
            {
                BtnStartOrStop = "断开";
                isStart = true;
                if (string.IsNullOrWhiteSpace(Ip) || string.IsNullOrWhiteSpace(Port))
                {
                    MessageBox.Show("IP 和 Port 不能为空");
                    return;
                }
                try
                {
                    if (tcpClient != null)
                    {
                        tcpClient.Setup(new TouchSocket.Core.TouchSocketConfig()
                            .SetRemoteIPHost($"{Ip}:{Port}")
                            .ConfigureContainer(a => {
                                a.AddConsoleLogger();//添加一个日志注入
                            }));
                        tcpClient.Connect();//连接,连接不成功抛出异常
                        SendMagIsEnable = true;
                        tcpClient.Logger.Info("客户端成功连接");
                        tcpClient.Received = (client, e) => {
                            //从服务器收到信息
                            string mes = Encoding.UTF8.GetString(e.ByteBlock.Buffer, 0, e.ByteBlock.Len);
                            string ServerLog = $"接收到信息:{mes}";
                            DateTime dateTime = DateTime.Now;
                            string times = dateTime.ToString("yyyy-MM-dd HH:mm:ss");
                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                UpdataRichTextLine.Invoke((Brush)new BrushConverter().ConvertFromString("#FFD9ECFF"), $"【{times} Client】", (Brush)new BrushConverter().ConvertFromString("#55B155"), $"{ServerLog}");
                                //UpdataRichText.Invoke((Brush)new BrushConverter().ConvertFromString("#FFD9ECFF"), $"【{times} client】:");
                                //UpdataRichText.Invoke((Brush)new BrushConverter().ConvertFromString("#55B155"), $"{ServerLog}");
                            });
                            
                            Console.WriteLine($"接收到信息:{mes}");
                            return EasyTask.CompletedTask;
                        };
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"连接服务器失败: {ex.Message}");
                    BtnStartOrStop = "连接";
                    isStart = false;
                    SendMagIsEnable = false;
                }

            }
            else
            {
                BtnStartOrStop = "连接";
                isStart = false;
                SendMagIsEnable = false;
                // 关闭Socket
                if (tcpClient != null)
                {
                    tcpClient.Close();
                }
            }
        }
        #endregion

        #region 发送信息按钮事件
        [RelayCommand]
        private void SendMsg()
        {
            if(!string.IsNullOrEmpty(ServerSendMsg))
            {
                byte[] data = Encoding.Default.GetBytes(ServerSendMsg);
                tcpClient.Send(data);
            }
        }
        #endregion

        #endregion

    }
}

4、UI与后台代码的关联

using CommunityToolkit.Mvvm.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using WPF_Demo_V2.ViewModel;

namespace WPF_Demo_V2.View
{
    /// <summary>
    /// TcpClientPage.xaml 的交互逻辑
    /// </summary>
    public partial class TcpClientPage : Page
    {
        FlowDocument Doc = new FlowDocument();

        public TcpClientPage()
        {
            InitializeComponent();
            this.DataContext = new TcpClientViewModel() { UpdataRichText = ShowRichText, ClearRichText = ClearText, UpdataRichTextLine = ShowRichTextLine };
        }

        private void ShowRichText(Brush brush, string txt)
        {
            //this.Dispatcher.Invoke(() => {
                var p = new Paragraph(); // Paragraph 类似于 html 的 P 标签
                var r = new Run(txt); // Run 是一个 Inline 的标签
                p.Inlines.Add(r);
                p.LineHeight = 1;
                p.Foreground = brush;//设置字体颜色
                Doc.Blocks.Add(p);
                myRichTextBox.Document = Doc;
                myRichTextBox.ScrollToEnd();
            //});

        }

        private void ShowRichTextLine(Brush fbrush, string ftxt, Brush bbrush, string btxt)
        {
            //this.Dispatcher.Invoke(() => {
            var p = new Paragraph(); // Paragraph 类似于 html 的 P 标签
            var r1 = new Run(ftxt)
            {
                Foreground = fbrush
            };
            p.Inlines.Add(r1);
            var r2 = new Run(btxt)
            {
                Foreground = bbrush
            };
            p.Inlines.Add(r2);
            p.LineHeight = 1;
            Doc.Blocks.Add(p);
            myRichTextBox.Document = Doc;
            myRichTextBox.ScrollToEnd();
            //});

        }




        private void ClearText(bool obj)
        {
            if (obj)
            {
                System.Windows.Documents.FlowDocument doc = myRichTextBox.Document;
                doc.Blocks.Clear();
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hlpinghcg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值