1、先看颜值
2、开始干
1、创建项目
2、引入前面的通讯库
创建目录将前面生成的通讯库dll文件复制到项目的目录
本项目引入dll文件
3、创建命令基类
RelayCommand.cs代码
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MitsubishiMcToolWPF.Common
{
/// <summary>
/// 命令实现类
/// </summary>
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged;
/// <summary>
/// 要执行的操作
/// </summary>
private Action<object> executeActions;
/// <summary>
/// 是否可以执行的委托
/// </summary>
private Func<object, bool> canExecuteFunc;
/// <summary>
/// 构造函数 无参构造
/// </summary>
public RelayCommand() { }
/// <summary>
/// 通过执行的委托构造
/// </summary>
/// <param name="execute"></param>
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
/// <summary>
/// 通过执行的操作与是否可执行的委托
/// </summary>
/// <param name="execute">要执行的操作</param>
/// <param name="canExecute">是否可以被执行</param>
public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
this.executeActions = execute;
this.canExecuteFunc = canExecute;
}
/// <summary>
/// 命令是否可以执行
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
if (canExecuteFunc != null)
return this.canExecuteFunc(parameter);
else
return true;
}
/// <summary>
/// 要执行的操作
/// </summary>
/// <param name="parameter"></param>
public void Execute(object parameter)
{
if (executeActions == null)
return;
this.executeActions(parameter);
}
/// <summary>
/// 执行CanExecuteChanged事件
/// </summary>
public void OnCanExecuteChanged()
{
this.CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
}
4、创建PLC实体类
PLCMemoryModel.cs代码
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MitsubishiMcToolWPF.Model
{
/// <summary>
/// PLC内存模型类
/// </summary>
public class PLCMemoryModel
{
/// <summary>
/// 存储区
/// </summary>
public string Area { get; set; } = "";
/// <summary>
/// 地址
/// </summary>
public string Address { get; set; } = "";
/// <summary>
/// 数据类型
/// </summary>
public string DataType { get; set; } = "";
/// <summary>
/// 读取或写入数据
/// </summary>
public string Count { get; set; } = "";
}
}
5、添加资源字典,即样式文件
CommonResource.xaml完整代码
cs
<!--读取写入Button按钮样式-->
<Style x:Key="ButtonStyle" TargetType="Button">
<Setter Property="Background" Value="#3F85FF"></Setter>
<Setter Property="Foreground" Value="White"></Setter>
<Setter Property="FocusVisualStyle" Value="{x:Null}"></Setter>
<Setter Property="Margin" Value="5"></Setter>
<Setter Property="Height" Value="90"></Setter>
<Setter Property="FontSize" Value="16"></Setter>
<!--模板的样式-->
<Setter Property="Template">
<Setter.Value>
<!--Button单选按钮样式-->
<ControlTemplate TargetType="Button">
<Grid >
<Border Background="{TemplateBinding Background}" CornerRadius="5" >
<TextBlock Margin="10 5 10 5" Text="{TemplateBinding Content}" FontSize="{TemplateBinding FontSize}" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
</Border>
</Grid>
<ControlTemplate.Triggers>
<!--鼠标放上去时的触发器-->
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#1E5FD1" ></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--连接断开按钮样式-->
<Style x:Key="ConnStyle" TargetType="Button">
<Setter Property="Background" Value="#3F8666"></Setter>
<Setter Property="Foreground" Value="White"></Setter>
<Setter Property="FocusVisualStyle" Value="{x:Null}"></Setter>
<Setter Property="Margin" Value="5"></Setter>
<Setter Property="Height" Value="60"></Setter>
<Setter Property="FontSize" Value="16"></Setter>
<!--模板的样式-->
<Setter Property="Template">
<Setter.Value>
<!--Button单选按钮样式-->
<ControlTemplate TargetType="Button">
<Grid >
<Border Background="{TemplateBinding Background}" CornerRadius="5" >
<TextBlock Margin="10 5 10 5" Text="{TemplateBinding Content}" FontSize="{TemplateBinding FontSize}" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
</Border>
</Grid>
<ControlTemplate.Triggers>
<!--鼠标放上去时的触发器-->
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#1E5FD1" ></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--TextBox默认样式-->
<Style TargetType="{x:Type TextBox}" x:Key="txtTextBoxStyle">
<Setter Property="Width" Value="150"/>
<Setter Property="Height" Value="25"/>
<Setter Property="BorderBrush" Value="#FF105190"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="2,0"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="#FFE4E4E4" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
<!--TextBlock默认样式-->
<Style TargetType="{x:Type TextBlock}" x:Key="txtTextBlockStyle">
<Setter Property="Margin" Value="1"/>
<Setter Property="Height" Value="15"/>
</Style>
<!--页面下拉框样式-->
<LinearGradientBrush x:Key="ComboBox.Static.Background" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="#FFE4E4E4" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ComboBox.Static.Border" Color="#FF105190"/>
<!--combox默认样式-->
<Style x:Key="cboStyle" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="{StaticResource ComboBox.Static.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource ComboBox.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<Setter Property="Width" Value="150"/>
<Setter Property="Height" Value="25"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Padding" Value="6,3,5,3"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="Both"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
</Style>
6、创建viewmodel
viewmodel是视图模型,wpf采用的是mvvm的渲染模式,即控件绑定对象的属性,属性发生更改时,驱动控件的数据显示,而控件动作是由命令command执行
1、模型基类
ViewModelBase.cs代码
cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using static MitsubishiMcToolWPF.MsgBoxWindow;
namespace MitsubishiMcToolWPF.ViewModel
{
/// <summary>
/// 视图模型基类
/// </summary>
public class ViewModelBase : INotifyPropertyChanged
{
/// <summary>
/// 属性值发生更改时触发
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 执行更改
/// C#5.0中的新特性CallerMemberName
/// </summary>
/// <param name="propertyName"></param>
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// 成功消息提示
/// </summary>
/// <param name="message"></param>
/// <param name="title"></param>
/// <param name="buttons"></param>
public void ShowMessage(string message, string title = "提示", CustomMessageBoxButton buttons = CustomMessageBoxButton.OK)
{
Show(message, title, buttons, CustomMessageBoxIcon.Infomation);
}
/// <summary>
/// 错误消息框
/// </summary>
/// <param name="message"></param>
/// <param name="title"></param>
/// <param name="buttons"></param>
public void ShowError(string message, string title = "错误", CustomMessageBoxButton buttons = CustomMessageBoxButton.OK)
{
Show(message, title, buttons, CustomMessageBoxIcon.Error);
}
/// <summary>
/// 询问消息框
/// </summary>
/// <param name="message"></param>
/// <param name="title"></param>
/// <param name="buttons"></param>
/// <returns></returns>
public CustomMessageBoxResult ShowQuestion(string message, string title = "询问", CustomMessageBoxButton buttons = CustomMessageBoxButton.OKCancel)
{
return Show(message, title, buttons, CustomMessageBoxIcon.Question);
}
}
}
2、主页模型mainviewmodel
MainViewModel.cs代码
cs
using Mitsubishi.Communication.MC.Mitsubishi;
using MitsubishiMcToolWPF.Common;
using MitsubishiMcToolWPF.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MitsubishiMcToolWPF.ViewModel
{
/// <summary>
/// 视图模型
/// </summary>
public class MainViewModel : ViewModelBase
{
/// <summary>
/// mctcp对象
/// </summary>
A3E mctcp;
#region 属性
PLCMemoryModel readPLCModel = SetInitModel();
/// <summary>
/// 读取PLC
/// </summary>
public PLCMemoryModel ReadPLCModel
{
get { return readPLCModel; }
set
{
readPLCModel = ReadPLCModel;
OnPropertyChanged();//属性通知
}
}
PLCMemoryModel writePLCModel = SetInitModel();
/// <summary>
/// 写入PLC
/// </summary>
public PLCMemoryModel WritePLCModel
{
get { return writePLCModel; }
set
{
writePLCModel = WritePLCModel;
OnPropertyChanged();
}
}
/// <summary>
/// 初始化页面参数
/// </summary>
/// <returns></returns>
private static PLCMemoryModel SetInitModel()
{
PLCMemoryModel model = new PLCMemoryModel();
model.Area = "D";
model.DataType = "short";
model.Address = "100";
model.Count = "1";
return model;
}
private string hostName = "192.168.1.7";
/// <summary>
/// PLC地址
/// </summary>
public string HostName
{
get
{
return hostName;
}
set
{
hostName = value;
}
}
private string hostPort = "6000";
/// <summary>
/// PLC端口
/// </summary>
public string HostPort
{
get { return hostPort; }
set
{
hostPort = value;
}
}
/// <summary>
/// 存储区下拉框数据源
/// </summary>
public List<string> CboCustTypes
{
get
{
return new List<string>() { "D", "X", "Y", "M", "R", "S", "TS" };
}
}
/// <summary>
/// 数据类型
/// </summary>
public List<string> CboCustDatas
{
get
{
return new List<string>() { "short", "float", "bool" };
}
}
private string readWords = "";
/// <summary>
/// 读数结果
/// </summary>
public string ReadWords
{
get { return readWords; }
set
{
readWords = value;
OnPropertyChanged();
}
}
private string writeWords = "";
/// <summary>
/// 写入数据
/// </summary>
public string WriteWords
{
get { return writeWords; }
set
{
writeWords = value;
OnPropertyChanged();
}
}
private string connectWords = "当前未连接";
/// <summary>
/// 连接状态
/// </summary>
public string ConnectWords
{
get { return connectWords; }
set
{
connectWords = value;
OnPropertyChanged();
}
}
#endregion
#region 命令
/// </summary>
/// <summary>
///连接按钮的命令
/// </summary>
public ICommand ConnCommand
{
get
{
return new RelayCommand(o =>
{
var name = HostName;
var port = HostPort;
mctcp = new A3E(name, short.Parse(port));// 创建连接
var result = mctcp.Connect();// 开始连接PLC
if (!result.IsSuccessed)
{
ShowError(result.Message, "错误");
return;
}
ConnectWords = "PLC已连接";
ShowMessage("PLC连接成功", "提示");
});
}
}
/// </summary>
/// <summary>
///断开按钮的命令
/// </summary>
public ICommand CloseCommand
{
get
{
return new RelayCommand(o =>
{
if (mctcp == null)
{
ShowError("PLC没有连接,不能断开", "错误");
return;
}
mctcp = null;
ConnectWords = "PLC已断开";
});
}
}
/// </summary>
/// <summary>
/// 读取按钮的命令
/// </summary>
public ICommand ReadCommand
{
get
{
return new RelayCommand(o =>
{
ReadWords = "";
var d = ReadPLCModel;
if (mctcp == null)
{
ShowError("PLC未连接,不能读取数据", "错误");
return;
}
//内存地址
string plcAddr = ReadPLCModel.Area + ReadPLCModel.Address;
//读取数量
short readCount = short.Parse(ReadPLCModel.Count);
//数据类型
string dataType = ReadPLCModel.DataType;
switch (dataType)
{
case "short":
var datas2 = mctcp.Read<short>(plcAddr, readCount);
if (!datas2.IsSuccessed)
{
ShowMessage(datas2.Message, "提示");
return;
}
ReadWords = string.Join(",", datas2.Datas);
break;
case "float":
var datas3 = mctcp.Read<float>(plcAddr, readCount);
if (!datas3.IsSuccessed)
{
ShowMessage(datas3.Message, "提示");
return;
}
ReadWords = string.Join(",", datas3.Datas);
break;
case "bool":
var datas4 = mctcp.Read<bool>(plcAddr, readCount);
if (!datas4.IsSuccessed)
{
ShowMessage(datas4.Message, "提示");
return;
}
ReadWords = string.Join(",", datas4.Datas);
break;
}
});
}
}
/// </summary>
/// <summary>
/// 写入按钮的命令处理
/// </summary>
public ICommand WriteCommand
{
get
{
return new RelayCommand(o =>
{
if (mctcp == null)
{
ShowError("PLC未连接,不能写入数据", "错误");
return;
}
var d = WritePLCModel;
//内存地址
string plcAddr = WritePLCModel.Area + WritePLCModel.Address;
//写入数量
ushort writeCount = ushort.Parse(WritePLCModel.Count);
//数据类型
string dataType = WritePLCModel.DataType;
//实际数量
string objWriteVals = WriteWords;
ushort objWCount = (ushort)objWriteVals.Split(',').Length;
//实际数量与要求数量不一致,不允许操作
if (writeCount != objWCount)
{
ShowError("写入值的数量不正确!");
return;
}
List<string> vals = objWriteVals.Split(',').ToList();
switch (dataType)
{
case "short":
//实际数值转换成list集合 short类型
List<short> objshort = new List<short>();
vals.ForEach((x) =>
{
objshort.Add(short.Parse(x));
});
var finish2 = mctcp.Write<short>(objshort, plcAddr);
if (finish2.IsSuccessed)
{
ShowMessage("写入成功", "提示");
}
break;
case "float":
//实际数值转换成list集合 float
List<float> objfloat = new List<float>();
vals.ForEach((x) =>
{
objfloat.Add(float.Parse(x));
});
var finish3 = mctcp.Write<float>(objfloat, plcAddr);
if (finish3.IsSuccessed)
{
ShowMessage("写入成功", "提示");
}
break;
case "bool":
//实际数值转换成list集合bool
List<bool> objbool = new List<bool>();
vals.ForEach((x) =>
{
if (x == "1")
{
objbool.Add(true);
}
else
{
objbool.Add(false);
}
});
var finish4 = mctcp.Write<bool>(objbool, plcAddr);
if (finish4.IsSuccessed)
{
ShowMessage("写入成功", "提示");
}
break;
}
});
}
}
#endregion
}
}
7、主界面布局
MainWindow.xaml完整代码
cs
<Window x:Class="MitsubishiMcToolWPF.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:MitsubishiMcToolWPF.ViewModel"
mc:Ignorable="d"
FontSize="13" FontFamily="Microsoft YaHei" ResizeMode="CanMinimize" Title="三菱PLC通讯工具" FontWeight="ExtraLight" WindowStartupLocation="CenterScreen" Height="450" Width="880" Name="mainWin" >
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid ShowGridLines="true">
<Grid.RowDefinitions>
<RowDefinition Height="62"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!--第一行标题-->
<Grid Grid.Row="0" Background="BlanchedAlmond">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="/imgs/log1.png" Width="60" Height="60" Margin="20,0" />
<TextBlock Grid.Column="1" Text="三菱MC协议通讯调试助手(Qna-3E模式)" FontSize="23" VerticalAlignment="Center" FontWeight="Bold" />
</Grid>
<!--第二行信息-->
<Grid Grid.Row="1" Margin="0 10 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!--第一列PLC参数-->
<StackPanel Grid.Column="0" Margin="20">
<TextBlock Text="PLC地址"/>
<TextBox Height="30" VerticalContentAlignment="Center" Padding="5,0" Margin="0,10" Text="{Binding HostName}" />
<TextBlock Text="端口号" Margin="0,10,0,0"/>
<TextBox Height="30" VerticalContentAlignment="Center" Padding="5,0" Margin="0,10" Text="{Binding HostPort}" />
<Button Content="连接" Margin="0,30,0,0" Height="30" Command="{Binding ConnCommand}" Style="{StaticResource ConnStyle}" />
<Button Content="断开" Margin="0,10" Height="30" Command="{Binding CloseCommand}" Style="{StaticResource ConnStyle}"/>
<TextBlock Text="{Binding ConnectWords,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Margin="0,10,0,0" Foreground="Blue"/>
</StackPanel>
<!--第二列分隔线-->
<GridSplitter Grid.Column="1" Background="#4490AC" Width="2" Height="450" Margin="0 -11 0 0"></GridSplitter>
<!--第三列操作区-->
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="80"/>
<RowDefinition Height="30"/>
<RowDefinition Height="60"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Margin="10">
<TextBlock Text="存储区:" Style="{StaticResource txtTextBlockStyle}" Margin="0 0 0 0" ></TextBlock>
<ComboBox ItemsSource="{Binding CboCustTypes}" SelectedValue="{Binding ReadPLCModel.Area}" Width="70" Style="{StaticResource cboStyle}"></ComboBox>
<TextBlock Text="数据类型:" Style="{StaticResource txtTextBlockStyle}" ></TextBlock>
<ComboBox ItemsSource="{Binding CboCustDatas}" SelectedValue="{Binding ReadPLCModel.DataType}" Width="70" Style="{StaticResource cboStyle}"></ComboBox>
<TextBlock Text="地址:" Style="{StaticResource txtTextBlockStyle}" ></TextBlock>
<TextBox Text="{Binding ReadPLCModel.Address}" Width="100" Style="{StaticResource txtTextBoxStyle}" />
<TextBlock Text="数量:" Style="{StaticResource txtTextBlockStyle}" ></TextBlock>
<TextBox Text="{Binding ReadPLCModel.Count}" Width="60" Style="{StaticResource txtTextBoxStyle}" />
<Button Content="读取" Height="30" Width="100" Command="{Binding ReadCommand}" CommandParameter="{Binding ElementName=loginWin}" Style="{StaticResource ButtonStyle}" />
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="10">
<TextBlock Text="读取结果(多个数据之间用逗号隔开)" Style="{StaticResource txtTextBlockStyle}" Margin="0 3 0 9"></TextBlock>
<TextBox Text="{Binding ReadWords,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="616" Style="{StaticResource txtTextBoxStyle}" Margin="-23,0,0,0" />
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Background="#4490AC" Width="706" Height="2" Margin="-1,0,0,0"></StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Margin="10">
<TextBlock Text="存储区:" Style="{StaticResource txtTextBlockStyle}" Margin="0 0 0 0"></TextBlock>
<ComboBox ItemsSource="{Binding CboCustTypes}" SelectedValue="{Binding WritePLCModel.Area }" Width="70" Style="{StaticResource cboStyle}"></ComboBox>
<TextBlock Text="数据类型:" Style="{StaticResource txtTextBlockStyle}" ></TextBlock>
<ComboBox ItemsSource="{Binding CboCustDatas}" SelectedValue="{Binding WritePLCModel.DataType }" Width="70" Style="{StaticResource cboStyle}"></ComboBox>
<TextBlock Text="地址:" Style="{StaticResource txtTextBlockStyle}" ></TextBlock>
<TextBox Text="{Binding WritePLCModel.Address }" Width="100" Style="{StaticResource txtTextBoxStyle}" />
<TextBlock Text="数量:" Style="{StaticResource txtTextBlockStyle}" ></TextBlock>
<TextBox Text="{Binding WritePLCModel.Count}" Width="60" Style="{StaticResource txtTextBoxStyle}" />
<Button Content="写入" Height="30" Width="100" Command="{Binding WriteCommand}" CommandParameter="{Binding ElementName=loginWin}" Style="{StaticResource ButtonStyle}" />
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Margin="10">
<TextBlock Text="写入数据(多个数据之间用逗号隔开)" Style="{StaticResource txtTextBlockStyle}" Margin="0 3 0 9" ></TextBlock>
<TextBox Text="{Binding WriteWords ,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="616" Style="{StaticResource txtTextBoxStyle}" Margin="-23,0,0,0" />
</StackPanel>
</Grid>
</Grid>
</Grid>
</Window>
8、启动MC服务器
9、异常错误处理
10、操作测试
1、连接PLC
2、读取数据,short,float,bool
3、写入数据,short,float
写入short
写入float
3、小结
走过路过不要错过,点赞关注收藏又圈粉,共同致富。
欢迎抄袭,复制,分享,转载,学习,截图..............
欢迎抄袭,复制,分享,转载,学习,截图..............