using DevExpress.Utils.Drawing; using DevExpress.XtraEditors; using GeoSigmaDrawLib; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Windows.Forms; namespace GeoSigma.SigmaDrawerStyle { public class FrmGradientRulerEditor : XtraUserControl { public class MarkItem { public ColorScaleEntry RawData; public double NormalizedPosition { get; set; } internal float CurrentY { get; set; } internal RectangleF Bounds { get; set; } internal RectangleF TextBounds { get; set; } internal bool IsHovered { get; set; } internal bool IsSelected { get; set; } } private List _items = new List(); private MarkItem _draggingItem = null; private MarkItem _editingItem = null; private MarkItem _selectedItem = null; private TextBox _editTextBox; private double _globalMin, _globalMax; private bool _isReversed; // 标记是否为反转状态 private ColorBlend _fixedGradientBlend; private const int GradientWidth = 40; private const int PaddingTopBottom = 30; private Rectangle _gradRect; public FrmGradientRulerEditor() { this.DoubleBuffered = true; this.Font = new Font("Tahoma", 9F); _editTextBox = new TextBox { Visible = false, Width = 60 }; _editTextBox.Leave += (s, e) => EndEdit(true); _editTextBox.KeyDown += (s, e) => { if (e.KeyCode == Keys.Enter) EndEdit(true); if (e.KeyCode == Keys.Escape) EndEdit(false); }; this.Controls.Add(_editTextBox); } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == Keys.Delete) { DeleteSelected(); return true; } return base.ProcessCmdKey(ref msg, keyData); } // 接收是否反转的参数 public void SetData(IEnumerable bgEntries, IEnumerable markEntries, double min, double max, bool isReversed) { _globalMin = min; _globalMax = max; _isReversed = isReversed; double range = max - min; UpdateGradientBlend(bgEntries, min, max); _items.Clear(); foreach (var entry in markEntries) { double pos = (range == 0) ? 0.5 : (entry.z - min) / range; _items.Add(new MarkItem { RawData = entry, NormalizedPosition = pos }); } Invalidate(); } private void UpdateGradientBlend(IEnumerable entries, double min, double max) { var baseSorted = entries.OrderBy(e => e.z).ToList(); if (baseSorted.Count > 1) { float[] posArray = new float[baseSorted.Count]; Color[] colorArray = new Color[baseSorted.Count]; double range = max - min; for (int i = 0; i < baseSorted.Count; i++) { float pos = (range == 0) ? 0 : (float)((baseSorted[i].z - min) / range); //强制限制在 0.0 ~ 1.0 之间,防越界崩溃 posArray[i] = Math.Max(0.0f, Math.Min(1.0f, pos)); colorArray[i] = Color.FromArgb(baseSorted[i].a, baseSorted[i].r, baseSorted[i].g, baseSorted[i].b); } if (!_isReversed) { for (int i = 0; i < posArray.Length; i++) posArray[i] = 1.0f - posArray[i]; } Array.Sort(posArray, colorArray); // GDI+ 终极保险:强制首尾两端绝对等于 0.0 和 1.0 posArray[0] = 0.0f; posArray[posArray.Length - 1] = 1.0f; // 防重叠崩溃:GDI+ 要求位置必须严格递增,如果有两个节点在同一位置会直接崩溃 for (int i = 1; i < posArray.Length; i++) { if (posArray[i] <= posArray[i - 1]) { posArray[i] = posArray[i - 1] + 0.00001f; // 微微偏移 if (posArray[i] > 1.0f) posArray[i] = 1.0f; } } // 再次确保最后一位是 1.0f (防偏移挤压) posArray[posArray.Length - 1] = 1.0f; _fixedGradientBlend = new ColorBlend { Colors = colorArray, Positions = posArray }; } } public void AddPointByValue(double val) { val = Math.Max(_globalMin, Math.Min(_globalMax, val)); // 【新增防重叠】:检查是否已经存在非常接近该 Z 值的节点 if (_items.Any(item => Math.Abs(item.RawData.z - val) < 1e-5)) { // 如果是用户点击按钮手动输入,给个弹窗提示;双击误触则静默忽略 XtraMessageBox.Show("已存在相同数值的标注点!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } double range = _globalMax - _globalMin; double pos = (range == 0) ? 0.5 : (val - _globalMin) / range; // 获取采样色 float blendPos = _isReversed ? (float)pos : 1.0f - (float)pos; Color sampled = GetInterpolatedColor(_fixedGradientBlend, blendPos); var entry = new ColorScaleEntry { z = val, r = sampled.R, g = sampled.G, b = sampled.B, a = sampled.A, isContinue = 1 }; var newItem = new MarkItem { RawData = entry, NormalizedPosition = pos }; _items.Add(newItem); SetSelectedItem(newItem); Invalidate(); } public void DeleteSelected() { if (_selectedItem != null && _items.Count > 1) { _items.Remove(_selectedItem); _selectedItem = null; Invalidate(); } } private void SetSelectedItem(MarkItem item) { _items.ForEach(i => i.IsSelected = false); if (item != null) item.IsSelected = true; _selectedItem = item; Invalidate(); } protected override void OnMouseDown(MouseEventArgs e) { if (_editTextBox.Visible) { EndEdit(true); return; } var hit = _items.FirstOrDefault(p => p.Bounds.Contains(e.Location)); if (hit != null) { SetSelectedItem(hit); _draggingItem = hit; this.Focus(); Capture = true; } else { SetSelectedItem(null); } } protected override void OnMouseDoubleClick(MouseEventArgs e) { var hitText = _items.FirstOrDefault(p => p.TextBounds.Contains(e.Location)); if (hitText != null) { StartEdit(hitText); return; } if (_gradRect.Contains(e.Location)) { float h = Height - (PaddingTopBottom * 2); double ratio = (e.Y - PaddingTopBottom) / h; // 正向时,从上往下数值递减;反向时,从上往下数值递增 if (!_isReversed) ratio = 1.0 - ratio; double val = _globalMin + (_globalMax - _globalMin) * ratio; AddPointByValue(val); } } protected override void OnMouseMove(MouseEventArgs e) { if (_editTextBox.Visible) return; foreach (var item in _items) item.IsHovered = item.Bounds.Contains(e.Location); if (_draggingItem != null) { float h = Height - (PaddingTopBottom * 2); double newPos = (e.Y - PaddingTopBottom) / h; if (!_isReversed) newPos = 1.0 - newPos; // 正反转映射 newPos = Math.Max(0.0, Math.Min(1.0, newPos)); _draggingItem.NormalizedPosition = newPos; var data = _draggingItem.RawData; data.z = _globalMin + (_globalMax - _globalMin) * newPos; // 实时采样颜色 float blendPos = _isReversed ? (float)newPos : 1.0f - (float)newPos; Color sampled = GetInterpolatedColor(_fixedGradientBlend, blendPos); data.r = sampled.R; data.g = sampled.G; data.b = sampled.B; _draggingItem.RawData = data; } Invalidate(); } protected override void OnMouseUp(MouseEventArgs e) { _draggingItem = null; Capture = false; } private void StartEdit(MarkItem item) { _editingItem = item; _editTextBox.Text = item.RawData.z.ToString("F3"); _editTextBox.Location = new Point((int)item.TextBounds.X, (int)item.TextBounds.Y); _editTextBox.Visible = true; _editTextBox.Focus(); _editTextBox.SelectAll(); } private void EndEdit(bool commit) { if (_editingItem != null && commit && double.TryParse(_editTextBox.Text, out double newVal)) { newVal = Math.Max(_globalMin, Math.Min(_globalMax, newVal)); if (_items.Any(item => item != _editingItem && Math.Abs(item.RawData.z - newVal) < 1e-5)) { XtraMessageBox.Show("已存在相同数值的标注点,修改被取消!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); } else { var data = _editingItem.RawData; data.z = newVal; double range = _globalMax - _globalMin; double pos = (range == 0) ? 0.5 : (newVal - _globalMin) / range; _editingItem.NormalizedPosition = pos; // 实时采样颜色 float blendPos = _isReversed ? (float)pos : 1.0f - (float)pos; Color sampled = GetInterpolatedColor(_fixedGradientBlend, blendPos); data.r = sampled.R; data.g = sampled.G; data.b = sampled.B; _editingItem.RawData = data; } } _editTextBox.Visible = false; _editingItem = null; Invalidate(); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; Rectangle rect = ClientRectangle; _gradRect = new Rectangle(rect.X + 40, rect.Y + PaddingTopBottom, GradientWidth, rect.Height - (PaddingTopBottom * 2)); if (_fixedGradientBlend != null) { using (LinearGradientBrush brush = new LinearGradientBrush(_gradRect, Color.Black, Color.White, 90f)) { brush.InterpolationColors = _fixedGradientBlend; g.FillRectangle(brush, _gradRect); } g.DrawRectangle(Pens.DimGray, _gradRect); foreach (var item in _items) { // 【关键】:反转时 Min 在顶,Max 在底;正向时 Max 在顶,Min 在底 if (_isReversed) item.CurrentY = _gradRect.Top + (float)(item.NormalizedPosition * _gradRect.Height); else item.CurrentY = _gradRect.Bottom - (float)(item.NormalizedPosition * _gradRect.Height); item.Bounds = new RectangleF(_gradRect.Right, item.CurrentY - 8, 150, 16); item.TextBounds = new RectangleF(_gradRect.Right + 30, item.CurrentY - 8, 120, 16); // 绘制横线色 (保持实时采样色) float blendPos = _isReversed ? (float)item.NormalizedPosition : 1.0f - (float)item.NormalizedPosition; Color sampledColor = GetInterpolatedColor(_fixedGradientBlend, blendPos); using (Pen p = new Pen(sampledColor, item.IsHovered ? 2.5f : 1.5f)) { g.DrawLine(p, _gradRect.Right, item.CurrentY, _gradRect.Right + 25, item.CurrentY); } if (_editingItem != item) { string label = item.RawData.z.ToString("F3"); Brush textBrush = item.IsSelected ? Brushes.Blue : (item.IsHovered ? Brushes.Red : Brushes.Black); using (Font textFont = new Font(this.Font, item.IsSelected ? FontStyle.Bold : FontStyle.Regular)) { g.DrawString(label, textFont, textBrush, item.TextBounds.X, item.TextBounds.Y); } } } } } private Color GetInterpolatedColor(ColorBlend blend, float pos) { if (pos <= blend.Positions[0]) return blend.Colors[0]; if (pos >= blend.Positions.Last()) return blend.Colors.Last(); for (int i = 1; i < blend.Positions.Length; i++) { if (pos < blend.Positions[i]) { float t = (pos - blend.Positions[i - 1]) / (blend.Positions[i] - blend.Positions[i - 1]); Color c1 = blend.Colors[i - 1], c2 = blend.Colors[i]; return Color.FromArgb((int)(c1.A + (c2.A - c1.A) * t), (int)(c1.R + (c2.R - c1.R) * t), (int)(c1.G + (c2.G - c1.G) * t), (int)(c1.B + (c2.B - c1.B) * t)); } } return blend.Colors.Last(); } public List GetResult() => _items.Select(i => i.RawData).OrderBy(d => d.z).ToList(); // 接收是否反转的参数 isReversed public static List ShowEditor(IWin32Window owner, IEnumerable bgEntries, IEnumerable markEntries, double min, double max, bool isReversed) { using (XtraForm form = new XtraForm()) { form.Text = "色标编辑器"; form.Size = new Size(315, 600); form.StartPosition = FormStartPosition.CenterParent; form.FormBorderStyle = FormBorderStyle.FixedDialog; form.MaximizeBox = false; form.MinimizeBox = false; var editor = new FrmGradientRulerEditor { Dock = DockStyle.Fill }; editor.SetData(bgEntries, markEntries, min, max, isReversed); PanelControl pnlBottom = new PanelControl { Dock = DockStyle.Bottom, Height = 60 }; SimpleButton btnAdd = new SimpleButton { Text = "增加", Size = new Size(80, 30), Location = new Point(15, 15) }; SimpleButton btnDel = new SimpleButton { Text = "删除", Size = new Size(80, 30), Location = new Point(105, 15) }; SimpleButton btnOk = new SimpleButton { Text = "保存", Size = new Size(100, 30), Location = new Point(195, 15), DialogResult = DialogResult.OK }; btnAdd.Click += (s, e) => { XtraInputBoxArgs args = new XtraInputBoxArgs(); args.Caption = "增加标注"; args.Prompt = "请输入Z值"; args.DefaultResponse = min.ToString("F3"); args.Buttons = new DialogResult[] { DialogResult.OK, DialogResult.Cancel }; var result = XtraInputBox.Show(args); if (result != null && double.TryParse(result.ToString(), out double dVal)) { editor.AddPointByValue(dVal); } }; btnDel.Click += (s, e) => editor.DeleteSelected(); pnlBottom.Controls.AddRange(new Control[] { btnAdd, btnDel, btnOk }); form.Controls.Add(editor); form.Controls.Add(pnlBottom); return (form.ShowDialog(owner) == DialogResult.OK) ? editor.GetResult() : null; } } } }