using JinYuan.Helper; using JinYuan.Models; using Language; using MiniExcelLibs; using SqlSugar; using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Windows.Forms; namespace LargeSquareOne { public partial class VariableEditForm : MultiLanguageForm { private string variableConfigPath = Application.StartupPath + "\\Config\\Variable.xlsx"; private string groupConfigPath = Application.StartupPath + "\\Config\\Group.xlsx"; private List groupNames = new List(); private List Variables = new List(); private VariableConfig editingConfig; private int PlcNum; public VariableEditForm(int plcNum) { PlcNum = plcNum; InitializeComponent(); InitializeDataGridView(); groupNames = GetGroupNames(PlcNum); Variables = GetALLVariables(); LoadVariables(); } private void InitializeDataGridView() { variableTable.AutoGenerateColumns = false; variableTable.AllowUserToAddRows = false; variableTable.SelectionMode = DataGridViewSelectionMode.FullRowSelect; variableTable.MultiSelect = false; variableTable.ReadOnly = false; // 添加列 variableTable.Columns.AddRange(new DataGridViewColumn[] { new DataGridViewTextBoxColumn { Name = "PlcNum", HeaderText = "PLC编号", Width = 80, ReadOnly = true, DataPropertyName = "PlcNum" }, new DataGridViewComboBoxColumn { Name = "GroupName", HeaderText = "组名", Width = 100, DataPropertyName = "GroupName", DataSource = new List(groupNames), // 使用新列表避免引用问题 ValueType = typeof(string), ReadOnly = false, DefaultCellStyle = new DataGridViewCellStyle { BackColor = Color.FromArgb(14, 23, 38), // 深色背景 ForeColor = Color.White, // 白色文字 SelectionBackColor = Color.FromArgb(0, 122, 204), // 选中时的背景色 SelectionForeColor = Color.White, // 选中时的文字颜色 Font = new Font("微软雅黑", 9F) // 字体设置 } }, new DataGridViewTextBoxColumn { Name = "VarName", HeaderText = "变量名称", Width = 200, DataPropertyName = "VarName" }, new DataGridViewTextBoxColumn { Name = "VarAddress", HeaderText = "变量地址", Width = 100, DataPropertyName = "VarAddress" }, new DataGridViewTextBoxColumn { Name = "Start", HeaderText = "起始地址", Width = 100, DataPropertyName = "Start" }, new DataGridViewComboBoxColumn { Name = "DataType", HeaderText = "数据类型", Width = 150, DataPropertyName = "DataType", Items = { "Short", "ByteArray" }, DefaultCellStyle = new DataGridViewCellStyle { BackColor = Color.FromArgb(14, 23, 38), // 深色背景 ForeColor = Color.White, // 白色文字 SelectionBackColor = Color.FromArgb(0, 122, 204), // 选中时的背景色 SelectionForeColor = Color.White, // 选中时的文字颜色 Font = new Font("微软雅黑", 9F) // 字体设置 } }, new DataGridViewTextBoxColumn { Name = "OffsetOrLength", HeaderText = "长度偏移", Width = 80, DataPropertyName = "OffsetOrLength" }, new DataGridViewTextBoxColumn { Name = "Remark", HeaderText = "备注", Width = 120, DataPropertyName = "Remark", Visible = false }, new DataGridViewButtonColumn { Name = "Edit", HeaderText = "操作", Width = 60, Text = "修改".Translated(), UseColumnTextForButtonValue = true }, new DataGridViewButtonColumn { Name = "Delete", HeaderText = "", Width = 60, Text = "删除".Translated(), UseColumnTextForButtonValue = true } }); // 添加事件处理 variableTable.CellBeginEdit += DataGridView_CellBeginEdit; variableTable.CellEndEdit += DataGridView_CellEndEdit; variableTable.CellContentClick += DataGridView_CellContentClick; //variableTable.RowPostPaint += DataGridView_RowPostPaint; } // 添加此方法从 Group.xlsx 获取组名称 private List GetGroupNames(int plcNum) // 添加 plcNum 参数 { try { var groups = MiniExcel.Query(groupConfigPath).ToList(); // 根据 PLC 编号筛选,然后获取组名 return groups.Where(g => g.PlcNum == plcNum) .Select(g => g.GroupName) .Distinct() .ToList(); } catch (Exception) { return new List { "默认组".Translated() }; // 发生错误时返回默认组 } } /// /// 获取所有通信组 /// /// private List GetALLVariables() { try { var variables = MiniExcel.Query(variableConfigPath).ToList(); // 确保所有变量的组名都在groupNames列表中 foreach (var variable in variables) { if (!groupNames.Contains(variable.GroupName)) { variable.GroupName = groupNames[0]; // 如果组名无效,使用第一个有效的组名 } } return variables; } catch (Exception) { return new List(); } } private void LoadVariables() { try { var plcGroups = Variables.Where(g => g.PlcNum == PlcNum).ToList(); // 在设置DataSource之前清除原有的绑定 variableTable.DataSource = null; // 确保ComboBox列已经设置好数据源 if (variableTable.Columns["GroupName"] is DataGridViewComboBoxColumn groupColumn) { groupColumn.DataSource = new List(groupNames); } // 设置新的数据源 variableTable.DataSource = plcGroups; // 刷新表格 variableTable.Refresh(); } catch (Exception ex) { ShowError($"加载变量失败: {ex.Message}"); } } private void DataGridView_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e) { if (e.RowIndex < 0) return; editingConfig = Variables[e.RowIndex]; // 禁止编辑PLC编号和组名列 if (variableTable.Columns[e.ColumnIndex].Name == "PlcNum") { e.Cancel = true; } //if (variableTable.Columns[e.ColumnIndex].Name == "PlcNum" || // variableTable.Columns[e.ColumnIndex].Name == "GroupName") //{ // e.Cancel = true; //} } private void DataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex < 0 || e.RowIndex >= Variables.Count) return; var config = Variables[e.RowIndex]; var value = variableTable[e.ColumnIndex, e.RowIndex].Value; try { UpdateConfigValue(config, variableTable.Columns[e.ColumnIndex].Name, value); } catch (Exception ex) { ShowError($"更新数据失败: {ex.Message}"); } } private void UpdateConfigValue(VariableConfig config, string columnName, object value) { switch (columnName) { case "GroupName": if (value != null && groupNames.Contains(value.ToString())) { config.GroupName = value.ToString(); } else { throw new Exception("无效的组名".Translated()); } break; case "VarName": if (string.IsNullOrWhiteSpace(value?.ToString())) { throw new Exception("变量名不能为空".Translated()); } if (Variables.Any(v => v != config && v.PlcNum == config.PlcNum && v.VarName.Equals(value.ToString(), StringComparison.OrdinalIgnoreCase))) { throw new Exception("变量名已存在".Translated()); } config.VarName = value.ToString(); break; case "VarAddress": if (!ValidateVariableAddress(value?.ToString())) { throw new Exception("地址格式不正确,应以'D'开头后跟数字".Translated()); } if (columnName == "VarAddress") { config.VarAddress = value.ToString(); } break; case "Start": if (!ValidateStartAddress(value?.ToString())) { throw new Exception("地址格式不正确,应是纯数字".Translated()); } if (columnName == "Start") { config.Start = value.ToString(); } break; case "DataType": if (!ValidateDataType(value?.ToString())) { throw new Exception("不支持的数据类型".Translated()); } config.DataType = value.ToString(); break; case "OffsetOrLength": if (!int.TryParse(value?.ToString(), out int length) || length <= 0) { throw new Exception("长度必须是大于0的数字".Translated()); } config.OffsetOrLength = length; break; case "Remark": config.Remark = value?.ToString() ?? string.Empty; break; } } private void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex < 0) return; // 获取当前行显示的数据 var currentRow = variableTable.Rows[e.RowIndex]; var config = (VariableConfig)currentRow.DataBoundItem; // 找到在 TotalGroups 中的真实索引 var realIndex = Variables.FindIndex(g => g.PlcNum == config.PlcNum && g.GroupName == config.GroupName && g.Start == config.Start); // 编辑按钮 if (variableTable.Columns[e.ColumnIndex] is DataGridViewButtonColumn && variableTable.Columns[e.ColumnIndex].Name == "Edit") { // 传递真实的索引,而不是显示的行索引 HandleEdit(config, realIndex); } // 删除按钮 else if (variableTable.Columns[e.ColumnIndex] is DataGridViewButtonColumn && variableTable.Columns[e.ColumnIndex].Name == "Delete") { HandleDelete(config); } } private void HandleEdit(VariableConfig config, int rowIndex) { try { var result = MessageBox.Show("是否保存修改的配置?".Translated(), "确认保存".Translated(), MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result != DialogResult.Yes) return; if (!ValidateConfigAll(config)) return; SaveConfigToExcel(); LoadVariables(); ShowSuccess("配置保存成功".Translated()); } catch (Exception ex) { ShowError($"保存失败: {ex.Message}"); } } private void HandleDelete(VariableConfig config) { var result = MessageBox.Show($"确定要删除该变量吗?\n PLC编号: [{config.PlcNum}],通信组: [{config.GroupName}],变量名: [{config.VarName}]", "确认删除", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result == DialogResult.Yes) { try { Variables.Remove(config); SaveConfigToExcel(); LoadVariables(); ShowSuccess("删除成功".Translated()); } catch (Exception ex) { ShowError($"删除失败: {ex.Message}"); } } } private void SaveConfigToExcel() { try { string tempFilePath = Path.Combine( Path.GetDirectoryName(variableConfigPath), $"temp_{Path.GetFileName(variableConfigPath)}"); // 先保存到临时文件 MiniExcel.SaveAs(tempFilePath, Variables); if (!File.Exists(tempFilePath)) { throw new Exception("创建临时文件失败".Translated()); } try { if (File.Exists(variableConfigPath)) { File.Delete(variableConfigPath); } File.Move(tempFilePath, variableConfigPath); } finally { if (File.Exists(tempFilePath)) { File.Delete(tempFilePath); } } } catch (Exception ex) { throw new Exception($"保存Excel文件时发生错误: {ex.Message}", ex); } } private void BtnAdd_Click(object sender, EventArgs e) { try { // 确保有可用的组名 if (groupNames == null || groupNames.Count == 0) { ShowError("没有可用的组名,请先在Group.xlsx中添加组".Translated()); return; } string VarName = GenerateNewVarName(); string VarAddress = FindNextAvailableAddress(); var newVariable = new VariableConfig { VarName = VarName, VarAddress = VarAddress, Start = VarAddress.Substring(1, VarAddress.Length-1), DataType = "Short", OffsetOrLength = 1, PlcNum = PlcNum, GroupName = groupNames[0], // 使用第一个可用的组名 Remark = "" }; if (!ValidateConfigAll(newVariable)) { return; } Variables.Add(newVariable); SaveConfigToExcel(); LoadVariables(); ShowSuccess("新建变量成功".Translated()); } catch (Exception ex) { ShowError($"添加变量失败: {ex.Message}"); } } private string GenerateNewVarName() { int index = 1; string baseName = "新变量".Translated(); string newName = baseName; while (Variables.Any(v => v.PlcNum == PlcNum && v.VarName.Equals(newName, StringComparison.OrdinalIgnoreCase))) { index++; newName = $"{baseName}{index}"; } return newName; } /// /// 获取地址 /// /// /// private string FindNextAvailableAddress() { var plcVariables = Variables.Where(v => v.PlcNum == PlcNum).ToList(); if (!plcVariables.Any()) { return "D0"; } int maxAddress = plcVariables.Max(v => { int startAddr = int.Parse(v.VarAddress.Substring(1)); return startAddr + v.OffsetOrLength; }); if (maxAddress >= 32675) { throw new Exception("没有可用的地址空间".Translated()); } return $"D{maxAddress}"; } /// /// 验证变量配置 /// private bool ValidateConfigAll(VariableConfig config) { try { // 基础验证 if (string.IsNullOrWhiteSpace(config.VarName)) { ShowError("变量名不能为空".Translated()); return false; } if (!ValidateVariableAddress(config.VarAddress)) { ShowError("变量地址格式不正确".Translated()); return false; } if (!ValidateStartAddress(config.Start)) { ShowError("起始地址格式不正确".Translated()); return false; } if (!ValidateDataType(config.DataType)) { ShowError("不支持的数据类型".Translated()); return false; } if (config.OffsetOrLength <= 0) { ShowError("长度必须大于0".Translated()); return false; } // 如果Variables为null,先初始化 if (Variables == null) { Variables = new List(); return true; } // 获取同一PLC的其他变量 var otherVariables = Variables.Where(v => v != config && v.PlcNum == config.PlcNum).ToList(); // 如果没有其他变量,则直接返回true if (!otherVariables.Any()) { return true; } // 检查重复 if (otherVariables.Any(v => v.VarName.Equals(config.VarName, StringComparison.OrdinalIgnoreCase))) { ShowError($"变量名 {config.VarName} 已存在"); return false; } if (otherVariables.Any(v => v.VarAddress.Equals(config.VarAddress, StringComparison.OrdinalIgnoreCase))) { ShowError($"变量地址 {config.VarAddress} 已存在"); return false; } // 地址范围验证 int startAddress; if (!int.TryParse(config.Start, out startAddress)) { ShowError("无效的起始地址".Translated()); return false; } int endAddress = startAddress + config.OffsetOrLength - 1; if (endAddress > 32767) { ShowError($"地址范围超出限制,结束地址不能超过32767".Translated()); return false; } // 检查地址范围冲突 foreach (var other in otherVariables) { int otherStart; if (!int.TryParse(other.Start, out otherStart)) { continue; // 跳过无效的地址 } int otherEnd = otherStart + other.OffsetOrLength - 1; if (!(endAddress < otherStart || startAddress > otherEnd)) { ShowError($"地址范围与变量 \"{other.VarName}\" 冲突"); return false; } } return true; } catch (Exception ex) { LogHelper.Instance.WriteError($"验证变量配置时发生错误: {ex}"); // 假设有日志记录器 ShowError($"验证配置时发生错误,请检查输入是否正确".Translated()); return false; } } /// /// 增强版的地址验证(支持多种地址格式) /// /// 要验证的地址字符串 /// 是否是有效的地址 private bool ValidateVariableAddress(string address) { try { // 检查地址是否为空 if (string.IsNullOrWhiteSpace(address)) { return false; } // 使用正则表达式匹配地址格式 // 支持的格式: // D0-D32767 // M0-M8191 // DB1.DBX0.0 // 等其他格式 var pattern = @"^([DMdm]\d+|DB\d+\.DBX\d+\.\d+)$"; if (!System.Text.RegularExpressions.Regex.IsMatch(address, pattern)) { return false; } // 根据不同地址类型进行验证 if (address.StartsWith("D", StringComparison.OrdinalIgnoreCase)) { // D区地址验证 string numberPart = address.Substring(1); if (!int.TryParse(numberPart, out int dAddress) || dAddress < 0 || dAddress > 32767) { return false; } } else if (address.StartsWith("M", StringComparison.OrdinalIgnoreCase)) { // M区地址验证 string numberPart = address.Substring(1); if (!int.TryParse(numberPart, out int mAddress) || mAddress < 0 || mAddress > 8191) { return false; } } else if (address.StartsWith("DB", StringComparison.OrdinalIgnoreCase)) { // DB块地址验证 var parts = address.Split('.'); if (parts.Length != 3) { return false; } // 验证DB块号 string dbNumberPart = parts[0].Substring(2); if (!int.TryParse(dbNumberPart, out int dbNumber) || dbNumber < 1 || dbNumber > 65535) { return false; } // 验证偏移地址 string offsetPart = parts[1].Substring(3); if (!int.TryParse(offsetPart, out int offset) || offset < 0 || offset > 65535) { return false; } // 验证位地址 string bitPart = parts[2]; if (!int.TryParse(bitPart, out int bit) || bit < 0 || bit > 7) { return false; } } return true; } catch { return false; } } /// /// 验证起始地址(纯数字格式) /// /// 要验证的起始地址字符串 /// 是否是有效的起始地址 private bool ValidateStartAddress(string startAddress) { try { // 检查地址是否为空 if (string.IsNullOrWhiteSpace(startAddress)) { return false; } // 验证是否为纯数字 if (!int.TryParse(startAddress, out int addressNum)) { return false; } // 检查地址范围(根据实际需求调整范围) if (addressNum < 0 || addressNum > 32767) { return false; } return true; } catch { return false; } } /// /// 验证数据类型 /// private bool ValidateDataType(string dataType) { if (string.IsNullOrWhiteSpace(dataType)) return false; // 根据实际支持的数据类型列表进行验证 string[] validTypes = new[] { "Bit", "Byte", "ByteArray", "Short", "Arrshort", "Word", "DWord", "Int", "DInt", "Real", "String" }; return validTypes.Contains(dataType, StringComparer.OrdinalIgnoreCase); } private string FindAddressGap(int requiredLength = 1) { // 获取当前PLC的所有变量,并按起始地址排序 var plcVariables = Variables .Where(v => v.PlcNum == PlcNum) .Select(v => new { Start = int.Parse(v.VarAddress.Substring(1)), End = int.Parse(v.VarAddress.Substring(1)) + v.OffsetOrLength - 1 }) .OrderBy(v => v.Start) .ToList(); // 如果没有变量,返回起始地址 if (!plcVariables.Any()) { return "D0"; } // 检查第一个变量之前是否有足够空间 if (plcVariables.First().Start >= requiredLength) { return "D0"; } // 检查变量之间的间隔 for (int i = 0; i < plcVariables.Count - 1; i++) { int gapStart = plcVariables[i].End + 1; int gapEnd = plcVariables[i + 1].Start - 1; if (gapEnd - gapStart + 1 >= requiredLength) { return $"D{gapStart}"; } } // 检查最后一个变量之后是否有足够空间 int lastEnd = plcVariables.Last().End; if (lastEnd + requiredLength <= 32675) { return $"D{lastEnd + 1}"; } throw new Exception("没有找到足够大的地址空间".Translated()); } // 错误提示 private void ShowError(string message) { MessageBox.Show(message, "验证错误".Translated(), MessageBoxButtons.OK, MessageBoxIcon.Error); } // 成功提示 private void ShowSuccess(string message) { MessageBox.Show(message, "成功".Translated(), MessageBoxButtons.OK, MessageBoxIcon.Information); } private void btn_Close_Click(object sender, EventArgs e) { this.Close(); } #region 减少闪烁 protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.ExStyle |= 0x02000000; return cp; } } #endregion #region 无边框拖动 private Point mPoint; private void Panel_MouseDown(object sender, MouseEventArgs e) { mPoint = new Point(e.X, e.Y); } private void Panel_MouseMove(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { this.Location = new Point(this.Location.X + e.X - mPoint.X, this.Location.Y + e.Y - mPoint.Y); } } #endregion } }