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

相关文章

MySql match against工具详细用法

《MySqlmatchagainst工具详细用法》在MySQL中,MATCH……AGAINST是全文索引(Full-Textindex)的查询语法,它允许你对文本进行高效的全文搜素,支持自然语言搜... 目录一、全文索引的基本概念二、创建全文索引三、自然语言搜索四、布尔搜索五、相关性排序六、全文索引的限制七

Java使用SLF4J记录不同级别日志的示例详解

《Java使用SLF4J记录不同级别日志的示例详解》SLF4J是一个简单的日志门面,它允许在运行时选择不同的日志实现,这篇文章主要为大家详细介绍了如何使用SLF4J记录不同级别日志,感兴趣的可以了解下... 目录一、SLF4J简介二、添加依赖三、配置Logback四、记录不同级别的日志五、总结一、SLF4J

基于Python实现读取嵌套压缩包下文件的方法

《基于Python实现读取嵌套压缩包下文件的方法》工作中遇到的问题,需要用Python实现嵌套压缩包下文件读取,本文给大家介绍了详细的解决方法,并有相关的代码示例供大家参考,需要的朋友可以参考下... 目录思路完整代码代码优化思路打开外层zip压缩包并遍历文件:使用with zipfile.ZipFil

C#实现将Excel表格转换为图片(JPG/ PNG)

《C#实现将Excel表格转换为图片(JPG/PNG)》Excel表格可能会因为不同设备或字体缺失等问题,导致格式错乱或数据显示异常,转换为图片后,能确保数据的排版等保持一致,下面我们看看如何使用C... 目录通过C# 转换Excel工作表到图片通过C# 转换指定单元格区域到图片知识扩展C# 将 Excel

基于Java实现回调监听工具类

《基于Java实现回调监听工具类》这篇文章主要为大家详细介绍了如何基于Java实现一个回调监听工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录监听接口类 Listenable实际用法打印结果首先,会用到 函数式接口 Consumer, 通过这个可以解耦回调方法,下面先写一个

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的

C#TextBox设置提示文本方式(SetHintText)

《C#TextBox设置提示文本方式(SetHintText)》:本文主要介绍C#TextBox设置提示文本方式(SetHintText),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录C#TextBox设置提示文本效果展示核心代码总结C#TextBox设置提示文本效果展示核心代