前言
在该篇的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="" 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();
}
}
}
}