Files
UserStringProperty/UserStringProperty/UI/MainDialog.cs
230238001 c54bdebe59 Initial commit: UserStringProperty Rhino plugin
- Rhino 8 C# plugin for managing Block UserString properties
- XML template import with merge modes
- WinForms UI for property editing
- .NET Framework 4.8
2026-05-02 10:18:00 +08:00

517 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Rhino.DocObjects;
using UserStringProperty.Core;
namespace UserStringProperty.UI
{
public enum ApplyMode
{
Replace, // 替代:清空现有属性后应用新模板
Append // 附加:合并,重复 key 时询问
}
public enum ConflictResolution
{
ReplaceDuplicates, // 替代重复的 key
SkipDuplicates // 跳过重复的 key
}
public partial class MainDialog : Form
{
private InstanceObject _instanceObject;
private ComboBox _cmbCmig;
private ComboBox _cmbPropertySet;
private Panel _panelFields;
private Dictionary<string, Control> _fieldControls = new Dictionary<string, Control>();
private Button _btnSave;
private Button _btnCancel;
private Button _btnLoadXml;
private Label _lblTemplateInfo;
public MainDialog(InstanceObject instanceObject)
{
_instanceObject = instanceObject;
InitializeComponent();
}
private void InitializeComponent()
{
this.Text = "构件属性编辑";
this.Size = new Size(550, 580);
this.StartPosition = FormStartPosition.CenterParent;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
// Cmig 选择
var lblCmig = new Label
{
Text = "模板类型:",
Location = new Point(10, 10),
Size = new Size(80, 20)
};
_cmbCmig = new ComboBox
{
Location = new Point(95, 8),
Size = new Size(180, 20),
DropDownStyle = ComboBoxStyle.DropDownList
};
_cmbCmig.SelectedIndexChanged += CmbCmig_SelectedIndexChanged;
this.Controls.Add(lblCmig);
this.Controls.Add(_cmbCmig);
// PropertySet 选择
var lblPropertySet = new Label
{
Text = "属性模板:",
Location = new Point(285, 10),
Size = new Size(80, 20)
};
_cmbPropertySet = new ComboBox
{
Location = new Point(370, 8),
Size = new Size(160, 20),
DropDownStyle = ComboBoxStyle.DropDownList
};
_cmbPropertySet.SelectedIndexChanged += CmbPropertySet_SelectedIndexChanged;
this.Controls.Add(lblPropertySet);
this.Controls.Add(_cmbPropertySet);
_btnLoadXml = new Button
{
Text = "加载XML",
Location = new Point(10, 38),
Size = new Size(90, 25)
};
_btnLoadXml.Click += BtnLoadXml_Click;
var btnResetTemplate = new Button
{
Text = "重置模板",
Location = new Point(105, 38),
Size = new Size(90, 25)
};
btnResetTemplate.Click += (s, e) =>
{
TemplateManager.Instance.Clear();
RefreshCmigCombo();
UpdateTemplateInfo();
};
_lblTemplateInfo = new Label
{
Text = "",
Location = new Point(200, 41),
Size = new Size(330, 15),
ForeColor = Color.Gray,
Font = new System.Drawing.Font(Font.FontFamily, 8)
};
var lblFields = new Label
{
Text = "字段:",
Location = new Point(10, 75),
Size = new Size(60, 20)
};
_panelFields = new Panel
{
Location = new Point(10, 95),
Size = new Size(510, 370),
AutoScroll = true,
BorderStyle = BorderStyle.FixedSingle
};
_btnSave = new Button { Text = "写入属性", Location = new Point(370, 480), Size = new Size(90, 30) };
_btnSave.Click += BtnSave_Click;
_btnCancel = new Button { Text = "取消", Location = new Point(270, 480), Size = new Size(90, 30) };
_btnCancel.Click += (s, e) => this.Close();
this.Controls.AddRange(new Control[] {
_btnLoadXml, btnResetTemplate, _lblTemplateInfo,
lblCmig, _cmbCmig, lblPropertySet, _cmbPropertySet,
lblFields, _panelFields, _btnSave, _btnCancel
});
RefreshCmigCombo();
UpdateTemplateInfo();
}
private void UpdateTemplateInfo()
{
var tm = TemplateManager.Instance;
if (!tm.IsLoaded)
{
_lblTemplateInfo.Text = "未加载模板";
}
else
{
var paths = tm.LoadedPaths;
if (paths.Count == 1)
_lblTemplateInfo.Text = $"已加载: {System.IO.Path.GetFileName(paths[0])}";
else
_lblTemplateInfo.Text = $"已加载 {paths.Count} 个模板文件";
}
}
private void RefreshCmigCombo()
{
_cmbCmig.Items.Clear();
_cmbPropertySet.Items.Clear();
var cmigNames = TemplateManager.Instance.GetCmigNames();
if (cmigNames.Count == 0)
{
_cmbCmig.Items.Add("请先加载XML模板");
_cmbCmig.SelectedIndex = 0;
_cmbCmig.Enabled = false;
_cmbPropertySet.Enabled = false;
}
else
{
_cmbCmig.Items.AddRange(cmigNames.ToArray());
_cmbCmig.Enabled = true;
if (_cmbCmig.Items.Count > 0)
_cmbCmig.SelectedIndex = 0;
}
}
private void RefreshPropertySetCombo(string cmigName)
{
_cmbPropertySet.Items.Clear();
var psNames = TemplateManager.Instance.GetPropertySetNames(cmigName);
if (psNames.Count == 0)
{
_cmbPropertySet.Items.Add("(无属性集)");
_cmbPropertySet.SelectedIndex = 0;
_cmbPropertySet.Enabled = false;
}
else
{
_cmbPropertySet.Items.AddRange(psNames.ToArray());
_cmbPropertySet.Enabled = true;
if (_cmbPropertySet.Items.Count > 0)
_cmbPropertySet.SelectedIndex = 0;
}
}
private void CmbCmig_SelectedIndexChanged(object sender, EventArgs e)
{
var cmigName = _cmbCmig.SelectedItem?.ToString();
if (!string.IsNullOrEmpty(cmigName))
{
RefreshPropertySetCombo(cmigName);
}
}
private void CmbPropertySet_SelectedIndexChanged(object sender, EventArgs e)
{
var cmigName = _cmbCmig.SelectedItem?.ToString();
var psName = _cmbPropertySet.SelectedItem?.ToString();
if (string.IsNullOrEmpty(cmigName) || string.IsNullOrEmpty(psName))
return;
// 直接加载字段显示,让用户先看到/编辑现有内容
// 替代/附加逻辑在保存时才处理
BuildFieldsForPropertySet(cmigName, psName);
}
private ApplyMode AskApplyMode()
{
var result = MessageBox.Show(
"Block 已有属性。\n\n选择「是」替代现有属性清空后应用新模板\n选择「否」附加到现有属性合并",
"应用模式",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question);
if (result == DialogResult.Yes)
return ApplyMode.Replace;
else if (result == DialogResult.No)
return ApplyMode.Append;
else
return ApplyMode.Replace; // 默认替代
}
private ConflictResolution AskConflictResolution(List<string> duplicateKeys)
{
var suffix = duplicateKeys.Count > 5 ? "..." : "";
var result = MessageBox.Show(
$"发现 {duplicateKeys.Count} 个重复的属性 key\n{string.Join(", ", duplicateKeys.Take(5))}{suffix}\n\n选择「是」替代这些 key 的值\n选择「否」跳过这些 key",
"属性冲突",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
return ConflictResolution.ReplaceDuplicates;
else
return ConflictResolution.SkipDuplicates;
}
private string GetExistingPropertyValue(string key)
{
var userStrings = _instanceObject.Attributes?.GetUserStrings();
if (userStrings != null && userStrings[key] != null)
{
return userStrings[key];
}
return null;
}
private void SetFieldValue(string key, string value)
{
if (_fieldControls.ContainsKey(key))
{
var ctrl = _fieldControls[key];
if (ctrl is ComboBox cmb)
{
if (cmb.Items.Contains(value))
cmb.SelectedItem = value;
}
else if (ctrl is TextBox txt)
{
txt.Text = value;
}
}
}
private List<string> GetExistingPropertyKeys()
{
var keys = new List<string>();
var userStrings = _instanceObject.Attributes?.GetUserStrings();
if (userStrings != null)
{
keys.AddRange(userStrings.AllKeys);
}
return keys;
}
private void BtnLoadXml_Click(object sender, EventArgs e)
{
using (var dlg = new OpenFileDialog())
{
dlg.Filter = "XML文件|*.xml";
dlg.Title = "选择属性配置文件";
if (dlg.ShowDialog() == DialogResult.OK)
{
var filePath = dlg.FileName;
XmlPropertyConfig incomingConfig;
try
{
incomingConfig = XmlPropertyConfig.Load(filePath);
}
catch (Exception ex)
{
MessageBox.Show($"XML 解析失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (incomingConfig.PropertySets.Count == 0)
{
MessageBox.Show("XML 中没有找到属性集", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var pluginFolder = TemplateManager.Instance.GetPluginFolderPath();
using (var importDlg = new XmlImportDialog(incomingConfig, pluginFolder))
{
if (importDlg.ShowDialog() == DialogResult.OK)
{
TemplateManager.Instance.Import(
filePath,
importDlg.SelectedCmigName,
importDlg.SelectedPropertySetName
);
RefreshCmigCombo();
UpdateTemplateInfo();
}
}
}
}
}
private void BuildFieldsForPropertySet(string cmigName, string psName)
{
_panelFields.Controls.Clear();
_fieldControls.Clear();
var ps = TemplateManager.Instance.GetPropertySet(cmigName, psName);
if (ps == null) return;
int y = 10;
foreach (var prop in ps.Properties)
{
if (prop.IsSeparator)
{
var sep = new Label
{
Text = prop.Name,
Location = new Point(10, y),
Size = new Size(480, 20),
Font = new System.Drawing.Font(Font.FontFamily, 9, FontStyle.Bold),
ForeColor = Color.DarkBlue,
BackColor = Color.AliceBlue
};
_panelFields.Controls.Add(sep);
y += 25;
continue;
}
// 只使用模板的默认值,不读取 block 现有属性
AddFieldControl(prop, y, prop.DefaultValue ?? "");
y += 28;
}
}
private void AddFieldControl(PropertyDefinition prop, int y, string value)
{
var lbl = new Label
{
Text = prop.Name,
Location = new Point(10, y),
Size = new Size(200, 20)
};
_panelFields.Controls.Add(lbl);
Control ctrl;
if (prop.Type == "list" && prop.Options.Count > 0)
{
var cmb = new ComboBox
{
Location = new Point(220, y),
Size = new Size(260, 20),
DropDownStyle = ComboBoxStyle.DropDownList
};
cmb.Items.AddRange(prop.Options.ToArray());
cmb.SelectedItem = prop.Options.Contains(value) ? value : prop.DefaultValue;
ctrl = cmb;
}
else
{
var txt = new TextBox
{
Location = new Point(220, y),
Size = new Size(260, 20),
Text = value
};
ctrl = txt;
}
_panelFields.Controls.Add(ctrl);
_fieldControls[prop.Name] = ctrl;
}
private void BtnSave_Click(object sender, EventArgs e)
{
var existingKeys = GetExistingPropertyKeys();
if (existingKeys.Count > 0)
{
// block 已有属性,询问 Replace 或 Append
var result = MessageBox.Show(
"Block 已有属性。\n\n选择「是」替代现有属性清空后应用新模板\n选择「否」附加到现有属性合并",
"应用模式",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question);
if (result == DialogResult.Cancel)
return;
if (result == DialogResult.Yes)
{
// 替代模式:清空所有现有属性
ClearAllProperties();
}
else
{
// 附加模式:检查重复 key
var cmigName = _cmbCmig.SelectedItem?.ToString();
var psName = _cmbPropertySet.SelectedItem?.ToString();
if (!string.IsNullOrEmpty(cmigName) && !string.IsNullOrEmpty(psName))
{
var ps = TemplateManager.Instance.GetPropertySet(cmigName, psName);
if (ps != null)
{
var duplicateKeys = ps.Properties
.Where(p => existingKeys.Contains(p.Name) && !p.IsSeparator)
.Select(p => p.Name)
.ToList();
if (duplicateKeys.Count > 0)
{
var suffix = duplicateKeys.Count > 5 ? "..." : "";
var res = MessageBox.Show(
$"发现 {duplicateKeys.Count} 个重复的属性 key\n{string.Join(", ", duplicateKeys.Take(5))}{suffix}\n\n选择「是」替代这些 key 的值\n选择「否」跳过这些 key",
"属性冲突",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Warning);
if (res == DialogResult.Cancel)
return;
if (res == DialogResult.Yes)
{
// 替代重复的 key - 不做特殊处理,直接保存会用模板值覆盖
}
else
{
// 跳过重复的 key - 需要恢复原值后再保存
foreach (var key in duplicateKeys)
{
var existingValue = GetExistingPropertyValue(key);
if (!string.IsNullOrEmpty(existingValue))
{
SetFieldValue(key, existingValue);
}
}
}
}
}
}
}
}
// 保存字段
var attrs = _instanceObject.Attributes;
foreach (var kvp in _fieldControls)
{
string value = "";
if (kvp.Value is ComboBox cmb)
value = cmb.SelectedItem?.ToString() ?? "";
else if (kvp.Value is TextBox txt)
value = txt.Text;
attrs.SetUserString(kvp.Key, value);
}
Rhino.RhinoApp.WriteLine($"已保存 {_fieldControls.Count} 个属性到 Block");
this.Close();
}
private void ClearAllProperties()
{
var attrs = _instanceObject.Attributes;
if (attrs == null) return;
var userStrings = attrs.GetUserStrings();
if (userStrings == null) return;
// 清除所有现有属性
foreach (var key in userStrings.AllKeys)
{
attrs.SetUserString(key, null);
}
}
}
}