- Rhino 8 C# plugin for managing Block UserString properties - XML template import with merge modes - WinForms UI for property editing - .NET Framework 4.8
517 lines
18 KiB
C#
517 lines
18 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|
||
} |