using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Windows.Forms; using DevExpress.XtraEditors; using DevExpress.XtraEditors.Controls; namespace KepGridEditor { public partial class FormTrend : XtraForm { public event EventHandler TrendDataChanged; private BindingList _trends = new BindingList(); private bool _isInternalUpdating = false; public List TrendParams => _trends.ToList(); public int FusionMode => cbMode.SelectedIndex; private int _nameCounter = 1; // 初始为1 public FormTrend() { InitializeComponent(); SetupUIConfig(); InitData(); BindEvents(); gaugeKnob.Paint += GaugeKnob_Paint; gaugeKnob.MouseDown += GaugeKnob_MouseDown; gaugeKnob.MouseMove += GaugeKnob_MouseMove; // 鼠标松开时,通知主界面最终结果 gaugeKnob.MouseUp += (s, e) => NotifyDataChanged(TrendOperateType.Update); } protected override void OnShown(EventArgs e) { base.OnShown(e); // 窗体完全显示后,手动触发一次 Update 通知 if (_trends.Count > 0) { NotifyDataChanged(TrendOperateType.Update); } } protected override void OnFormClosing(FormClosingEventArgs e) { base.OnFormClosing(e); NotifyDataChanged(TrendOperateType.Close); } private void SetupUIConfig() { cbAlgo.Properties.TextEditStyle = TextEditStyles.DisableTextEditor; cbMode.Properties.TextEditStyle = TextEditStyles.DisableTextEditor; cbAlgo.Properties.Items.Add("最小曲率法 (Thin Plate Spline)"); cbAlgo.SelectedIndex = 0; cbMode.Properties.Items.AddRange(new[] { "加权融合", "极值融合" }); cbMode.SelectedIndex = 0; arcScale.MinValue = 0; arcScale.MaxValue = 360; arcScale.StartAngle = -90; arcScale.EndAngle = 270; arcScale.MajorTickCount = 13; arcScale.AppearanceTickmarkText.TextBrush = new DevExpress.XtraGauges.Core.Drawing.SolidBrushObject(Color.Black); } private void InitData() { listTrends.DataSource = _trends; listTrends.DisplayMember = "DisplayText"; _trends.Add(new TrendSource { Name = $"趋势 {_nameCounter++}", Angle = 45, Ratio = 2.0 }); listTrends.SelectedIndex = 0; } private void BindEvents() { btnAdd.Click += (s, e) => { var selected = listTrends.SelectedItem as TrendSource; // 使用 _nameCounter++ 确保每次生成的数字都是唯一的,即使中间有删除 var newItem = new TrendSource { Name = $"趋势 {_nameCounter++}", Angle = 0, Ratio = 1.0, Cx = selected?.Cx ?? 0, Cy = selected?.Cy ?? 0 }; _trends.Add(newItem); listTrends.SelectedIndex = _trends.Count - 1; NotifyDataChanged(TrendOperateType.Update); }; btnDel.Click += (s, e) => { if (_trends.Count > 1) { var cur = listTrends.SelectedItem as TrendSource; string nameToDelete = cur.Name; // 记录要删除的名字 _trends.RemoveAt(listTrends.SelectedIndex); gaugeKnob.Invalidate(); NotifyDataChanged(TrendOperateType.Delete, nameToDelete); // 通知项删除 } }; cbMode.SelectedIndexChanged += (s, e) => NotifyDataChanged(TrendOperateType.Update); listTrends.SelectedIndexChanged += (s, e) => SyncUIFromModel(); trackRatio.ValueChanged += (s, e) => { if (_isInternalUpdating || listTrends.SelectedItem == null) return; var cur = listTrends.SelectedItem as TrendSource; cur.Ratio = trackRatio.Value / 10.0; lblRatioNum.Text = cur.Ratio.ToString("F1"); listTrends.Refresh(); }; // 滑块松开时通知 trackRatio.MouseUp += (s, e) => NotifyDataChanged(TrendOperateType.Update); } private void GaugeKnob_Paint(object sender, PaintEventArgs e) { if (listTrends.SelectedItem is TrendSource cur) { e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; Point center = new Point(gaugeKnob.Width / 2, gaugeKnob.Height / 2); double angleRad = (cur.Angle - 90) * Math.PI / 180.0; int radius = (Math.Min(gaugeKnob.Width, gaugeKnob.Height) / 2) - 20; Point endPoint = new Point((int)(center.X + radius * Math.Cos(angleRad)), (int)(center.Y + radius * Math.Sin(angleRad))); using (Pen p = new Pen(Color.Blue, 6)) { p.EndCap = LineCap.ArrowAnchor; e.Graphics.DrawLine(p, center, endPoint); } e.Graphics.FillEllipse(Brushes.DarkGray, center.X - 8, center.Y - 8, 16, 16); } } private void GaugeKnob_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) UpdateAngle(e.Location); } private void GaugeKnob_MouseMove(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) UpdateAngle(e.Location); } private void UpdateAngle(Point pos) { if (listTrends.SelectedItem == null) return; Point center = new Point(gaugeKnob.Width / 2, gaugeKnob.Height / 2); double angleRad = Math.Atan2(pos.Y - center.Y, pos.X - center.X); double angleDeg = angleRad * (180 / Math.PI) + 90; if (angleDeg < 0) angleDeg += 360; var cur = listTrends.SelectedItem as TrendSource; cur.Angle = Math.Round(angleDeg); lblAngleValue.Text = $"{cur.Angle}°"; listTrends.Refresh(); gaugeKnob.Invalidate(); } private void SyncUIFromModel() { if (listTrends.SelectedItem is TrendSource cur) { _isInternalUpdating = true; lblAngleValue.Text = $"{cur.Angle}°"; trackRatio.Value = (int)(cur.Ratio * 10); lblRatioNum.Text = cur.Ratio.ToString("F1"); _isInternalUpdating = false; gaugeKnob.Invalidate(); } } private void NotifyDataChanged(TrendOperateType type, string deletedName = "") { if (TrendDataChanged != null) { TrendDataChanged.Invoke(this, new TrendChangedEventArgs { ActiveTrend = listTrends.SelectedItem as TrendSource, AllTrends = _trends.ToList(), Mode = cbMode.SelectedIndex, OperateType = type, // 告知主界面是更新、删除还是关闭 DeletedName = deletedName }); } } private void updatePositionBtn_CheckedChanged(object sender, EventArgs e) { if (updatePositionBtn.Checked) { NotifyDataChanged(TrendOperateType.PickLocation); } } public void ReleasePickButton() { if (updatePositionBtn.InvokeRequired) { updatePositionBtn.Invoke(new Action(() => updatePositionBtn.Checked = false)); } else { updatePositionBtn.Checked = false; } NotifyDataChanged(TrendOperateType.Update); } } public enum TrendOperateType { Update, // 更新(旋钮、滑块、添加) Delete, // 删除 Close, // 界面关闭 PickLocation // 主界面进入拾取坐标模式 } public class TrendChangedEventArgs : EventArgs { public TrendSource ActiveTrend { get; set; } // 当前被修改的项 public List AllTrends { get; set; } // 全量列表 public int Mode { get; set; } // 融合模式 public TrendOperateType OperateType { get; set; } // 操作类型 public string DeletedName { get; set; } // 被删除项的名称,方便OSG清理 } public class TrendSource { public string Name { get; set; } public double Angle { get; set; } public double Ratio { get; set; } public double Weight { get; set; } = 1.0; public int Cx { get; set; } = 0; public int Cy { get; set; } = 0; public string DisplayText => $"{Name} : {Angle,3}° | 强度: {Ratio:F1} | w: {Weight:F2}"; } }