WPF 全栈项目学习(操作物理机)

140 阅读4分钟

本文是对操作物理机的 WPF 项目的代码学习笔记,涉及的内容包括 C# PLC 通讯等内容。

1. SystemRunningInfo 代码学习

1. 内存和 CPU 使用率

Task.Run(async () =>
{
    PerformanceCounter cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
    PerformanceCounter ramCounter = new PerformanceCounter("Memory", "Available MBytes");
    await Task.Delay(3000);

    while (true)
    {
        cpuCounter.NextValue();
        await Task.Delay(200);
        var cpuUsage = cpuCounter.NextValue();
        var ramUsage = ramCounter.NextValue();
    }
});

2. 字符串和日期的格式化输出

mainWindowViewModel.CpuInfo = $"{cpuUsage:f2}%";
mainWindowViewModel.SystemTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

3. timeSpan 的使用

var timeSpan = (DateTime.Now - MainWindowViewModel.startTime);

// timeSpan.Days timeSpan.Hours timeSpan.Minutes timeSpan.Seconds

4. DNS 上方法

Dns.GetHostName();
Dns.GetHostAddresses(Dns.GetHostName());

5. C# 中的 map 方法

instanceOfList.Select(m => m.ToString()).ToList();

6. Join 方法

string.Join("  ",address); // address as List<string>

7. 继承 AutoRun 的接口会自动被运行

将需要执行的 Task 写在子类的构造函数中,然后,只要实例化就能够保证 Task 被执行。

using System;
using System.Threading.Tasks;

namespace Auto.Demo
{
    /// <summary>
    /// 继承 IAutoRun 的接口会被自动运行
    /// </summary>
    public class AutoRunDemo : IAutoRun
    {
        public AutoRunDemo()
        {
            Task.Run(async () =>
            {
                while (true)
                {
                    Console.WriteLine(DateTime.Now);
                    await Task.Delay(200);
                }
            });
        }
    }
}

通过反射可以找到所有实现了 IAutoRun 接口的类,通过一个遍历,逐个实例化它们即可。

using System;
using System.Linq;
using System.Reflection;

namespace Auto.Demo
{
    public static class AutoRunner
    {
        public static void Start()
        {
            var types = Assembly.GetExecutingAssembly().GetTypes();
            var autoRunTypes = types
                .Where(t => t.IsClass && !t.IsAbstract && typeof(IAutoRun).IsAssignableFrom(t))
                .ToList();

            foreach (var type in autoRunTypes)
            {
                Activator.CreateInstance(type);
            }
        }
    }
}

8. 使用定时器统计计算机资源情况并自动在后台执行

整体代码如下所示:

using Application.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Application.Businesses
{
    /// <summary>
    /// 应用运行时系统相关信息
    /// </summary>
    public class SystemRunningInfo : IBase, IAutoRun
    {
        private IServiceProvider mServiceProvider;
        private MainWindowViewModel mainWindowViewModel;
        public SystemRunningInfo(IServiceProvider mServiceProvider)
        {
            Task.Run(async () =>
            {

                this.mServiceProvider = mServiceProvider;
                PerformanceCounter cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
                PerformanceCounter ramCounter = new PerformanceCounter("Memory", "Available MBytes");
                await Task.Delay(3000);
                mainWindowViewModel = mServiceProvider.GetViewModel<MainWindowViewModel>();
                mainWindowViewModel.SystemIPAddrss= GetAllIPAddress();
                while (true)
                {
                    cpuCounter.NextValue();
                    await Task.Delay(200);
                    var cpuUsage = cpuCounter.NextValue();
                    var ramUsage = ramCounter.NextValue();
                    mainWindowViewModel.CpuInfo = $"{cpuUsage:f2}%";
                    mainWindowViewModel.SystemMemory = $"{ramUsage}MB";
                    mainWindowViewModel.SystemTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                    var timeSpan = (DateTime.Now - MainWindowViewModel.startTime);
                    mainWindowViewModel.RunningTime = $"{timeSpan.Days}{timeSpan.Hours}小时{timeSpan.Minutes}{timeSpan.Seconds}秒";
                }
            });
        }
        public string GetAllIPAddress()
        {
            var address = Dns.GetHostAddresses(Dns.GetHostName()).Where(m => m.IsIPv6LinkLocal==false).Select(m => m.ToString()).ToList();
            address.Add(Dns.GetHostName());
            return string.Join("  ",address);

        }
    }
}

2. PrintConfig 代码学习

1. Tcp 连接和写值

// 1. 构建 TcpClient 和 NetworkStream

        /// <summary>
        /// 网络数据流
        /// </summary>
        NetworkStream NetworkStream;
        private TcpClient tcpClient = new TcpClient();

// 2. 连接到服务器(对 TcpClient 和 NetworkStream 赋值然后再连接)
        
        public Result Connect(string hostName = "127.0.0.1", int port = 9998)
        {
            try
            {
                Close();
                tcpClient = new TcpClient();
                tcpClient.Connect(hostName, port);
                NetworkStream = tcpClient.GetStream();
                return Result.Accept("连接成功");
            }
            catch (Exception err)
            {
                return Result.Reject($"连接失败,{err.Message}");
            }
        }

// 3. 关闭现有连接

        public void Close()
        {
            if (tcpClient != null)
            {
                tcpClient.Close();
                tcpClient.Dispose();
            }
        }

// 4. 通过网络写值
        private void Write<T>(T data)
        {
            this.Connect();
            var str = JsonConvert.SerializeObject(data, Formatting.Indented);
            var content = Encoding.UTF8.GetBytes(str);
            NetworkStream.WriteByte(0x02);
            NetworkStream.Write(content, 0, content.Length);
            NetworkStream.WriteByte(0x03);
            NetworkStream.Flush();
        }

// 5. 读取网络值
        private T Read<T>()
        {
            try
            {
                byte[] buffer = new byte[4096 * 400];
                var length = NetworkStream.Read(buffer, 0, buffer.Length);
                if (length >= 0)
                {
                    var array = buffer.Take(length).ToArray();
                    if (array.FirstOrDefault() == 0x02)
                    {
                        array = array.Skip(1).ToArray();
                        if (array.LastOrDefault() == 0x03)
                        {
                            array = array.Take(array.Length - 1).ToArray();
                            var content = Encoding.UTF8.GetString(array);
                            var str = JsonConvert.DeserializeObject<T>(content);
                            return str;
                        }
                    }

                    return default;
                }
                else
                {
                    this.Connect();
                    return default;
                }
            }
            catch (Exception)
            {
                return default;
            }
        }

上述代码中的细节:

  1. 数据流封装
    NetworkStream.WriteByte(0x02);  // 写入起始标志
    NetworkStream.Write(content, 0, content.Length);  // 写入数据
    NetworkStream.WriteByte(0x03);  // 写入结束标志
    NetworkStream.Flush();  // 刷新流,确保数据发送
  1. 数据流识别
if (array.FirstOrDefault() == 0x02)  // 检查起始标志
{
    array = array.Skip(1).ToArray();  // 去掉起始标志
    if (array.LastOrDefault() == 0x03)  // 检查结束标志
    {
        array = array.Take(array.Length - 1).ToArray();  // 去掉结束标志
        var content = Encoding.UTF8.GetString(array);  // 将字节数组转换为字符串
        var str = JsonConvert.DeserializeObject<T>(content);  // 反序列化为指定类型
        return str;
    }
}
  1. 将字节数组转换为字符串
        var content = Encoding.UTF8.GetString(array);  // 将字节数组转换为字符串
  1. 从流中读取一定长度的数据

先声明 buffer 然后向 buffer 中读取指定长度。

byte[] buffer = new byte[4096 * 400];
var length = NetworkStream.Read(buffer, 0, buffer.Length);
if (length >= 0)
{
    var array = buffer.Take(length).ToArray();
}
  1. TcpClient 和 NetworkStream 的创建和关闭
tcpClient = new TcpClient();
tcpClient.Connect(hostName, port);
NetworkStream = tcpClient.GetStream();

2. 通过 switch 获取 url

        public override string GetUri()
        {
            string serverPath = null;
            switch (ServerPath)
            {
                case ServerPoint.HangZhou:
                    serverPath = "http://mes-expose.hikvision.com:12304/ws/pkgPrintConfig";
                    break;
                case ServerPoint.ChongQing:
                    serverPath = "http://mescq-expose.hikvision.com:12304/ws/pkgPrintConfig";
                    break;
                case ServerPoint.WuHan:
                    serverPath = "http://himeswh-expose.hikvision.com.cn:12304/ws/pkgPrintConfig";
                    break;
                case ServerPoint.Others:
                    throw new NotSupportedException("不支持改服务器类型");
                default:
                    break;
            }
            return serverPath;
        }

3. 反序列化获得附加方法

如果 T 是一个类类型,并且 T 上定义了方法 A,那么通过 JsonConvert.DeserializeObject<T>(rawData) 反序列化后得到的对象 res 也会有方法 A。

4. 封装一个 Result 类

看看一个简单的结果,会被封装成多么复杂:

1. ResultCore<TStatus>
public abstract class ResultCore<TStatus>
{
    // 状态
    public TStatus Status { get; set; }

    // 构造函数
    protected ResultCore(TStatus status)
    {
        Status = status;
    }

    // 验证是否成功
    protected abstract bool ValidateSuccess(TStatus status);
}
  • 说明:
    • ResultCore<TStatus> 是一个泛型抽象类,用于表示一个操作的结果核心。
    • Status 属性表示操作的状态,类型为泛型 TStatus
    • 构造函数 ResultCore(TStatus status) 用于初始化 Status 属性。
    • ValidateSuccess(TStatus status) 是一个抽象方法,用于验证操作是否成功,具体的实现由子类提供。
2. ResultCore
public abstract class ResultCore : ResultCore<bool>
{
    // 是否出错
    public bool IsError { get; set; }

    // 是否成功
    public bool IsSuccess => !IsError;

    // 错误内容
    public virtual string Message { get; set; }

    // 构造函数
    public ResultCore(bool isError, string message = null)
        : base(isError)
    {
        IsError = isError;
        Message = message;
    }

    // 验证是否成功
    protected override bool ValidateSuccess(bool status)
    {
        return !status;
    }
}
  • 说明:
    • ResultCoreResultCore<bool> 的具体实现,用于表示一个布尔状态的结果核心。
    • IsError 属性表示操作是否出错。
    • IsSuccess 属性表示操作是否成功,它是 IsError 的反值。
    • Message 属性用于存储错误信息或成功信息。
    • 构造函数 ResultCore(bool isError, string message = null) 用于初始化 IsErrorMessage 属性。
    • ValidateSuccess(bool status) 方法重写了父类的抽象方法,用于验证操作是否成功。在这里,statusfalse 表示成功。
3. Result
public class Result : ResultCore
{
    // 构造函数
    public Result(bool isError, string message)
        : base(isError, message)
    {
    }

    // 结果必须大于0
    public static Result MustGreaterZero(int result, string failedMessage)
    {
        if (result > 0)
        {
            return Accept();
        }

        return Reject(failedMessage);
    }

    // 成功结果
    public static Result Accept(string message = null)
    {
        return new Result(isError: false, message ?? "OK");
    }

    // 失败结果
    public static Result Reject(string message)
    {
        return new Result(isError: true, message);
    }
}
  • 说明:
    • Result 类是 ResultCore 的具体实现,用于表示一个操作的结果。
    • 构造函数 Result(bool isError, string message) 用于初始化 IsErrorMessage 属性。
    • MustGreaterZero(int result, string failedMessage) 是一个静态方法,用于检查 result 是否大于 0。如果大于 0,返回成功结果;否则返回失败结果,并附带失败信息。
    • Accept(string message = null) 是一个静态方法,用于返回一个成功的结果对象,默认消息为 "OK"。
    • Reject(string message) 是一个静态方法,用于返回一个失败的结果对象,并附带失败信息。

总结

  • ResultCore<TStatus> 是一个泛型抽象类,用于定义操作结果的核心结构。
  • ResultCoreResultCore<bool> 的具体实现,用于表示布尔状态的结果。
  • Result 类是 ResultCore 的具体实现,提供了更多的实用方法,如 MustGreaterZeroAcceptReject,用于生成成功或失败的结果对象。

这些类可以用于在应用程序中统一处理操作结果,特别是在需要返回成功或失败状态时,提供了一种结构化的方式来处理和传递结果信息。

注意:ResultCore : ResultCore<bool>public ResultCore(bool isError, string message = null) : base(isError) 中的 : base(isError). : base(isError) 是构造函数初始化列表的一部分,用于调用基类(父类)的构造函数。具体来说,它表示当前类的构造函数在执行之前,会先调用基类的构造函数,并将 isError 作为参数传递给基类的构造函数。

3. OPBase 代码学习

1. 字符串比较

"NoRead".Equals(code, StringComparison.OrdinalIgnoreCase)

2. 轮询方式等候资源

var result = new DeviceResult();
if (reader == null)
{
    result.IsError = true;
    result.Message = "设备连接失败";
}
else
{
    var codeRes = await reader.RequestAsync("start");
    int count = 0;
    while (codeRes.Content.IsError && count++ < repeatTime)
    {
        await Task.Delay(80);
        codeRes = await reader.RequestAsync("start");
    }
}

3. 两种构造数据的方式

1. 数据构造方式

第一种方式(GetMaterialMoveOutRequest
var request = new MesRequestBase<MaterialMoveOutInfo>
{
    Id = Guid.NewGuid().ToString(),
    MachineId = configuration[$"{opName}:MachineId"],
    WorkStation = configuration[$"{opName}:WorkStation"],
    Data = new MaterialMoveOutInfo(barcode, workNumber: workNumber)
};
request.Data.CraftData = null;
  • 特点
    1. 使用对象初始化器:通过对象初始化器语法直接设置 MesRequestBase<MaterialMoveOutInfo> 的属性。
    2. 分步设置属性:在创建对象后,进一步设置 Data.CraftData 属性。
    3. 灵活性高:可以在创建对象后动态调整属性值。
第二种方式(GetMaterialMoveInRequest
var MachineId = configuration[$"{opName}:MachineId"];
var WorkStation = configuration[$"{opName}:WorkStation"];
var request = new RequestWithWknum(WorkStation, MachineId, barcode, workNumber);
  • 特点
    1. 通过构造函数初始化:直接通过构造函数传递参数,一次性完成对象的初始化。
    2. 简洁性高:代码更简洁,直接通过构造函数完成所有必要的设置。

4. 生成唯一 id 值

Id = Guid.NewGuid().ToString();

5. C# 版本的 Promise.all

await Task.WhenAll(task);
await Task.WhenAll(task1, task2);

6. Task 上的常用 API 及启动和停止

Task 是 C# 中用于表示异步操作的核心类,属于 System.Threading.Tasks 命名空间。它提供了一种简洁的方式来处理异步编程,支持并发任务的启动、等待和取消。以下是 Task 的常见使用方式和一些重要的 API。


1. 启动任务

1.1 使用 Task.Run 启动异步任务

Task.Run 是启动异步任务的最常用方法,它会将工作项排队到线程池中执行。

Task task = Task.Run(() =>
{
    // 执行异步操作
    Thread.Sleep(1000); // 模拟耗时操作
    Console.WriteLine("Task completed!");
});
1.2 使用 Task.Start 启动任务

Task.Start 是另一种启动任务的方式,但它需要先创建一个 Task 对象,然后手动调用 Start 方法。

Task task = new Task(() =>
{
    Thread.Sleep(1000);
    Console.WriteLine("Task completed!");
});
task.Start();

2. 等待任务完成

2.1 使用 await 等待单个任务

await 关键字用于等待任务完成,同时不会阻塞主线程。

await task;
Console.WriteLine("Task has finished.");
2.2 使用 Task.Wait 等待单个任务

Task.Wait 是同步方法,会阻塞当前线程,直到任务完成。

task.Wait();
Console.WriteLine("Task has finished.");
2.3 使用 Task.WhenAll 等待多个任务

Task.WhenAll 用于等待多个任务完成。

Task task1 = Task.Run(() => { /* Task 1 */ });
Task task2 = Task.Run(() => { /* Task 2 */ });

await Task.WhenAll(task1, task2);
Console.WriteLine("All tasks have finished.");

3. 获取任务结果

3.1 使用 Task.Result 获取结果

Task.Result 是同步属性,会阻塞当前线程,直到任务完成,并返回任务的结果。

Task<int> task = Task.Run(() => 42);
int result = task.Result; // 阻塞直到任务完成
Console.WriteLine(result); // 输出:42
3.2 使用 await 获取结果

await 关键字可以用于等待任务完成并获取结果,而不会阻塞主线程。

Task<int> task = Task.Run(() => 42);
int result = await task;
Console.WriteLine(result); // 输出:42

4. 异步方法

4.1 定义异步方法

异步方法使用 async 关键字修饰,并返回 TaskTask<T>

public async Task<int> GetNumberAsync()
{
    await Task.Delay(1000); // 模拟异步操作
    return 42;
}
4.2 调用异步方法

使用 await 调用异步方法。

int number = await GetNumberAsync();
Console.WriteLine(number); // 输出:42

5. 取消任务

5.1 使用 CancellationToken

CancellationToken 用于取消任务的执行。

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

Task task = Task.Run(() =>
{
    while (!token.IsCancellationRequested)
    {
        Thread.Sleep(100);
        Console.WriteLine("Task is running...");
    }
}, token);

// 取消任务
cts.Cancel();

6. 异常处理

6.1 捕获任务中的异常

任务中的异常可以通过 try-catch 块捕获。

try
{
    await Task.Run(() =>
    {
        throw new Exception("Something went wrong!");
    });
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

7. 常见的 Task API

  • Task.Run:启动一个异步任务。
  • Task.WhenAll:等待多个任务完成。
  • Task.WhenAny:等待多个任务中的任意一个完成。
  • Task.Delay:创建一个延迟任务。
  • Task.FromCanceled:创建一个已取消的任务。
  • Task.FromException:创建一个抛出异常的任务。
  • Task.FromResult:创建一个已完成的任务,返回指定结果。

总结

Task 是 C# 中处理异步编程的核心工具。通过 Task.Run 启动任务,使用 await 等待任务完成,并通过 Task.WhenAll 等方法处理多个任务。Task 提供了丰富的 API,支持异步操作、结果获取、异常处理和任务取消。掌握这些基本用法,可以帮助你更高效地编写异步代码。

4. 创建文件夹保存照片

1. 创建文件夹的代码

public static string CreateFolder(string folderName)
{
    if(!Directory.Exists(folderName))
    {
        Directory.CreateDirectory(folderName);
    }
    return folderName;
}

2. 通过图片 uri 保存图片至指定位置

public void SaveBitmapImageDirectly(ImageSource imageSource, string filePath, string name)
{
    if(imageSource == null) return;
    BitmapSource bitmapSource = imageSource as BitmapSource;
    name += ".jpg";
    string outputFileName = filePath + name;
    CreateFolder(filePath);
    if(File.Exists(filePath)) File.Delete(filePath);

    using(FileStream fileStream = File.Create(filePath))
    {
        JpegBitmapEncoder encoder = new JpegBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(BitmapFrame.Create(bitmapSource)));
        encoder.Save(fileStream);
    }
}

3. 扫码枪原理

可以简单的将其理解成一种外设,就好像键盘一样是一种文字的输入设备。