【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&#x000A;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="&#xec09; 保存" 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="&lt;-------- 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("写入成功");