【WPF & C# 编程】WPF&C#编程问题总结
1 年前
· 来自专栏
WPF上位机开发系列
随查随用,比较方便
1. 获取指定目录下文件、文件夹
DirectoryInfo folder = new DirectoryInfo(@"D:demos\images");
FileInfo[] files = folder.GetFiles();
DirectoryInfo[] folders = folder.GetDirectories();
// 判断文件夹是否存在,不存在则创建
DirectoryInfo folder = new DirectoryInfo(vm.StrSaveImagePath);
if (!folder.Exists)
folder.Create();
// 删除文件
File.Delete(files[i].FullName);
// 一些属性
FileInfo.Exists:文件是否存在;
FileInfo.Name:文件名;
FileInfo.Extensioin:文件扩展名;
FileInfo.FullName:文件完整路径;
FileInfo.Directory:文件所在目录;
FileInfo.DirectoryName:文件完整路径;
FileInfo.Length:文件大小(字节数);
FileInfo.IsReadOnly:文件是否只读;
FileInfo.CreationTime:文件创建时间;
FileInfo.LastAccessTime:文件访问时间;
FileInfo.LastWriteTime:文件修改时间;
2. string.Format 格式化输出
string.Format("{0:N1}", 56789); // 56,789.0
string.Format("{0:N2}", 56789); // 56,789.00
string.Format("{0:F1}", 56789); // 56789.0
string.Format("{0:F2}", 56789); // 56789.00
(56789 / 100.0).ToString("#.##"); // 567.89
(56789 / 100).ToString("#.##"); // 567
string.Format("{0:C}", 0.2) // ¥0.20
string.Format("{0:C1}", 23.15) // ¥23.2
string.Format("{0:D3}", 23) // 023
string.Format("{0:D2}", 1223) // 1223
string.Format("{0:N}", 14200) // 14,200.00
string.Format("{0:N3}", 14200.2458) // 14,200.246
string.Format("{0:P}", 0.24583) // 24.58%
string.Format("{0:P1}", 0.24583) // 24.6%
string.Format("{0:0000.00}", 12394.039) // 12394.04
string.Format("{0:0000.00}", 194.039) // 0194.04
string.Format("{0:###.##}", 12394.039) // 12394.04
string.Format("{0:####.#}", 194.039) // 194
3. 日期时间
// 当前时间
DateTime currentTime = DateTime.Now;
int year = currentTime.Year;
int month = currentTime.Month;
int day = currentTime.Day;
int hour = currentTime.Hour;
int minute = currentTime.Minute;
int second = currentTime.Second;
int millisecond = currentTime.Millisecond;
// 间隔
DateTime t1 = new DateTime(2021, 8, 20);
DateTime t2 = DateTime.Now;
double day = t2.Subtract(t1).TotalDays;
// 格式化输出
string.Format("{0:d}", System.DateTime.Now) // 2009-3-20
string.Format("{0:D}", System.DateTime.Now) // 2009年3月20日
string.Format("{0:f}", System.DateTime.Now) // 2009年3月20日 15:37
string.Format("{0:F}", System.DateTime.Now) // 2009年3月20日 15:37:52
string.Format("{0:g}", System.DateTime.Now) // 2009-3-20 15:38
string.Format("{0:G}", System.DateTime.Now) // 2009-3-20 15:39:27
string.Format("{0:m}", System.DateTime.Now) // 3月20日
string.Format("{0:t}", System.DateTime.Now) // 15:41
string.Format("{0:T}", System.DateTime.Now) // 15:41:50
4. 线程 Thread,任务 Task 传参
// 不带参数
Thread thread = new Thread(TestUnitThread);
thread.Start();
thread.IsBackground = true;
private void TestUnitThread()
Thread.Sleep(1000);
// 带参数:两种方式
Thread thread = new Thread(new ThreadStart(delegate { TestUnitThread("AutoCalibration"); }));
Thread thread = new Thread(() => { TestUnitThread("AutoCalibration"); });
thread.Start();
private void TestUnitThread(string command)
Thread.Sleep(1000);
// 无参
Task task = new Task(TestUnitThread);
task.Start();
// 有参
Task task = new Task(() => { TestUnitThread("RefreshCamera"); });
task.Start();
5. 计时器
private DispatcherTimer del_timer;
del_timer = new DispatcherTimer
// 一天执行一次
Interval = new TimeSpan(1, 0, 0, 0)
del_timer.Tick += new EventHandler(DeleteFiles);
del_timer.Start();
private void DeleteFiles(object sender, EventArgs e)
Thread.Sleep(1000);
6. 保存配置文件 Serializable 序列化和反序列化
MVVM 模式不行,可创建一个相同内容的类
// CfgBearingVMs.cs
[Serializable]
public class CfgBearingVMs
private int intSelectedIndexDel;
public int IntSelectedIndexDel
get => intSelectedIndexDel;
set { intSelectedIndexDel = value; }
// CfgBearingVM.cs
public class CfgBearingVM : ViewModelBase
private int intSelectedIndexDel;
public int IntSelectedIndexDel
get => intSelectedIndexDel;
set => Set(ref intSelectedIndexDel, value);
// 保存
CfgBearingVMs vm = new CfgBearingVMs();
using (FileStream stream = new FileStream(@"setting\setting.bin", FileMode.Create))
BinaryFormatter format = new BinaryFormatter();
format.Serialize(stream, vm);
stream.Close();
// 读取
using (FileStream stream = new FileStream("setting/setting.bin", FileMode.Open))
BinaryFormatter format = new BinaryFormatter();
CfgBearingVMs vm = format.Deserialize(stream) as CfgBearingVMs;
stream.Close();
7. 屏幕截图
private void CaptureScreen()
System.Drawing.Size size = Screen.PrimaryScreen.Bounds.Size;
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(size.Width, size.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap);
g.CopyFromScreen(0, 0, 0, 0, size);
bitmap.Save("screen.png", System.Drawing.Imaging.ImageFormat.Png);
8. TextBlock 多行文本
// 后台代码
TextBlock1.Text = "AAAAAAA\nBBBBBBBB";
XAML代码
<TextBlock Text="AAAAAAA
BBBBBB"/>
9. 控件相对位置、鼠标位置
// 相对于父级偏移量
Vector vector = VisualTreeHelper.GetOffset(myTextBlock);
Point currentPoint = new Point(vector.X, vector.Y);
// 相对于 Window 偏移量
GeneralTransform transform = myTextBlock.TransformToAncestor(this);
Point currentPoint = transform .Transform(new Point(0, 0));
// 鼠标当前位置
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
//Point pt= e.GetPosition(e.Device.Target);
Point pt= e.GetPosition(DrawingCanvas);
10. 屏幕尺寸
// 全屏尺寸
double width = SystemParameters.PrimaryScreenWidth;
double height = SystemParameters.PrimaryScreenHeight;
// 不含任务栏尺寸
double width = SystemParameters.WorkArea.Width;
double height = SystemParameters.WorkArea.Height;
11. 显示图像
XAML 代码
<Image Source="pack://application:,,,/images/test.bmp"/>
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(@"test.png", UriKind.RelativeOrAbsolute);
image.EndInit();
ImageShow.Source = image;
12. 程序暂停
// 线程里直接
Thread.Sleep(1000);
public static class DispatcherHelper
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents()
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrames), frame);
Dispatcher.PushFrame(frame);
catch (InvalidOperationException)
private static object ExitFrames(object frame)
((DispatcherFrame)frame).Continue = false;
return null;
// 暂停 1s
DateTime t = DateTime.Now.AddMilliseconds(1000);
while (DateTime.Now < t)
DispatcherHelper.DoEvents();
13. TabControl 隐藏 Header
TabItem Visibility="Collapsed"
14. 一维数组保存成文本
double[] a1 = new double[4];
a1[3] = 8;
File.WriteAllLines("a1.txt", a1.Select(d => d.ToString()));
15. 文件路径
string dirPath = @"D:\TestDir";
string filePath = @"D:\TestDir\TestFile.txt";
// 当前路径
Environment.CurrentDirectory;
// 文件或文件夹所在目录
Path.GetDirectoryName(filePath); // D:\TestDir
Path.GetDirectoryName(dirPath); // D:\
// 文件扩展名
Path.GetExtension(filePath); // .txt
// 文件名
Path.GetFileName(filePath); // TestFile.txt
Path.GetFileName(dirPath); // TestDir
Path.GetFileNameWithoutExtension(filePath); // TestFile
// 绝对路径
Path.GetFullPath(filePath); // D:\TestDir\TestFile.txt
Path.GetFullPath(dirPath); // D:\TestDir
// 更改扩展名
Path.ChangeExtension(filePath, ".jpg"); // D:\TestDir\TestFile.jpg
// 根目录
Path.GetPathRoot(dirPath); //D:\
// 生成路径
Path.Combine(new string[] { @"D:\", "BaseDir", "SubDir", "TestFile.txt" }); // D:\BaseDir\SubDir\TestFile.txt
// 生成随机文件
Path.GetRandomFileName();
// 创建临时文件
Path.GetTempFileName();
// 返回当前系统的临时文件夹的路径
Path.GetTempPath();
// 文件名中无效字符
Path.GetInvalidFileNameChars();
// 路径中无效字符
Path.GetInvalidPathChars();
16. 渐变色
// XAML
<Grid x:Name="GridBack">
<Grid.Background>
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="0.5" Color="Indigo"/>
<GradientStop Offset="1.0" Color="Violet"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Grid.Background>
</Grid>
// 等价后台代码
GradientStop color1 = new GradientStop
Offset = 0,
Color = Colors.Red,
GradientStop color2 = new GradientStop
Offset = 0.5,
Color = Colors.Indigo,
GradientStop color3 = new GradientStop
Offset = 1,
Color = Colors.Violet,
LinearGradientBrush brush = new LinearGradientBrush();
brush.GradientStops.Add(color1);
brush.GradientStops.Add(color2);
brush.GradientStops.Add(color3);
GridBack.Background = brush;
17. Button 复杂背景
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Background="White" BorderBrush="White">
<Polygon Points="100,25 125,0 200,25 125,50" Fill="LightBlue"/>
<Polygon Points="100,25 75,0 0,25 75,50" Fill="LightPink"/>
</Grid>
</Button>
18. 后台设置元素绑定
// XAML 绑定
<Slider x:Name="SliderFont" Margin="10" Minimum="10" Maximum="40" SmallChange="1" LargeChange="4"/>
<Button x:Name="ButtonFont" Content="Binding fontsize" Margin="5"
FontSize="{Binding ElementName=SliderFont, Path=Value, Mode=TwoWay}"/>
// 后台绑定
Binding binding = new Binding
Source = SliderFont,
Path = new PropertyPath("Value"),
Mode = BindingMode.TwoWay,
ButtonFont.SetBinding(FontSizeProperty, binding);
// 注意设置 Mode 为 TwoWay,否则若其它地方有修改,则绑定失效,比如
ButtonFont.FontSize = 30;
19. 资源样式动画
// 资源定义
<Window.Resources>
<ImageBrush x:Key="TileBrush" TileMode="Tile" ViewportUnits="Absolute" Viewport="0 0 16 16" Opacity="0.5"
ImageSource="Resource\Image\icon.ico"/>
<Style x:Key="BigFontButtonStyle" TargetType="Button">
<Setter Property="FontFamily" Value="Times New Roman"/>
<Setter Property="FontSize" Value="24"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<!-- 资源继承 -->
<Style x:Key="EmphasizeBigFont" BasedOn="{StaticResource BigFontButtonStyle}" TargetType="Button">
<Setter Property="Control.Foreground" Value="Magenta"/>
<Setter Property="Control.Background" Value="DarkBlue"/>
</Style>
<!-- 关联事件 -->
<Style x:Key="MouseOverHighlightStyle" TargetType="TextBlock">
<Setter Property="Padding" Value="5"/>
<EventSetter Event="MouseEnter" Handler="TextBlock_MouseEnter"/>
<EventSetter Event="MouseLeave" Handler="TextBlock_MouseLeave"/>
<!-- 事件触发器,字体缩放效果 -->
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize" To="24"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1" Storyboard.TargetProperty="FontSize"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
// 资源使用
<Button Content="DynamicResource" Margin="10" Background="{DynamicResource TileBrush}"/>
<Button Content="StaticResource" Margin="10" Background="{StaticResource TileBrush}"/>
<Button Content="ButtonStyle" Margin="10" Style="{StaticResource EmphasizeBigFont}"/>
<TextBlock Text="Hover over me" Margin="10" Style="{StaticResource MouseOverHighlightStyle}"/>
// 后台代码
// 后台修改资源,动态资源改变
Resources["TileBrush"] = new SolidColorBrush(Colors.LightGoldenrodYellow);
private void TextBlock_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
(sender as TextBlock).Background = new SolidColorBrush(Colors.LightGoldenrodYellow);
private void TextBlock_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
(sender as TextBlock).Background = null;
20. VS2019 C# 类注释模板
C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\IDE\ItemTemplates\CSharp\Code\2052\Class\Class.cs
namespace $rootnamespace$
/// ----------------------------------------------------------------
/// Author : Taosy.W
/// Created Time: $time$
/// Description :
/// ------------------------------------------------------
/// Version Modified Time Modified By Modified Content
/// V1.0.0.0 $time$ Taosy.W
public class $safeitemrootname$
21. 子线程更新UI
// 在新的线程里用如下方式更新到界面
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate ()
// 更新时间
StrCurTime = string.Format("{0:G}", DateTime.Now);
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
StrCurTime = string.Format("{0:G}", DateTime.Now);
22. ComboBox绑定枚举、显示描述
// 枚举
public enum Gender
Male = 0,
Female
// 资源搞一波
<Window.Resources>
<ObjectDataProvider x:Key="DemoGender" MethodName="GetValues" ObjectType="{x:Type core:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type Type="model:Gender"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource DemoGender}}" SelectedIndex="0"/>
// 显示描述属性
<ComboBox ItemsSource="{Binding EnumsDescription}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding ExampleProperty}"/>
// 添加描述属性
public enum Gender
[Description("男性")]
Male = 0,
[Description("女性")]
Female
// ViewModel
private Dictionary<Gender, string> enumsDescription;
public Dictionary<Gender, string> EnumsDescription
get => new Dictionary<Gender, string>()
{Gender.Male, "男性"},
{Gender.Female, "女性"},
set => Set(ref enumsDescription, value);
// 或者下面方式
private Dictionary<Gender, string> enumsDescription;
public Dictionary<Gender, string> EnumsDescription
Dictionary<Gender, string> pairs = new Dictionary<Gender, string>();
foreach (Gender item in Enum.GetValues(typeof(Gender)))
DescriptionAttribute attributes = (DescriptionAttribute)item.GetType().GetField(item.ToString()).GetCustomAttribute(typeof(DescriptionAttribute), false);
pairs.Add(item, attributes.Description);
return pairs;
set => Set(ref enumsDescription, value);
private Gender exampleProperty;
public Gender ExampleProperty
get => exampleProperty;
set => Set(ref exampleProperty, value);
23. DataGrid 指定行颜色
// 根据条件设置颜色
<DataGrid Height="400" SelectionMode="Single" HeadersVisibility="All" AutoGenerateColumns="False"
CanUserSortColumns="False" ItemsSource="{Binding DataListCalInfo}">
<DataGrid.Columns>
<DataGridTextColumn Width="auto" Binding="{Binding Header}" IsReadOnly="True" Header="项目"/>
<DataGridTextColumn Width="*" Binding="{Binding Content}" IsReadOnly="True" Header="描述">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEffective}" Value="false">
<Setter Property="Background" Value="{Binding ColorFalse}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsEffective}" Value="true">
<Setter Property="Background" Value="{Binding ColorTrue}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
/DataGrid>
24. 横竖分隔线
// 横向分隔线
<Separator Background="{DynamicResource PrimaryBrush}"/>
// 纵向分隔线
<GridSplitter Width="1" Background="{DynamicResource PrimaryBrush}"/>
25. 打开文件、选择文件夹对话框
// 打开文件对话框
using Microsoft.Win32;
OpenFileDialog dialog = new OpenFileDialog
Title = "选择标定图片",
Filter = "图像文件(*.jpg;*.png;*.bmp)|*.jpg;*.png;*.bmp",
RestoreDirectory = true,
if (dialog.ShowDialog() != true)
return;
string filename = dialog.FileName;
// 打开文件夹对话框
System.Windows.Forms.FolderBrowserDialog dialog = new System.Windows.Forms.FolderBrowserDialog();
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.Cancel)
return;
string foldername = dialog.SelectedPath.Trim();
26. TextBox 限定数字输入
// 设置 TextBox 的 TextChanged 事件
TextChanged="SetTextChanged"
private void SetTextChanged(object sender, TextChangedEventArgs e)
TextBox textBox = sender as TextBox;
TextChange[] change = new TextChange[e.Changes.Count];
e.Changes.CopyTo(change, 0);
int offset = change[0].Offset;
if (change[0].AddedLength > 0)
if (!int.TryParse(textBox.Text, out int num))
textBox.Text = textBox.Text.Remove(offset, change[0].AddedLength);
textBox.Select(offset, 0);
27. 声音报警
// 三种方式都可以
public class BeepMethod
public const uint BeepOk = 0x00000000;
public const uint BeepError = 0x00000010;
public const uint BeepQuestion = 0x00000020;
public const uint BeepWarning = 0x00000030;
public const uint BeepInformation = 0x00000040;
[DllImport("user32.dll")]
public static extern int MessageBeep(uint beepType);
// MessageBeep(BeepOk);
[DllImport("kernel32.dll", EntryPoint = "Beep")]
public static extern int Beep(int dwFreq, int dwDuration);
// BeepMethod.Beep(800, 300);
[DllImport("winmm.dll")]
public static extern bool PlaySound(string filename, int Mod, int Flags);
// PlaySound(@"C:\Windows\Media\Alarm01.wav", 0, 1);
// 连续播放
// PlaySound(@"C:\Windows\Media\Alarm01.wav", 0, 9);
28. 右键菜单隐藏
<Border x:Name="DrawingBorder" ClipToBounds="True">
<!-- 这是一个载体,用来放右键菜单 -->
<GroupBox x:Name="BackgroundCanvas" Background="White">
<GroupBox.ContextMenu>
<ContextMenu>
<MenuItem Header="载入相机" Command="{Binding CmdContextMenuEvent}" CommandParameter="LoadCamera"/>
<MenuItem Header="卸载相机" Command="{Binding CmdContextMenuEvent}" CommandParameter="CloseCamera"/>
<MenuItem Header="载入图像" Command="{Binding CmdContextMenuEvent}" CommandParameter="LoadImage"/>
<MenuItem Header="保存图像" Command="{Binding CmdContextMenuEvent}" CommandParameter="SaveImage"/>
<MenuItem Header="保存窗口" Command="{Binding CmdContextMenuEvent}" CommandParameter="SaveWindow"/>
</ContextMenu>
</GroupBox.ContextMenu>
</GroupBox>
<halcon:HSmartWindowControlWPF Name="HalconWPF" BorderThickness="1" BorderBrush="{DynamicResource PrimaryBrush}" IsHitTestVisible="False"/>
<InkCanvas x:Name="DrawingCanvas" Background="Transparent" IsHitTestVisible="False" EditingMode="None"/>
</Grid>
</Border>
// 给设置右键菜单的控件的父级控件添加事件
private void DrawingBorder_ContextMenuOpening(object sender, ContextMenuEventArgs e)
// 不显示右键菜单,可添加一些条件来控制是否显示
e.Handled = true;
29. 计算多边形质心
/// <summary>
/// 计算多边形的质心
/// </summary>
/// <param name="points"></param>
/// <returns></returns>
public static Point GetPolygonCenter(this StylusPointCollection points)
Point point = new Point(0, 0);
double area = 0;
double x0;
double y0;
double x1;
double y1;
double temp;
for (int i = 0; i < points.Count - 1; i++)
x0 = points[i].X;
y0 = points[i].Y;
x1 = points[i + 1].X;
y1 = points[i + 1].Y;
temp = (x0 * y1) - (x1 * y0);
area += temp;
point.X += (x0 + x1) * temp;
point.Y += (y0 + y1) * temp;
x0 = points[points.Count - 1].X;
y0 = points[points.Count - 1].Y;
x1 = points[0].X;
y1 = points[0].Y;
temp = (x0 * y1) - (x1 * y0);
area += temp;
point.X += (x0 + x1) * temp;
point.Y += (y0 + y1) * temp;
point.X /= 3 * area;
point.Y /= 3 * area;
return point;
30. 计算夹角
/// <summary>
/// 计算两点间的距离
/// </summary>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <returns></returns>
public static double GetDistancePP(Point p1, Point p2)
return Math.Sqrt(((p1.X - p2.X) * (p1.X - p2.X)) + ((p1.Y - p2.Y) * (p1.Y - p2.Y)));
// 余弦定理
/// <summary>
/// 计算夹角
/// </summary>
/// <param name="C"></param>
/// <param name="A"></param>
/// <param name="B"></param>
/// <returns></returns>
public static double GetPointAngle(Point C, Point A, Point B)
double a = GetDistancePP(B, C);
double b = GetDistancePP(A, C);
double c = GetDistancePP(A, B);
double cTheta = ((a * a) + (b * b) - (c * c)) / (2 * a * b);
cTheta = Math.Min(1, cTheta);
return Math.Acos(cTheta) * 180 / Math.PI;
31. 计算旋转方向
/// <summary>
/// 计算旋转方向:顺时针、逆时针
/// </summary>
/// <param name="p0"> 旋转中心 </param>
/// <param name="p1"> 起点 </param>
/// <param name="p2"> 终点 </param>
/// <returns></returns>
public static bool GetRotateDirection(Point p0, Point p1, Point p2)
Vector vector1 = p1 - p0;
Vector vector2 = p2 - p0;
bool direction;
// 向量叉乘判断
if (vector1.X * vector2.Y - vector1.Y * vector2.X > 0)
direction = true;
direction = false;
return direction;
32. 判断点是否在多边形内部
/// <summary>
/// 光线投影算法判断点是否在多边形内部
/// </summary>
/// <param name="pt"></param>
/// <param name="points"></param>
/// <param name="noneZeroMode"></param>
/// <returns></returns>
public static bool GetPointInPolygon(this StylusPointCollection points, Point pt, bool noneZeroMode = false)
int ptNum = points.Count;
if (ptNum < 3)
return false;
int j = ptNum - 1;
bool oddNodes = false;
int zeroState = 0;
for (int k = 0; k < ptNum; k++)
Point ptK = (Point)points[k];
Point ptJ = (Point)points[j];
if (((ptK.Y > pt.Y) != (ptJ.Y > pt.Y)) && (pt.X < (ptJ.X - ptK.X) * (pt.Y - ptK.Y) / (ptJ.Y - ptK.Y) + ptK.X))
oddNodes = !oddNodes;
if (ptK.Y > ptJ.Y)
zeroState++;
zeroState--;
j = k;
return noneZeroMode ? zeroState != 0 : oddNodes;
33. NPOI 读写 Excel
NuGet 下载 NPOI 包;
用 IWorkbook ISheet IRow ICell 统一管理避免踩坑;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using NPOI.SS.Util;
/// <summary>
/// 创建 Excel 并写入内容
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonCreateExcel_Click(object sender, RoutedEventArgs e)
string filename = @"data\test1.xlsx";
// 建议用 IWorkbook ISheet IRow ICell 统一管理避免不必要的错误
// workbook --> sheet --> row --> cell
// 创建空白工作簿
IWorkbook workbook = new XSSFWorkbook();
// 创建空白表格 指定 Sheet 名称
ISheet sheet = workbook.CreateSheet("Sheet1");
// 创建行 从 0 开始
IRow row = sheet.CreateRow(0);
// 在 row 中创建 cell 写内容
ICell cell = row.CreateCell(0);
cell.SetCellValue("行1列1");
cell = row.CreateCell(2);
cell.SetCellValue("行1列3");
row = sheet.CreateRow(1);
cell = row.CreateCell(1);
cell.SetCellValue("行2列2");
// 保存到本地
using (FileStream file = new FileStream(filename, FileMode.Create))
workbook.Write(file);
file.Close();
/// <summary>
/// 新增内容到现有 Excel
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonAppendExcel_Click(object sender, RoutedEventArgs e)
// 打开 Excel 文件
string filename = @"data\test1.xlsx";
using (FileStream file = new FileStream(filename, FileMode.Open))
IWorkbook workbook = new XSSFWorkbook(file);
// 获取指定 Sheet
ISheet sheet = workbook.GetSheet("Sheet1");
// 新增内容的行号
int row_idx = sheet.LastRowNum + 1;
// 添加行
IRow row = sheet.CreateRow(row_idx);
// 添加列
ICell cell = row.CreateCell(0);
// 赋值
cell.SetCellValue("新增行");
cell = row.CreateCell(2);
// 赋值
cell.SetCellValue(12.3);
// 保存
FileStream fs = new FileStream(filename, FileMode.Create);
workbook.Write(fs);
fs.Close();
file.Close();
/// <summary>
/// 设置单元格格式:合并、居中、字体颜色、背景颜色
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonCellStyleExcel_Click(object sender, RoutedEventArgs e)
string filename = @"data\test2.xlsx";
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet("Sheet1");
// 创建行 从 0 开始
IRow row = sheet.CreateRow(0);
// 在 row 中创建 cell 写内容
ICell cell = row.CreateCell(0);
cell.SetCellValue("工程");
// 居中
ICellStyle style = workbook.CreateCellStyle();
style.Alignment = NPOI.SS.UserModel.HorizontalAlignment.Center;
// 红色字体
IFont font = workbook.CreateFont();
font.Color = IndexedColors.Red.Index;
style.SetFont(font);
// 黄色填充
style.FillForegroundColor = IndexedColors.Yellow.Index;
style.FillPattern = FillPattern.SolidForeground;
// 设置样式
cell.CellStyle = style;
// 合并单元格
_ = sheet.AddMergedRegion(new CellRangeAddress(0, 0, 0, 5));
// 第二行
row = sheet.CreateRow(1);
cell = row.CreateCell(0);
cell.SetCellValue("项目1");
// 居中 + 黄色填充
style = workbook.CreateCellStyle();
style.Alignment = NPOI.SS.UserModel.HorizontalAlignment.Center;
style.FillForegroundColor = IndexedColors.Yellow.Index;
style.FillPattern = FillPattern.SolidForeground;
// 设置样式
cell.CellStyle = style;
_ = sheet.AddMergedRegion(new CellRangeAddress(1, 1, 0, 2));
cell = row.CreateCell(3);
cell.SetCellValue("项目2");
cell.CellStyle = style;
_ = sheet.AddMergedRegion(new CellRangeAddress(1, 1, 3, 5));
// 第三行
row = sheet.CreateRow(2);
for (int i = 0; i < 6; i += 3)
cell = row.CreateCell(i);
cell.SetCellValue("时间");
cell = row.CreateCell(i + 1);
cell.SetCellValue("子项1");
cell = row.CreateCell(i + 2);
cell.SetCellValue("子项2");
// 保存到本地
using (FileStream file = new FileStream(filename, FileMode.Create))
workbook.Write(file);
file.Close();
/// <summary>
/// 获取 Sheet 名称
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonSheetNamesExcel_Click(object sender, RoutedEventArgs e)
string filename = @"data\test.xlsx";
// 获取 Sheet 名称
IWorkbook workbook;
FileStream file;
using (file = File.OpenRead(filename))
// xls:HSSFWorkbook;
// xlsx::XSSFWorkbook
workbook = new XSSFWorkbook(file);
int sheet_count = workbook.NumberOfSheets;
string names = "";
for (int i = 0; i < sheet_count; i++)
names += string.Format("第 {0} 个 Sheet:{1}\n", i + 1, workbook.GetSheetName(i));
_ = MessageBox.Show(names);
/// <summary>
/// 读取指定 Sheet 的内容
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonReadContentExcel_Click(object sender, RoutedEventArgs e)
// 读取 Excel 指定 sheet 内容
// workbook --> sheet --> row --> cell
string filename = @"data\test.xlsx";
using (FileStream file = new FileStream(filename, FileMode.Open))
IWorkbook workbook = new XSSFWorkbook(file);
List<List<string>> content = new List<List<string>>();
// 读取名为 test 的 sheet
ISheet sheet = workbook.GetSheet("test");
IRow row;
ICell cell;
List<string> row_content;
for (int i = 0; i <= sheet.LastRowNum; i++)
row_content = new List<string>();
row = sheet.GetRow(i);
// 不为 null 则继续
if (row != null)
for (int j = 0; j < row.LastCellNum; j++)
// cell
cell = sheet.GetRow(i).GetCell(j);
if (cell != null)
// 如果是公式,则读取公式计算的值
if (cell.CellType == CellType.Formula)
cell.SetCellType(CellType.String);
row_content.Add(cell.StringCellValue);
// 这样显示的内容是公式
row_content.Add(cell.ToString());
row_content.Add("");
content.Add(row_content);
_ = MessageBox.Show("读取完成");
34. 子窗体关闭时刷新主窗体(WinForm)
// 主窗体调用子窗体
ExcelProcessForm form = new ExcelProcessForm();
form.ShowDialog();
if (form.DialogResult == DialogResult.Yes)
// 引用子窗体里的变量
string bool = form.FlagValue;
// 刷新主窗体内容
RefreshMainWindow();
// 子窗体声明 public 变量
public bool FlagValue = false;
// 子窗体事件
// 根据 DialogResult 来确定主窗体是否刷新 注意设置 DialogResult 保持子窗体关闭或继续
private void BtnExcelPath_Click(object sender, EventArgs e)
OpenFileDialog dialog = new OpenFileDialog()
Filter = "Excel (*.xlsx)|*.xlsx",
Multiselect = false,
RestoreDirectory = true,
if (dialog.ShowDialog() == DialogResult.OK)
CB_Sheet.Items.Clear();
CB_Sheet.Items.AddRange(sheet_names);
// 默认是 DialogResult.Cancel 执行完会自动关闭
// 设置为 DialogResult.None 保持窗体继续
DialogResult = DialogResult.None;
private void BtnOK_Click(object sender, EventArgs e)
DialogResult = DialogResult.Yes;
private void BtnCancel_Click(object sender, EventArgs e)
DialogResult = DialogResult.No;
35. ListBox 内容保存成文本、从文本读取到 ListBox
// 读取文本到 listBox 的 Items
OpenFileDialog dialog = new OpenFileDialog()
Filter = "指令流程文件 (*.cpf)|*.cpf",
Multiselect = false,
RestoreDirectory = true,
if (dialog.ShowDialog() == DialogResult.OK)
string filename = dialog.FileName;
ListItem.Items.Clear();
StreamReader reader = new StreamReader(filename);
string line;
while ((line = reader.ReadLine()) != null)
ListItem.Items.Add(line);
reader.Close();
// 保存 listBox 的 Items 内容
SaveFileDialog dialog = new SaveFileDialog()
Filter = "指令流程文件 (*.cpf)|*.cpf",
RestoreDirectory = true,
if (dialog.ShowDialog() == DialogResult.OK)
string filename = dialog.FileName;
StreamWriter writer = new StreamWriter(filename);
foreach (var item in ListItem.Items)
writer.WriteLine(item.ToString());
writer.Close();
36. 文件拷贝
File.Copy(oldPath, newPath, true);
37. 子线程不弹出 new 的窗体
FormQuery form = new FormQuery();
// 注意这里要给 this
form.ShowDialog(this);
38. 数字绑定格式
<TextBlock Text="{Binding NumPixelSize, StringFormat={}{0:F4} mm/pixel}"/>
39. IconFont 图标字体使用
// 文件添加到资源 Resource/iconfont.ttf
<FontFamily x:Key="IconFont">pack://application:,,,/Resource/#iconfont</FontFamily>
// 使用
<Button Content=" 保存" FontFamily="{StaticResource IconFont}"/>
// 后台设置
Btn.Content= "\xec09";
40. 窗体禁止拖拽、缩放
WindowStyle = "SingleBorderWindow";
ResizeMode = "CanMinimize";
WindowState = "Maximized";
// 重新设计窗体
WindowStyle="None"
WindowState="Maximized"
MaxWidth = SystemParameters.WorkArea.Width + 20;
MaxHeight = SystemParameters.WorkArea.Height + 12;
41. 获取硬盘序列号
/// <summary>
/// 获取硬盘序列号
/// </summary>
/// <returns></returns>
private string GetHardDiskSN()
string hdInfo = "";//硬盘序列号
ManagementObject disk = new ManagementObject("win32_logicaldisk.deviceid=\"c:\"");
hdInfo = disk.Properties["VolumeSerialNumber"].Value.ToString();
disk = null;
return hdInfo.Trim();
42. 读写 xml 文件
// xml 文件内容
<?xml version="1.0" encoding="utf-8"?>
<Calibaration>
<name>0</name>
<name>0</name>
</Down>
</Calibaration>
// 写入 xml 文件
// 创建文档
XDocument xDoc = new XDocument();
// 根节点
XElement root = new XElement("Calibaration");
// 添加根节点
xDoc.Add(root);
// 节点
XElement up = new XElement("Up");
XElement down = new XElement("Down");
up.SetElementValue("Name", CalibarationUp);
down.SetElementValue("Name", CalibarationDown);
// 添加节点
root.Add(up, down);
// 保存
xDoc.Save(@"calibaration\calibaration.xml");
// 读取 xml 文件
string xmlFileName = Path.Combine(Environment.CurrentDirectory, @"calibaration\calibaration.xml");
// 加载 xml 文件
XDocument xDoc = XDocument.Load(xmlFileName);
// 获取根
XElement root = xDoc.Root;
// 获取 Up 根
XElement element = root.Element("Up");
// 获取值
string up = element.Element("Name").Value;
PixelSizeUp = double.Parse(up);
PixelSizeDown = double.Parse(roots.Element("Down").Element("Name").Value);
// xml 文件内容
<?xml version="1.0" encoding="utf-8"?>
<Calibaration>
<PixelSize Up="0" Down="0" />
<CameraID Up="camera" Down="camera" />
</Calibaration>
// 写入 xml 内容
// 创建文档
XDocument xDoc = new XDocument();
// 根节点
XElement root = new XElement("Calibaration");
// 添加根节点 必须只有一个根节点
xDoc.Add(root);
// 节点
XElement pixelSize = new XElement("PixelSize");
XAttribute pixelUp = new XAttribute("Up", CalibarationUp);
XAttribute pixelDown = new XAttribute("Down", CalibarationDown);
pixelSize.Add(pixelUp, pixelDown);
// 添加节点
root.Add(pixelSize);
// 再添加上下相机的序列号
if (IntUpCamera >= 0 && IntDownCamera >= 0)
XElement cameraID = new XElement("CameraID");
XAttribute camUp = new XAttribute("Up", ListCameraInfo[IntUpCamera]);
XAttribute camDown = new XAttribute("Down", ListCameraInfo[IntDownCamera]);
cameraID.Add(camUp, camDown);
root.Add(cameraID);
// 保存
xDoc.Save(@"calibaration\calibaration.xml");
// 读取 xml 内容
string xmlFileName = Path.Combine(Environment.CurrentDirectory, @"calibaration\calibaration.xml");
// 加载 xml 文件
XDocument xDoc = XDocument.Load(xmlFileName);
// 获取所有根
XElement roots = xDoc.Root;
// 获取 PixelSize 根
XElement element = roots.Element("PixelSize");
// 获取值
string up = element.Attribute("Up").Value;
PixelSizeUp = double.Parse(up);
PixelSizeDown = double.Parse(roots.Element("PixelSize").Attribute("Down").Value);
CameraUpID = roots.Element("CameraID").Attribute("Up").Value;
CameraDownID = roots.Element("CameraID").Attribute("Down").Value;
43. 控件保存为图片
private void SaveToPng(FrameworkElement visual, string fileName)
PngBitmapEncoder encoder = new PngBitmapEncoder();
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)visual.ActualWidth, (int)visual.ActualHeight, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(visual);
BitmapFrame frame = BitmapFrame.Create(bitmap);
encoder.Frames.Add(frame);
using (FileStream stream = File.Create(fileName))
encoder.Save(stream);
44. 多线程执行完毕后处理
// 4 个检测任务同步执行
AutoResetEvent[] watchers = new AutoResetEvent[4];
for (int i = 0; i < watchers.Length; i++)
watchers[i] = new AutoResetEvent(false);
Hv_Tasks[0] = new Task(() =>
ObjectDetection(EnumImagePosition.LeftBack);
// 线程执行完的时候通知
_ = watchers[0].Set();
Hv_Tasks[1] = new Task(() =>
ObjectDetection(EnumImagePosition.LeftFront);
_ = watchers[1].Set();
Hv_Tasks[2] = new Task(() =>
ObjectDetection(EnumImagePosition.RightBack);
_ = watchers[2].Set();
Hv_Tasks[3] = new Task(() =>
ObjectDetection(EnumImagePosition.RightFront);
_ = watchers[3].Set();
Hv_Tasks[0].Start();
Hv_Tasks[1].Start();
Hv_Tasks[2].Start();
Hv_Tasks[3].Start();
// 确保所有线程都执行完毕
bool result = WaitHandle.WaitAll(watchers);
//for (int i = 0; i < watchers.Length; i++)
// result &= watchers[i].WaitOne();
if (result)
_ = Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate ()
// 重置显示信息
StrInfoDownBack = Hv_Results[0].ShowInfo;
StrInfoDownFront = Hv_Results[1].ShowInfo;
StrInfoUpBack = Hv_Results[2].ShowInfo;
StrInfoUpFront = Hv_Results[3].ShowInfo;
45. 文本显示小于号 <
<TextBlock Grid.Row="1" Text="<-------- dy ----"/>
46. 资源路径 图像路径
// UserControl 文件
<UserControl.Resources>
<ResourceDictionary>
<!-- 主题样式 -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
<ResourceDictionary Source="pack://application:,,,/WSlibs;component/Resource/Style/Geometry.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Image Source="pack://application:,,,/Resource/Image/GRP.ico"/>
// App.xaml 文件
<Application x:Class="GRP.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
<ResourceDictionary Source="Resource/Style/Geometry.xaml"/>
<ResourceDictionary Source="Resource/Style/Brush.xaml"/>
</ResourceDictionary.MergedDictionaries>
<FontFamily x:Key="IconFont">pack://application:,,,/Resource/#iconfont</FontFamily>
</ResourceDictionary>
</Application.Resources>
</Application>
// 后台获取
// 输出路径 bin 和 HalconWPF 同路径 所以用 ..\ 返回到上一层级
HOperatorSet.ReadImage(out Ho_Image, @"..\HalconWPF\Resource\Image\calibration_circle.bmp");
47. VS 2019 生成很慢很慢
解决:更新 VS
48. C# WinForm 调用 UserControl 用户控件的正确方式
新建项目:WPF 用户控件库(.NET Framework),生成 dll;
C# WinForm 窗体项目:Windows 窗体应用(.NET Framework),添加引用;
此时,工具箱中会有 UserControl 可供选择,一般不建议直接拖,很容易出错;
正确做法:
在 Window 上放置一个容器,然后后台生成控件,使用 ElementHost,将 Child 设置为 UserControl, Parent 设置为该容器;
MVVM 模式,可以获取 UserControl 的 DataContext;
using WSlibs;
using WSlibs.ViewModel;
private CamSetting MyCamSetting = new CamSetting();
private CamSettingVM MyCamSettingVM = new CamSettingVM();
private CamSettingVM MyCamSettingVM = new CamSettingVM();
// 两种方式都可
ElementHost host = new ElementHost()
Child = MyCamSetting,
Parent = tabPage1,
Height = 250,
Width = 400,
host.Show();
MyCamSettingVM = MyCamSetting.DataContext as CamSettingVM;
// 使用
MyCamSettingVM.SetCameraOpen();
_ = new ElementHost
Child = MyCamCalibration,
Parent = tabPage2,
Dock = DockStyle.Fill,
49. C# WinForm 调用 UserControl 用户控件在子线程里更新 UI 时 Application.Current 为 null
UserControl 的 ViewModel 中有线程在实时刷新时间并显示到界面,启动 C# WinForm 软件时报错
// 需要一个辅助类
public static class DispatcherHelper
public static Dispatcher Dispatcher => Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
// 更新 UI
// 一般情况 在新的线程里用如下方式更新到界面
_ = Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate ()
StrCurTime = string.Format("{0:G}", DateTime.Now);
// 解决方案:Application.Current 替换为 DispatcherHelper
_ = DispatcherHelper.Dispatcher.BeginInvoke(new Action(() =>
StrCurTime = string.Format("{0:G}", DateTime.Now);
50. 计算点到直线的距离
- 点到直线距离公式;
- 海伦公式;
/// <summary>
/// 计算两点间的距离
/// </summary>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <returns></returns>
public static double GetDistancePP(Point p1, Point p2)
return Math.Sqrt(((p1.X - p2.X) * (p1.X - p2.X)) + ((p1.Y - p2.Y) * (p1.Y - p2.Y)));
/// <summary>
/// 点到直线距离
/// </summary>
/// <param name="p0"></param>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <returns></returns>
public static double GetDistancePL(Point p0, Point p1, Point p2)
double dist = 0;
double x1 = p1.X;
double y1 = p1.Y;
double x2 = p2.X;
double y2 = p2.Y;
// 垂直情况
if (Math.Abs(x1 - x2) < 1e-6)
dist = Math.Abs(p0.X - x1);
// 根据点到直线距离公式计算
// 计算斜率和截距
double k = (y2 - y1) / (x2 - x1);
double b = (x2 * y1 - x1 * y2) / (x2 - x1);
dist = Math.Abs(k * p0.X - p0.Y + b) / Math.Sqrt(k * k + 1);
// 根据海伦公式计算
//double lenP1P2 = GetDistancePP(p1, p2);
//double lenP0P1 = GetDistancePP(p0, p1);
//double lenP0P2 = GetDistancePP(p0, p2);
//double len = 0.5 * (lenP0P1 + lenP0P2 + lenP1P2);
//double area = Math.Sqrt(len * (len - lenP0P1) * (len - lenP0P2) * (len - lenP1P2));
//dist = 2 * area / lenP1P2;
return dist;
51. InkCanvas 保存和读取 Stroke
对于默认的 Stroke 有效;
private StrokeCollection ModuleStrokeROI { get; set; } = new StrokeCollection();
// 保存模板对应的 Stroke
FileStream fileStream = new FileStream(filename.Replace("shm", "isf"), FileMode.Create, FileAccess.ReadWrite);
DrawingCanvas.Strokes.Save(fileStream);
fileStream.Close();
// 读取模板对应的 Stroke
FileStream fileStream = new FileStream(filename.Replace("shm", "isf"), FileMode.Open, FileAccess.Read);
DrawingCanvas.Strokes = new StrokeCollection(fileStream);
fileStream.Close();
52. 语音播报
private SpeechSynthesizer Synthesizer { get; set; } = new SpeechSynthesizer();
Synthesizer.SpeakAsync("合格品");
53. 定时删除文件
/// <summary>
/// 计时器 删除文件
/// </summary>
private void TimerDeleteFiles()
// 先执行一次
DeleteFiles(null, null);
// 计时器执行
DispatcherTimer update_timer = new DispatcherTimer
// 1 天执行一次
Interval = new TimeSpan(1, 0, 0, 0)
update_timer.Tick += new EventHandler(DeleteFiles);
update_timer.Start();
private void DeleteFiles(object sender, EventArgs e)
DirectoryInfo folder = new DirectoryInfo("image0");
if (folder.Exists)
FileInfo[] files = folder.GetFiles();
DateTime tt = DateTime.Now;
foreach (FileInfo item in files)
DateTime t0 = item.CreationTime;
TimeSpan dt = tt - t0;
// 删除 7 天以上的文件
if (dt.TotalDays >= 7)
File.Delete(item.FullName);
54. 遍历类的属性和值
// 遍历属性和值
CMetrologyObjectParams objParams = MyMetrologyObjectVM.GetMetrologyObjectParams();
PropertyInfo[] properties = objParams.GetType().GetProperties();
foreach (PropertyInfo item in properties)
string key = item.Name;
// 强制转换类型
HTuple value = (HTuple)item.GetValue(objParams, null);
HOperatorSet.SetMetrologyObjectParam(Hv_MetrologyHandle, "all", key, value);
55. 绑定到自身属性、父辈属性
// 绑定到自身属性
<Slider x:Name="SliderMove" Value="0" Minimum="-100" Maximum="100" ToolTip="{Binding ElementName=SliderMove, Path=Minimum}"/>
<Slider x:Name="SliderMove" Value="0" Minimum="-100" Maximum="100" ToolTip="{Binding RelativeSource={RelativeSource self}, Path=Value}"/>
// 绑定到父辈属性
<Slider x:Name="SliderMove" Value="0" Minimum="-100" Maximum="100" ToolTip="{Binding RelativeSource={RelativeSource AncestorType={x:Type hc:Shield}}, Path=Subject}"/>
56. ListBox滚动条位置
private void RefreshLogging(string item)
DateTime t = DateTime.Now;
if (LB_log.Items.Count > 10000)
LB_log.Items.RemoveAt(0);
int count = LB_log.Items.Count;
LB_log.Items.Add(item + t.ToString("yyyy-MM-dd HH:mm:ss:fff") + " 耗时:" + (t - T0_Time).Milliseconds + "ms");
// 滚动到当前行
LB_log.SelectedIndex = count;
LB_log.ScrollIntoView(LB_log.Items[count]);
// 数据绑定方式
/// <summary>
/// 操作列表 不要绑定字符串
/// </summary>
private ObservableCollection<CDataModel> listCmdOperators = new ObservableCollection<CDataModel>();
public ObservableCollection<CDataModel> ListCmdOperators
get => listCmdOperators;
set => Set(ref listCmdOperators, value);
/// <summary>
/// 添加算子
/// </summary>
public RelayCommand CmdAddOperator => new Lazy<RelayCommand>(() => new RelayCommand(AddOperator)).Value;
private void AddOperator()
if (IntSelectOperators < 0)
return;
int idx = IntSelectCmdOperators;
if (idx < 0)
// 末尾添加
idx = ListCmdOperators.Count - 1;
ListCmdOperators.Add(new CDataModel { Name = ListOperators[IntSelectOperators].Name });
// 当前位置下一行添加
ListCmdOperators.Insert(idx + 1, new CDataModel { Name = ListOperators[IntSelectOperators].Name });
// 选中新添加行
IntSelectCmdOperators = idx + 1;
private void MyCmdList_SelectionChanged(object sender, SelectionChangedEventArgs e)
int idx = MyCmdList.SelectedIndex;
if (idx < 0)
return;
// 滚动到指定行
MyCmdList.ScrollIntoView(MyCmdList.Items[idx]);
57. 序列化多个对象到文件
// 保存
// 单个对象
COperatorParams param = new COperatorParams();
using (FileStream stream = new FileStream(@"config\Task_1.cmds", FileMode.Create))
BinaryFormatter format = new BinaryFormatter();
format.Serialize(stream, param);
stream.Close();
// 两个对象
COperatorParams param1 = new COperatorParams();
COperatorParams param2 = new COperatorParams();
param2.Decompose3_Index = 1;
using (FileStream stream = new FileStream(@"config\Task_2.cmds", FileMode.Create))
BinaryFormatter format = new BinaryFormatter();
format.Serialize(stream, param1);
format.Serialize(stream, param2);
stream.Close();
// 多个对象
public List<COperatorParams> MyOperatorParamsList = new List<COperatorParams>();
using (FileStream stream = new FileStream(@"config\Task_3.cmds", FileMode.Create))
BinaryFormatter format = new BinaryFormatter();
format.Serialize(stream, MyOperatorParamsList);
stream.Close();
/// 加载
// 单个对象
using (FileStream stream = new FileStream(@"config/Task_1.cmds", FileMode.Open))
BinaryFormatter format = new BinaryFormatter();
COperatorParams param = format.Deserialize(stream) as COperatorParams;
stream.Close();
// 两个对象
using (FileStream stream = new FileStream(@"config\Task_2.cmds", FileMode.Open))
BinaryFormatter format = new BinaryFormatter();
COperatorParams param1 = format.Deserialize(stream) as COperatorParams;
COperatorParams param2 = format.Deserialize(stream) as COperatorParams;
stream.Close();
// 多个对象
using (FileStream stream = new FileStream(@"config\Task_3.cmds", FileMode.Open))
BinaryFormatter format = new BinaryFormatter();
MyOperatorParamsList = format.Deserialize(stream) as List<COperatorParams>;
stream.Close();
58. ModBus TCP 通讯
引用 HslCommunication.dll
private ModbusTcpNet MBS { get; set; }
if (MBS == null)
// 连接
MBS = new ModbusTcpNet("127.0.0.1");
OperateResult result = MBS.ConnectServer();
// 浮点数是这个顺序
MBS.DataFormat = HslCommunication.Core.DataFormat.CDAB;
if (!result.IsSuccess)
MBS.ConnectClose();
MBS = null;
return;
// 读取 100 的值
TB_value100.Text = MBS.ReadInt16("100").Content.ToString();
// 写入到 101 和 102
MBS.Write("101", (short)101);
MBS.Write("102", (float)102.1);
TB_value101.Text = MBS.ReadInt16("101").Content.ToString();
TB_value102.Text = MBS.ReadFloat("102").Content.ToString();
59. TCP IP 通讯
引用 SimpleTCP.dll
public partial class TcpDemo : UserControl
private SimpleTcpClient SimTcpClient { get; set; }
private char[] Delimiter { get; set; } = new char[1] { ',' };
private SimpleTcpServer SimTcpServer { get; set; }
public TcpDemo()
InitializeComponent();
SimTcpClient = new SimpleTcpClient
// 设置编码格式
StringEncoder = Encoding.ASCII,
// 设置分隔符,默认是0x13
Delimiter = Encoding.ASCII.GetBytes(Delimiter)[0]
// 接收到数据触发的事件
SimTcpClient.DataReceived += (mysender, msg) => { TcpDataRecieved(msg.MessageString); };
SimTcpServer = new SimpleTcpServer
// 设置编码格式
StringEncoder = Encoding.ASCII,
// 设置分隔符,默认是0x13
Delimiter = Encoding.ASCII.GetBytes(Delimiter)[0]
SimTcpServer.DataReceived += (mysender, msg) => { TcpDataRecieved(msg.MessageString); };
#region SimpleTCP 客户端
private void BtnSimTcpClientConnect_Click(object sender, RoutedEventArgs e)
SimTcpClient.Connect("192.168.1.1", 502);
private void BtnSimTcpClientWrite_Click(object sender, RoutedEventArgs e)
SimTcpClient.Write("SetUVar,B,1,0,100;");
Thread.Sleep(100);
SimTcpClient.Write("SetUVar,B,1,1,200;");
private void BtnSimTcpClientClose_Click(object sender, RoutedEventArgs e)
SimTcpClient.Disconnect();
#endregion
#region SimpleTCP 服务器
private void BtnSimTcpServerStart_Click(object sender, RoutedEventArgs e)
SimTcpServer.Start(IPAddress.Parse("192.168.1.10"), 502);
private void BtnSimTcpServerSend_Click(object sender, RoutedEventArgs e)
SimTcpServer.Broadcast("SetUVar,B,1,0,100;");
private void BtnSimTcpServerClose_Click(object sender, RoutedEventArgs e)
SimTcpServer.Stop();
#endregion
/// <summary>
/// 数据接收
/// </summary>
/// <param name="msg"></param>
private void TcpDataRecieved(string receiveMsg)
if (receiveMsg == "TCP_Server_OK\n" || receiveMsg == "TCP_Client_OK\n")
MessageBox.Show("连接成功");
else if (receiveMsg == "0000\n")
MessageBox.Show("写入成功");