C# WPF燃气报警器记录读取串口工具

2024-09-06 23:20

本文主要是介绍C# WPF燃气报警器记录读取串口工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

C# WPF燃气报警器记录读取串口工具

  • 概要
  • 串口帧数据
  • 布局文件
  • 代码文件
  • 运行效果
  • 源码下载

概要

  • 符合国标文件《GB+15322.2-2019.pdf》串口通信协议定义;
  • 可读取燃气报警器家用版设备历史记录信息等信息;
    在这里插入图片描述
    在这里插入图片描述

串口帧数据

串口通信如何确定一帧数据接收完成是个麻烦事,本文采用最后一次数据接收完成后再过多少毫秒认为一帧数据接收完成,开始解析出来。每次接收到数据更新一次recvTimems 。定时器mTimer定时周期10毫秒,定时器回调函数里判断接收时间超过20ms(这个时间的长短和串口波特率有关)认为一帧数据接收完成。接收数据时间差未超过20ms则将接收数据追加到rxBuf数据缓冲,

long recvTimems = 0;    // 用于计算串口接收完一帧数据
int rxLen = 0;  // 串口接收到的数据长度
byte[] rxBuff = new byte[128];  // 串口数据接收缓存
private static Timer mTimer;    // 定时器,10ms执行一次mTimer = new Timer(recvTimerCalback, null, 0, 10);  // 创建并启动定时器private void recvTimerCalback(object obj)
{//Console.WriteLine("timer callback!" + recvTimems);this.Dispatcher.Invoke(new Action(()=> {// UI线程分离,更新UI数据显示if (((Environment.TickCount - recvTimems) >= 20) && (rxLen > 0)){// 串口接收时间超过X ms认为一帧数据接收完成这里解析处理接收数据.....// 一帧数据处理结束清空数据缓冲Array.Clear(rxBuff, 0, rxBuff.Length);rxLen = 0;}}));  
}private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{SerialPort comPort = (SerialPort)sender;try{recvTimems = Environment.TickCount; // 更新串口数据接收时间//rxBuff[rxLen] = mSerialPort.ReadByte();int readCnt = mSerialPort.BytesToRead;Console.WriteLine("in readCnt:" + readCnt);mSerialPort.Read(rxBuff, rxLen, readCnt);rxLen += readCnt;Console.WriteLine("out rxLen:" + rxLen);}catch (Exception ce){MessageBox.Show(ce.Message);}            }

布局文件

XAML

<Window x:Class="GasAlarmTestTool.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:GasAlarmTestTool"mc:Ignorable="d"Title="燃气报警器接口工具" Height="600" Width="800"><Grid><TabControl><TabItem><TabItem.Header><StackPanel><Label>串口设置</Label></StackPanel></TabItem.Header><StackPanel Orientation="Horizontal"><GroupBox Header="串口" Width="200"><StackPanel><StackPanel Orientation="Horizontal"><Label Margin="3" Height="25" Width="50">端口号</Label><ComboBox x:Name="comboBoxCOM" Margin="3" Height="20" Width="130" DragDrop.Drop="comboBoxCOM_Drop"/></StackPanel><StackPanel Orientation="Horizontal"><Label Margin="3" Height="25" Width="50">波特率</Label><ComboBox x:Name="comboBoxBaudRate" Margin="3" Height="20" Width="130" SelectedIndex="0" IsEditable="True" ></ComboBox></StackPanel><StackPanel Orientation="Horizontal"><Label Margin="3" Height="25" Width="50">数据位</Label><ComboBox x:Name="comboBoxDataBit" Margin="3" Height="20" Width="130" SelectedIndex="3"><ComboBoxItem>5</ComboBoxItem><ComboBoxItem>6</ComboBoxItem><ComboBoxItem>7</ComboBoxItem><ComboBoxItem>8</ComboBoxItem></ComboBox></StackPanel><StackPanel Orientation="Horizontal"><Label Margin="3" Height="25" Width="50">停止位</Label><ComboBox x:Name="comboBoxStopBit" Margin="3" Height="20" Width="130" SelectedIndex="0"><ComboBoxItem>1位</ComboBoxItem><ComboBoxItem>1.5位</ComboBoxItem><ComboBoxItem>2位</ComboBoxItem></ComboBox></StackPanel><StackPanel Orientation="Horizontal"><Label Margin="3" Height="25" Width="50">校验位</Label><ComboBox x:Name="comboBoxSdd" Margin="3" Height="20" Width="130" SelectedIndex="0"><ComboBoxItem>无校验</ComboBoxItem><ComboBoxItem>奇校验</ComboBoxItem><ComboBoxItem>偶校验</ComboBoxItem><ComboBoxItem>1 校验</ComboBoxItem><ComboBoxItem>0 校验</ComboBoxItem></ComboBox></StackPanel><StackPanel Orientation="Horizontal"><Label Margin="3" Height="25" Width="50">流控位</Label><ComboBox x:Name="comboBoxlik" Margin="3" Height="20" Width="130" SelectedIndex="0" ><ComboBoxItem>无流控</ComboBoxItem><ComboBoxItem>RTS/CTS</ComboBoxItem><ComboBoxItem>XON/XOFF</ComboBoxItem></ComboBox></StackPanel><StackPanel Orientation="Horizontal" HorizontalAlignment="Center"><Button x:Name="btnOpenCloseCom" Margin="3" Width="80" Height="25" Click="btnOpenCloseCom_Click">打开串口</Button><Button x:Name="btnClearRecv" Margin="3" Width="80" Height="25" Click="btnClearRecv_Click">清空接收</Button></StackPanel></StackPanel></GroupBox><GroupBox Header="接收数据" MinWidth="590" MaxWidth="1000"><ScrollViewer><ScrollViewer.Content><TextBlock x:Name="textBlockRecv" MinWidth="300" MaxWidth="1000"></TextBlock></ScrollViewer.Content></ScrollViewer></GroupBox></StackPanel></TabItem><TabItem><TabItem.Header><StackPanel><Label>数据读取</Label></StackPanel></TabItem.Header><StackPanel Orientation="Horizontal"><StackPanel Orientation="Vertical"><GroupBox Header="记录总数"><StackPanel Orientation="Vertical"><Button x:Name="btnReadRecCount" Margin="3" Padding="5" Click="btnReadRecCount_Click">读取记录总数</Button><StackPanel Orientation="Horizontal"><Label>报警记录总数:</Label><TextBox x:Name="txtBoxWarningCount">0</TextBox></StackPanel><StackPanel Orientation="Horizontal"><Label>报警恢复记录总数:</Label><TextBox x:Name="txtBoxWarningRecoveryCount">0</TextBox></StackPanel><StackPanel Orientation="Horizontal"><Label>故障记录总数:</Label><TextBox x:Name="txtBoxFaultCount">0</TextBox></StackPanel><StackPanel Orientation="Horizontal"><Label>故障恢复记录总数:</Label><TextBox x:Name="txtBoxFaultRecoryCount">0</TextBox></StackPanel><StackPanel Orientation="Horizontal"><Label>掉电记录总数:</Label><TextBox x:Name="txtBoxPoweroffCount">0</TextBox></StackPanel><StackPanel Orientation="Horizontal"><Label>上电记录总数:</Label><TextBox x:Name="txtBoxPoweronCount">0</TextBox></StackPanel><StackPanel Orientation="Horizontal"><Label>传感器失效记录总数:</Label><TextBox x:Name="txtSensorInvalidCount">0</TextBox></StackPanel></StackPanel></GroupBox><GroupBox Header="日期时间"><StackPanel Orientation="Vertical"><Button x:Name="btnReadDateTime" Click="btnReadDateTime_Click">读取日期时间</Button><TextBox x:Name="txtBoxDateTime">0000-00-00 00:00</TextBox></StackPanel></GroupBox></StackPanel><StackPanel Orientation="Vertical"><GroupBox Header="读取指定记录"><StackPanel Orientation="Horizontal"><ComboBox x:Name="cmboxRecordType" Width="120" SelectedIndex="0"><ComboBoxItem>报警记录</ComboBoxItem><ComboBoxItem>报警恢复记录</ComboBoxItem><ComboBoxItem>故障记录</ComboBoxItem><ComboBoxItem>故障恢复记录</ComboBoxItem><ComboBoxItem>掉电记录</ComboBoxItem><ComboBoxItem>上电记录</ComboBoxItem><ComboBoxItem>传感器失效记录</ComboBoxItem></ComboBox><TextBox x:Name="txtBoxNumber" Width="50">1</TextBox><Button x:Name="btnReadRecord" Click="btnReadRecord_Click" ClickMode="Press">读取记录</Button><TextBox x:Name="txtBoxRecordInfo">0000/00/00  00:00</TextBox></StackPanel></GroupBox></StackPanel></StackPanel></TabItem></TabControl></Grid>
</Window>

代码文件

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;namespace GasAlarmTestTool
{/// <summary>/// MainWindow.xaml 的交互逻辑/// </summary>public partial class MainWindow : Window{private int warningCnt = 0;private int warningRecoveryCnt = 0;private int faultCnt = 0;private int faultRecoveryCnt = 0;private int poweroffCnt = 0;private int poweronCnt = 0;private int sensorInvalidCnt = 0;long recvTimems = 0;    // 用于计算串口接收完一帧数据int rxLen = 0;  // 串口接收到的数据长度byte[] rxBuff = new byte[128];  // 串口数据接收缓存private static Timer mTimer;    // 定时器,10ms执行一次SerialPort mSerialPort = new SerialPort(); private StringBuilder lineBuilder = new StringBuilder();    // 用于存放串口接收数据private List<string> baudrateList = new List<string> { "4800", "9600", "19200","38400","57600","115200","256000","1000000","2000000","3000000"};public MainWindow(){InitializeComponent();// 获取所有可用串口端口,并添加到comboBoxCOMstring[] ports = System.IO.Ports.SerialPort.GetPortNames();comboBoxCOM.ItemsSource = ports;comboBoxCOM.SelectedIndex = 0;  // 默认选择索引comboBoxBaudRate.ItemsSource = baudrateList;    // 波特率设置combobox数据源mTimer = new Timer(recvTimerCalback, null, 0, 10);  // 创建并启动定时器}/// <summary>/// 窗口关闭处理/// </summary>/// <param name="e"></param>protected override void OnClosing(System.ComponentModel.CancelEventArgs e){            if (MessageBox.Show("确定要退出吗?", "确认", MessageBoxButton.YesNo) != MessageBoxResult.Yes){// 用户选择“否”,取消关闭e.Cancel = true;}mSerialPort.Close();mTimer.Dispose();base.OnClosing(e);}private string RecordInfoString(byte[] buf){if (buf.Length < 11) {return "";}int index = buf[4];int year = buf[5] << 8 | buf[6];int month = buf[7];int day = buf[8];int hour = buf[9];int minute = buf[10];return  year.ToString() + "/" + month.ToString() + "/" + day.ToString() + "   " + hour.ToString() + ":" + minute.ToString();            }/// <summary>/// 串口接收处理定时器回调/// </summary>/// <param name="obj"></param>private void recvTimerCalback(object obj){//Console.WriteLine("timer callback!" + recvTimems);this.Dispatcher.Invoke(new Action(()=> {// UI线程分离,更新UI数据显示if (((Environment.TickCount - recvTimems) >= 20) && (rxLen > 0)){// 串口接收时间超过X ms认为一帧数据接收完成uint HEAD = rxBuff[0];uint C1 = rxBuff[1];uint C2 = rxBuff[2];uint L = rxBuff[3];uint CS = rxBuff[L + 4];uint END = rxBuff[L + 5];// 打印串口结束缓存数据uint _CS = 0;   // 计算接收数据校验和uint index = 0;UInt32 temp = 0;for (int i = 0; i < rxLen; i++){if (i < (rxLen - 2)){temp += rxBuff[i];}Console.WriteLine(rxBuff[i].ToString("X2"));}_CS = (uint)(temp % 0xFF);if ((0xAA == rxBuff[0]) && (0x55 == END)){// TODO: 接收到一帧完整数据Console.WriteLine("RS232 RECV ONE FRAME!" + CS.ToString("X2") + ", " + _CS.ToString("X2"));if (CS == _CS){// TODO: CHECKSUM8 校验和正确Console.WriteLine("CheckSum OK");if (0x00 == C2){if (0x00 == C1){// TODO: 查询各类记录总数warningCnt = rxBuff[4]; // 探测器报警记录总数warningRecoveryCnt = rxBuff[5]; // 探测器报警恢复记录总数faultCnt = rxBuff[6];   // 探测器故障记录总数faultRecoveryCnt = rxBuff[7]; // 探测器故障恢复记录总数poweroffCnt = rxBuff[8];    // 探测器掉电记录总数poweronCnt = rxBuff[9];     // 探测器上电记录总数sensorInvalidCnt = rxBuff[10];  // 传感器失效记录总数txtBoxWarningCount.Text = warningCnt.ToString();txtBoxWarningRecoveryCount.Text = warningRecoveryCnt.ToString();txtBoxFaultCount.Text = faultCnt.ToString();txtBoxFaultRecoryCount.Text = faultRecoveryCnt.ToString();txtBoxPoweroffCount.Text = poweroffCnt.ToString();  txtBoxPoweronCount.Text = poweronCnt.ToString();}}else if (0x01 == C2){// 报警记录txtBoxRecordInfo.Text = RecordInfoString(rxBuff);}else if (0x02 == C2){// 报警恢复记录txtBoxRecordInfo.Text = RecordInfoString(rxBuff);}else if (0x03 == C2){// 故障记录txtBoxRecordInfo.Text = RecordInfoString(rxBuff);}else if (0x04 == C2){// 故障恢复记录txtBoxRecordInfo.Text = RecordInfoString(rxBuff);}else if (0x05 == C2){// 掉电记录txtBoxRecordInfo.Text = RecordInfoString(rxBuff);}else if (0x06 == C2){// 上电记录txtBoxRecordInfo.Text = RecordInfoString(rxBuff);}else if (0x07 == C2){// 传感器失效记录txtBoxRecordInfo.Text = RecordInfoString(rxBuff);}else if (0x08 == C2){// TODO: 日期时间int year = rxBuff[4] << 8 | rxBuff[5];int month = rxBuff[6];int day = rxBuff[7];int hour = rxBuff[8];int minute = rxBuff[9];txtBoxDateTime.Text = year.ToString() + "/" + month.ToString() + "/" + day.ToString() + "   "+hour.ToString() + ":"+minute.ToString();                                }}else{// TODO: CHECKSUM8 校验和有误Console.WriteLine("CheckSum ERR");}}Array.Clear(rxBuff, 0, rxBuff.Length);rxLen = 0;}}));                        }/// <summary>/// 串口数据接收函数/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e){SerialPort comPort = (SerialPort)sender;try{recvTimems = Environment.TickCount; // 更新串口数据接收时间//rxBuff[rxLen] = mSerialPort.ReadByte();int readCnt = mSerialPort.BytesToRead;Console.WriteLine("in readCnt:" + readCnt);mSerialPort.Read(rxBuff, rxLen, readCnt);rxLen += readCnt;Console.WriteLine("out rxLen:" + rxLen);#if falsebyte[] data = new byte[mSerialPort.BytesToRead];mSerialPort.Read(data, 0, data.Length);Console.WriteLine(data.Length);foreach (byte b in data){ Console.WriteLine(b.ToString("X2"));                    }
#endif}catch (Exception ce){MessageBox.Show(ce.Message);}            }private void btnOpenCloseCom_Click(object sender, RoutedEventArgs e){if (mSerialPort.IsOpen){mSerialPort.Close();btnOpenCloseCom.Content = "打开串口";Console.WriteLine("关闭串口成功");Debug.WriteLine("关闭串口成功");comboBoxBaudRate.IsEnabled = true;comboBoxCOM.IsEnabled = true;comboBoxDataBit.IsEnabled = true;comboBoxStopBit.IsEnabled = true;comboBoxSdd.IsEnabled = true;comboBoxlik.IsEnabled = true;}else{mSerialPort.PortName = comboBoxCOM.SelectedItem.ToString();mSerialPort.BaudRate = 4800; // 波特率mSerialPort.DataBits = 8;   // 数据位mSerialPort.StopBits = StopBits.One;    // 停止位mSerialPort.Parity = Parity.None;   // 校验位mSerialPort.Handshake = Handshake.None;//mSerialPort.ReadTimeout = 1500; // 读超时//mSerialPort.Encoding = Encoding.UTF8; // 编码方式//mSerialPort.RtsEnable = true;mSerialPort.DataReceived += SerialPort_DataReceived;Console.WriteLine("baudrate SelectedIndex:" + comboBoxBaudRate.SelectedIndex);Console.WriteLine("baudrate SelectedValue:" + comboBoxBaudRate.SelectedValue);Console.WriteLine("baudrate Text:" + comboBoxBaudRate.Text);if (comboBoxBaudRate.SelectedIndex < 0){mSerialPort.BaudRate = Convert.ToInt32(comboBoxBaudRate.Text);}else {switch (comboBoxBaudRate.SelectedValue){case "4800":mSerialPort.BaudRate = 4800;break;case "9600":mSerialPort.BaudRate = 9600;break;case "19200":mSerialPort.BaudRate = 19200;break;case "38400":mSerialPort.BaudRate = 38400;break;case "57600":mSerialPort.BaudRate = 57600;break;case "115200":mSerialPort.BaudRate = 115200;break;case "256000":mSerialPort.BaudRate = 256000;break;case "1000000":mSerialPort.BaudRate = 1000000;break;case "2000000":mSerialPort.BaudRate = 2000000;break;case "3000000":mSerialPort.BaudRate = 3000000;break;                       default:MessageBox.Show("波特率设置有误!");break;}}Console.WriteLine("端口:" + mSerialPort.PortName  + ",波特率:" + mSerialPort.BaudRate);// mSerialPort.Write("Hello world"); // 写字符串口// mSerialPort.Write(new byte[] { 0xA0, 0xB0, 0xC0}, 0, 3); // 写入3个字节数据//Debug.WriteLine("Hello world");//MessageBox.Show("端口名:" + mSerialPort.PortName);try{mSerialPort.Open();btnOpenCloseCom.Content = "关闭串口";Console.WriteLine("打开串口成功");Debug.WriteLine("打开串口成功");comboBoxBaudRate.IsEnabled = false;comboBoxCOM.IsEnabled = false;comboBoxDataBit.IsEnabled = false;comboBoxStopBit.IsEnabled = false;comboBoxSdd.IsEnabled = false;comboBoxlik.IsEnabled = false;}catch (Exception ex){MessageBox.Show(ex.Message);}}}private void btnClearRecv_Click(object sender, RoutedEventArgs e){lineBuilder.Clear();textBlockRecv.Text = lineBuilder.ToString();}private void comboBoxCOM_Drop(object sender, DragEventArgs e){string[] ports = System.IO.Ports.SerialPort.GetPortNames();comboBoxCOM.ItemsSource = ports;comboBoxCOM.SelectedIndex = 0;}private void comboBoxBaudRate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e){ComboBoxItem obj = (ComboBoxItem)sender;}private void btnReadRecCount_Click(object sender, RoutedEventArgs e){if (mSerialPort.IsOpen){mSerialPort.Write(new byte[] { 0xAA, 0x00, 0x00, 0x00, 0xAA, 0x55 }, 0, 6); // 写入3个字节数据         }else{MessageBox.Show("请先打开串口!");}}private void btnReadDateTime_Click(object sender, RoutedEventArgs e){if (mSerialPort.IsOpen){mSerialPort.Write(new byte[] { 0xAA, 0x00, 0x08, 0x00, 0xB2, 0x55 }, 0, 6);}else{MessageBox.Show("请先打开串口!");}}private void btnReadRecord_Click(object sender, RoutedEventArgs e){            int index = Convert.ToInt32(txtBoxNumber.Text);if (index < 1 || index > 255){MessageBox.Show("记录索引:1~255!");}else {byte C1 = (byte)Convert.ToInt32(txtBoxNumber.Text);byte C2 = (byte)(cmboxRecordType.SelectedIndex);byte CS = (byte)((0xAA + C1 + C2)%0xFF);if (mSerialPort.IsOpen){byte[] Data = new byte[16];Data[0] = 0xAA;Data[1] = C1;   // 指定记录条数Data[2] = (byte)(C2 + 1);   // C2 记录分类从1开始Data[3] = 0x00;Data[4] = CS;Data[5] = 0x55;mSerialPort.Write(Data, 0, 6);}else{MessageBox.Show("请先打开串口!");}}}}
}

运行效果

在这里插入图片描述
在这里插入图片描述

源码下载

下载源码

这篇关于C# WPF燃气报警器记录读取串口工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1143402

相关文章

C#连接SQL server数据库命令的基本步骤

《C#连接SQLserver数据库命令的基本步骤》文章讲解了连接SQLServer数据库的步骤,包括引入命名空间、构建连接字符串、使用SqlConnection和SqlCommand执行SQL操作,... 目录建议配合使用:如何下载和安装SQL server数据库-CSDN博客1. 引入必要的命名空间2.

Java中读取YAML文件配置信息常见问题及解决方法

《Java中读取YAML文件配置信息常见问题及解决方法》:本文主要介绍Java中读取YAML文件配置信息常见问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录1 使用Spring Boot的@ConfigurationProperties2. 使用@Valu

Python办公自动化实战之打造智能邮件发送工具

《Python办公自动化实战之打造智能邮件发送工具》在数字化办公场景中,邮件自动化是提升工作效率的关键技能,本文将演示如何使用Python的smtplib和email库构建一个支持图文混排,多附件,多... 目录前言一、基础配置:搭建邮件发送框架1.1 邮箱服务准备1.2 核心库导入1.3 基础发送函数二、

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

C#读写文本文件的多种方式详解

《C#读写文本文件的多种方式详解》这篇文章主要为大家详细介绍了C#中各种常用的文件读写方式,包括文本文件,二进制文件、CSV文件、JSON文件等,有需要的小伙伴可以参考一下... 目录一、文本文件读写1. 使用 File 类的静态方法2. 使用 StreamReader 和 StreamWriter二、二进

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

C#中Guid类使用小结

《C#中Guid类使用小结》本文主要介绍了C#中Guid类用于生成和操作128位的唯一标识符,用于数据库主键及分布式系统,支持通过NewGuid、Parse等方法生成,感兴趣的可以了解一下... 目录前言一、什么是 Guid二、生成 Guid1. 使用 Guid.NewGuid() 方法2. 从字符串创建

Python使用OpenCV实现获取视频时长的小工具

《Python使用OpenCV实现获取视频时长的小工具》在处理视频数据时,获取视频的时长是一项常见且基础的需求,本文将详细介绍如何使用Python和OpenCV获取视频时长,并对每一行代码进行深入解析... 目录一、代码实现二、代码解析1. 导入 OpenCV 库2. 定义获取视频时长的函数3. 打开视频文

C# 比较两个list 之间元素差异的常用方法

《C#比较两个list之间元素差异的常用方法》:本文主要介绍C#比较两个list之间元素差异,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 使用Except方法2. 使用Except的逆操作3. 使用LINQ的Join,GroupJoin

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.