自定义可移动点二维坐标轴控件

自定义可移动点二维坐标轴控件

自定义可移动点二维坐标轴控件

目录

路由参数

X_YResultCollection为当前X轴对应Y轴值存储字典

public class ResultCollectionChangedEventArgs(RoutedEvent routedEvent, object source, IDictionary<double, double> resultCollection) : RoutedEventArgs(routedEvent, source)
{
    public IDictionary<double, double> X_YResultCollection = resultCollection;
}

坐标轴控件定义

class CoordinateSystemControl : UserControl
{
    #region 依赖属性

    /// <summary>
    /// 标注线颜色
    /// </summary>
    public Brush CalloutLineColor
    {
        get { return (Brush)GetValue(CalloutLineColorProperty); }
        set { SetValue(CalloutLineColorProperty, value); }
    }

    /// <summary>
    /// 坐标系颜色
    /// </summary>
    public Brush CoordinateSystemColor
    {
        get { return (Brush)GetValue(CoordinateSystemColorProperty); }
        set { SetValue(CoordinateSystemColorProperty, value); }
    }

    /// <summary>
    /// X轴名称
    /// </summary>
    public string X_AxisName
    {
        get { return (string)GetValue(X_AxisNameProperty); }
        set { SetValue(X_AxisNameProperty, value); }
    }

    /// <summary>
    /// Y轴名称
    /// </summary>
    public string Y_AxisName
    {
        get { return (string)GetValue(Y_AxisNameProperty); }
        set { SetValue(Y_AxisNameProperty, value); }
    }

    /// <summary>
    /// 选择点颜色
    /// </summary>
    public Brush SelectElementColor
    {
        get { return (Brush)GetValue(SelectElementColorProperty); }
        set { SetValue(SelectElementColorProperty, value); }
    }

    /// <summary>
    /// 选择点被选中图标
    /// </summary>
    public Brush SelectedElementColor
    {
        get { return (Brush)GetValue(SelectedElementColorProperty); }
        set { SetValue(SelectedElementColorProperty, value); }
    }

    /// <summary>
    /// Y轴箭头图标
    /// </summary>
    public FrameworkElement YAeeow
    {
        get { return (FrameworkElement)GetValue(YAeeowProperty); }
        set { SetValue(YAeeowProperty, value); }
    }

    /// <summary>
    /// X轴箭头图标
    /// </summary>
    public FrameworkElement XAeeow
    {
        get { return (FrameworkElement)GetValue(XAeeowProperty); }
        set { SetValue(XAeeowProperty, value); }
    }

    /// <summary>
    /// Y轴集合
    /// </summary>
    public IList<double> YCollection
    {
        get { return (IList<double>)GetValue(YCollectionProperty); }
        set { SetValue(YCollectionProperty, value); }
    }

    /// <summary>
    /// X轴集合
    /// </summary>
    public IList<double> XCollection
    {
        get { return (IList<double>)GetValue(XCollectionProperty); }
        set { SetValue(XCollectionProperty, value); }
    }

    /// <summary>
    /// X轴结点对应Y轴值
    /// </summary>
    public IDictionary<double, double> X_YResultCollection
    {
        get { return (IDictionary<double, double>)GetValue(X_YResultCollectionProperty); }
        set { SetValue(X_YResultCollectionProperty, value); }
    }

    /// <summary>
    /// 结果集合改变命令
    /// </summary>
    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    #endregion

    #region 路由事件

    /// <summary>
    /// 结果集合改变路由事件
    /// </summary>
    public static readonly RoutedEvent ResultCollectionChangedEvent = EventManager.RegisterRoutedEvent("ResultCollectionChanged", RoutingStrategy.Bubble, typeof(EventHandler<ResultCollectionChangedEventArgs>), typeof(CoordinateSystemControl));

    public event RoutedEventHandler ResultCollectionChanged
    {
        add => AddHandler(ResultCollectionChangedEvent, value);
        remove => RemoveHandler(ResultCollectionChangedEvent, value);
    }

    #endregion

    #region 依赖属性注册

    public static readonly DependencyProperty CalloutLineColorProperty =
        DependencyProperty.Register("CalloutLineColor", typeof(Brush), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata());

    public static readonly DependencyProperty CoordinateSystemColorProperty =
       DependencyProperty.Register("CoordinateSystemColor", typeof(Brush), typeof(CoordinateSystemControl), new PropertyMetadata());

    public static readonly DependencyProperty X_AxisNameProperty =
        DependencyProperty.Register("X_AxisName", typeof(string), typeof(CoordinateSystemControl), new PropertyMetadata("X轴"));

    public static readonly DependencyProperty Y_AxisNameProperty =
   DependencyProperty.Register("Y_AxisName", typeof(string), typeof(CoordinateSystemControl), new PropertyMetadata("Y轴"));

    public static readonly DependencyProperty SelectElementColorProperty =
    DependencyProperty.Register("SelectElementColor", typeof(Brush), typeof(CoordinateSystemControl), new PropertyMetadata());

    public static readonly DependencyProperty SelectedElementColorProperty =
        DependencyProperty.Register("SelectedElementColor", typeof(Brush), typeof(CoordinateSystemControl), new PropertyMetadata());

    public static readonly DependencyProperty YAeeowProperty =
        DependencyProperty.Register("YAeeow", typeof(FrameworkElement), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata());

    public static readonly DependencyProperty XAeeowProperty =
        DependencyProperty.Register("XAeeow", typeof(FrameworkElement), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata());

    public static readonly DependencyProperty YCollectionProperty =
        DependencyProperty.Register("YCollection", typeof(IList<double>), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, YCollectionCallback));

    public static readonly DependencyProperty XCollectionProperty =
        DependencyProperty.Register("XCollection", typeof(IList<double>), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, XCollectionCallback));

    public static readonly DependencyProperty X_YResultCollectionProperty =
        DependencyProperty.Register("X_YResultCollection", typeof(IDictionary<double, double>), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, X_YResultCollectionChangedCallback));

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(CoordinateSystemControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, CommandChangedCallback));

    #endregion

    #region 私有字段
    private bool _isFirstLoaded = false;
    //坐标系顶点留空距离
    private readonly int _spacedPoints = 15;
    private readonly Canvas _canvas;
    //X、Y轴线
    private Line _yLine;
    private Line _xLine;

    private Point _originPoint;
    private Point _yEndpoint;
    private Point _xEndpoint;
    private int _ySingleDistance;
    private int _xSingleDistance;
    private double _proportionSize;

    //选择点实时数据位置变形
    private Transform _transform_textBox;
    //选择点位置校准变形
    private Transform _transform_chekEllipse;
    //拖动选择点位置变形
    private Transform _transform_clickMoveEllipse;

    //选择点显示值TextBox映射
    private readonly Dictionary<Ellipse, TextBox> _selectEllipse_TextBoxMap = [];
    //选择点与连接线链表
    private readonly LinkedList<Shape> _selectEllipse_ConnectionLines = [];
    private readonly Dictionary<double, Ellipse> _xValue_EllipseMap = [];

    #endregion

    public CoordinateSystemControl()
    {
        _canvas = new Canvas() { Margin = new Thickness(20) };
        _canvas.SizeChanged += Canvas_SizeChanged;
        _canvas.Loaded += Canvas_Loaded;
        this.AddChild(_canvas);

        _transform_textBox = new TranslateTransform() { X = -13, Y = -25 };
        _transform_chekEllipse = new TranslateTransform() { X = -7, Y = -5 };
        _transform_clickMoveEllipse = new TransformGroup
        {
            Children = { new TranslateTransform() { X = -3, Y = -5 }, new ScaleTransform(1.5, 1.5) }
        };

        //初始化依赖属性
        CalloutLineColor = Brushes.DarkBlue;
        CoordinateSystemColor = Brushes.Black;
        SelectElementColor = Brushes.BlueViolet;
        SelectedElementColor = Brushes.MediumVioletRed;
    }

    #region 事件与回调
    private void Canvas_Loaded(object sender, RoutedEventArgs e)
    {
        Initialize();
        _isFirstLoaded = true;
    }

    private static void CommandChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue is ICommand command)
        {
            var sender = d as CoordinateSystemControl;
            command.CanExecuteChanged -= sender!.Command_CanExecuteChanged;
            command.CanExecuteChanged += sender.Command_CanExecuteChanged;
        }
    }

    private void Command_CanExecuteChanged(object? sender, EventArgs e)
    {
        IsEnabled = Command.CanExecute(null);
    }

    private static void YCollectionCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.OldValue is ObservableCollection<double> oldValue)
        {
            oldValue.CollectionChanged -= YCollectionChangedCallback;
        }
        if (e.NewValue is ObservableCollection<double> newValue)
        {
            newValue.CollectionChanged += YCollectionChangedCallback;
        }


        void YCollectionChangedCallback(object? sender, NotifyCollectionChangedEventArgs e)
        {
            if (d is CoordinateSystemControl control)
            {
                if (control._isFirstLoaded)
                {
                    control.Initialize();
                }
            }
        }
    }

    private static void XCollectionCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.OldValue is ObservableCollection<double> oldValue)
        {
            oldValue.CollectionChanged -= XCollectionChangedCallback;
        }
        if (e.NewValue is ObservableCollection<double> newValue)
        {
            newValue.CollectionChanged += XCollectionChangedCallback;
        }


        void XCollectionChangedCallback(object? sender, NotifyCollectionChangedEventArgs e)
        {
            if (d is CoordinateSystemControl control)
            {
                if (control._isFirstLoaded)
                {
                    control.Initialize();
                }
            }
        }
    }

    private static void X_YResultCollectionChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs de)
    {
        if (de.OldValue is ObservableDictionary<double, double> oldValue)
        {
            oldValue.CollectionChanged -= X_YResultCollectionChangedCallback;
        }
        if (de.NewValue is ObservableDictionary<double, double> newValue)
        {
            newValue.CollectionChanged += X_YResultCollectionChangedCallback;
        }


        void X_YResultCollectionChangedCallback(object? sender, NotifyCollectionChangedEventArgs e)
        {
            if (d is not CoordinateSystemControl control) return;
            if (control.IsMouseCaptureWithin) return;

            var oldValue = de.OldValue as ObservableDictionary<double, double>;
            if (de.NewValue is not ObservableDictionary<double, double> newValue) return;

            foreach (var item in newValue)
            {
                double x = item.Key;
                var point = oldValue?.Where(n => n.Key == x).FirstOrDefault();
                if (point is null || point.Value.Value != item.Value)
                {
                    if (!control._xValue_EllipseMap.TryGetValue(x, out var ellipse)) continue;
                    double YUnit = control.CalculateYUnit(item.Value);
                    control.SetSelectEllipsePosition(ellipse, control.GetActualCanvasTop(YUnit));
                }
            }
        }
    }

    private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        var textBox = (TextBox)sender;
        if (!textBox.IsKeyboardFocused) return;

        if (!double.TryParse(textBox.Text, out double value) || !ValidateNumber(value))
        {
            textBox.Background = Brushes.OrangeRed;
            return;
        }
        textBox.Background = Brushes.Transparent;

        double result = CalculateYUnit(value);

        var selectEllipse = _selectEllipse_TextBoxMap.Single(n => n.Value.GetHashCode() == textBox.GetHashCode()).Key;

        var canvasTop = GetActualCanvasTop(result);
        SetSelectEllipsePosition(selectEllipse, canvasTop);
        StoragePosition(selectEllipse);

        bool ValidateNumber(double value)
        {
            return value >= 0 && value <= YCollection.Last();
        }
    }

    private void SelectEllipse_Control_MouseUp(object sender, MouseButtonEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Released)
        {
            Mouse.Capture(null);
            var element = (Ellipse)sender;
            element.RenderTransform = _transform_chekEllipse;
            element.Fill = SelectElementColor;

            StoragePosition(element);
            RaiseEventInvokeCommand();
        }
        e.Handled = true;
    }

    private void SelectEllipse_Control_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed && Mouse.Captured == sender)
        {
            var ellipse = (Ellipse)sender;
            var position = ValidataMouseYValue(e.GetPosition(_canvas).Y - 5);
            SetSelectEllipsePosition(ellipse, position);
        }
        e.Handled = true;
    }

    private void SelectEllipse_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            Mouse.Capture((IInputElement)sender);
            var element = (Ellipse)sender;
            element.RenderTransform = _transform_clickMoveEllipse;
            element.Fill = SelectedElementColor;
        }
        e.Handled = true;
    }
    #endregion

    void Initialize()
    {
        YCollection ??= [1, 2, 3, 4, 5, 6, 7, 8, 9];
        XCollection ??= [1, 2, 3, 4, 5, 6, 7, 8, 9];
        //清理缓存数据
        _selectEllipse_TextBoxMap.Clear();
        _selectEllipse_ConnectionLines.Clear();
        _xValue_EllipseMap.Clear();

        if (_canvas.Children.Count > 0) _canvas.Children.RemoveRange(0, _canvas.Children.Count);

        _originPoint = new Point(0, _canvas.ActualHeight);
        _yEndpoint = new Point(_originPoint.X, _spacedPoints + 7);
        _xEndpoint = new Point(_canvas.ActualWidth - _spacedPoints - X_AxisName.Length * 7, _originPoint.Y);
        _ySingleDistance = (int)CalculateSingleDistance(Math.Abs(_originPoint.Y - _yEndpoint.Y), YCollection);
        _xSingleDistance = (int)CalculateSingleDistance(Math.Abs(_originPoint.X - _xEndpoint.X), XCollection);
        _proportionSize = _ySingleDistance / 7;

        _yLine = new Line() { Stroke = CoordinateSystemColor, StrokeThickness = 2, X1 = _yEndpoint.X, Y1 = _yEndpoint.Y, X2 = _originPoint.X, Y2 = _originPoint.Y };
        _xLine = new Line() { Stroke = CoordinateSystemColor, StrokeThickness = 2, X1 = _originPoint.X, Y1 = _originPoint.Y, X2 = _xEndpoint.X, Y2 = _xEndpoint.Y };

        _canvas.Children.Add(_yLine);
        _canvas.Children.Add(_xLine);

        var y_AxisText = new TextBlock { Text = Y_AxisName };
        var x_AxisText = new TextBlock { Text = X_AxisName };
        _canvas.Children.Add(y_AxisText);
        _canvas.Children.Add(x_AxisText);
        Canvas.SetTop(x_AxisText, _yEndpoint.Y - _spacedPoints);
        Canvas.SetLeft(x_AxisText, _yEndpoint.X + _spacedPoints);
        Canvas.SetTop(y_AxisText, _xEndpoint.Y - _spacedPoints * 2);
        Canvas.SetLeft(y_AxisText, _xEndpoint.X);

        //加入坐标轴图标
        AddAeeow();
        //添加标注线和标注值和选择点
        AddCalloutLine();
    }

    private void AddAeeow()
    {
        YAeeow ??= new Path()
        {
            Data = new PathGeometry()
            {
                Figures = [new PathFigure(){
                    IsClosed = true,
                    StartPoint = new Point(0, 0),
                    Segments = [new LineSegment(new Point(-_proportionSize, _proportionSize/2), true),
                        new LineSegment(new Point(0, -_proportionSize), true),
                        new LineSegment(new Point(_proportionSize, _proportionSize/2), true)]
                    }]
            },
            Fill = CoordinateSystemColor
        };

        XAeeow ??= new Path()
        {
            Data = new PathGeometry()
            {
                Figures = [new PathFigure() {
                    IsClosed = true,
                    StartPoint = new Point(0, 0),
                    Segments = [new LineSegment(new Point(-_proportionSize/2, _proportionSize), true),
                        new LineSegment(new Point(_proportionSize, 0), true),
                        new LineSegment(new Point(-_proportionSize/2, -_proportionSize), true)]
                    }]
            },
            Fill = CoordinateSystemColor
        };
        _canvas.Children.Add(YAeeow);
        _canvas.Children.Add(XAeeow);

        Canvas.SetTop(this.YAeeow, _yEndpoint.Y);
        Canvas.SetLeft(this.YAeeow, _yEndpoint.X);
        Canvas.SetTop(this.XAeeow, _xEndpoint.Y);
        Canvas.SetLeft(this.XAeeow, _xEndpoint.X);
    }

    private void AddCalloutLine()
    {
        //添加Y轴标注线和标注值
        AddYCalloutLines();
        //添加X轴标注线和标注值和选择点
        AddXCalloutLinesAndSelectEllipses();
    }

    private void AddYCalloutLines()
    {
        for (int i = 1; i < YCollection.Count + 1; i++)
        {
            var canvasTop = GetActualCanvasTop(i);
            //添加标注线和标注值
            var line = new Line() { Stroke = CalloutLineColor, StrokeThickness = 1, X1 = 0, Y1 = 0, X2 = _proportionSize, Y2 = 0 };
            var text = new TextBlock() { Text = YCollection[i - 1].ToString(), RenderTransformOrigin = new(0.5, 0.5), RenderTransform = new TranslateTransform() { X = -12, Y = -8 } };

            Canvas.SetLeft(line, _originPoint.X);
            Canvas.SetTop(line, canvasTop);
            Canvas.SetLeft(text, _originPoint.X);
            Canvas.SetTop(text, canvasTop);

            _canvas.Children.Add(line);
            _canvas.Children.Add(text);
        }
    }

    private void AddXCalloutLinesAndSelectEllipses()
    {
        for (int i = 1; i < XCollection.Count + 1; i++)
        {
            var currentValue = XCollection[i - 1];
            var cancasLeft = GetActualCanvasLeft(i);

            var line = new Line() { Stroke = CalloutLineColor, StrokeThickness = 1, X1 = 0, Y1 = 0, X2 = 0, Y2 = -_proportionSize };
            var textBlock = new TextBlock() { Text = currentValue.ToString(), RenderTransformOrigin = new(0.5, 0.5), RenderTransform = new TranslateTransform() { X = -5, Y = 5 } };

            Canvas.SetLeft(line, cancasLeft);
            Canvas.SetTop(line, _originPoint.Y);
            Canvas.SetLeft(textBlock, cancasLeft);
            Canvas.SetTop(textBlock, _originPoint.Y);

            _canvas.Children.Add(textBlock);
            _canvas.Children.Add(line);

            //添加选择点
            var selectEllipse = new Ellipse
            {
                Width = 14,
                Height = 14,
                Fill = SelectElementColor,
                RenderTransformOrigin = new Point(0.5, 0.5),
                RenderTransform = _transform_chekEllipse,
            };
            selectEllipse.AddHandler(MouseDownEvent, new MouseButtonEventHandler(SelectEllipse_MouseDown));
            selectEllipse.AddHandler(MouseMoveEvent, new MouseEventHandler(SelectEllipse_Control_MouseMove));
            selectEllipse.AddHandler(MouseUpEvent, new MouseButtonEventHandler(SelectEllipse_Control_MouseUp));

            X_YResultCollection ??= new ObservableDictionary<double, double>();
            bool excist = X_YResultCollection.Any(n => n.Key == currentValue);
            string text = excist ? X_YResultCollection!.Single(n => n.Key == currentValue).Value.ToString("F2") : string.Empty;

            var textBox = new TextBox() { Text = text, Background = Brushes.Transparent, BorderThickness = new(0), RenderTransform = _transform_textBox };
            textBox.TextChanged += TextBox_TextChanged;

            _canvas.Children.Add(selectEllipse);
            _canvas.Children.Add(textBox);
            _selectEllipse_TextBoxMap.Add(selectEllipse, textBox);
            _xValue_EllipseMap.Add(currentValue, selectEllipse);

            AddSelectEllipse(selectEllipse, i, currentValue);
            AddConnectionLine(selectEllipse, i == XCollection.Count);
        }
    }

    private void AddConnectionLine(Ellipse selectEllipse, bool isLast = false)
    {
        if (_selectEllipse_ConnectionLines.Count == 0)
        {
            _selectEllipse_ConnectionLines.AddFirst(selectEllipse);
        }
        else
        {
            var lastConnectionLine = (Line)_selectEllipse_ConnectionLines.Last!.Value;
            lastConnectionLine.X2 = Canvas.GetLeft(selectEllipse);
            lastConnectionLine.Y2 = Canvas.GetTop(selectEllipse);
            _selectEllipse_ConnectionLines.AddLast(selectEllipse);
        }

        if (isLast) return;

        var ConnectionLine = new Line() { StrokeThickness = 1, X1 = Canvas.GetLeft(selectEllipse), Y1 = Canvas.GetTop(selectEllipse), X2 = 0, Y2 = 0, Stroke = Brushes.LightGray };
        _selectEllipse_ConnectionLines.AddLast(ConnectionLine);
        _canvas.Children.Add(ConnectionLine);
        //将选择点置于最上层
        Panel.SetZIndex(selectEllipse, 1);
    }

    private void ChangeConnectionPosition(Ellipse ellipse, double canvasTop)
    {
        var linkedNode = _selectEllipse_ConnectionLines.Find(ellipse);
        if (linkedNode?.Previous != null)
        {
            var previousLine = (Line)linkedNode.Previous.ValueRef;
            previousLine.Y2 = canvasTop;
        }
        if (linkedNode?.Next != null)
        {
            var nextLine = (Line)linkedNode.Next.ValueRef;
            nextLine.Y1 = canvasTop;
        }
    }

    private double ValidataMouseYValue(double yPosition)
    {
        double MaxYValue = _originPoint.Y - _ySingleDistance * YCollection.Count;
        if (yPosition < MaxYValue)
        {
            yPosition = MaxYValue;
        }
        if (yPosition > _originPoint.Y)
        {
            yPosition = _originPoint.Y;
        }
        return yPosition;
    }

    private void StoragePosition(Ellipse ellipse)
    {
        var xUnit = Math.Abs(_originPoint.X - Canvas.GetLeft(ellipse)) / _xSingleDistance;
        var xValue = XCollection[(int)xUnit - 1];
        var yUnit = (_originPoint.Y - Canvas.GetTop(ellipse)) / _ySingleDistance;

        double yValue = CalculateYValue(yUnit);
        X_YResultCollection[xValue] = Math.Round(yValue, 2);
    }

    private double CalculateYValue(double yunit)
    {
        var yArray = YCollection.ToArray();
        bool minFlag = yunit >= 1;
        if (yunit >= yArray.Length) return yArray.Last();

        int lastValueIndex = (int)yunit - 1;
        double lastValue = minFlag ? yArray[lastValueIndex] : 0;
        double nextValue = yArray[lastValueIndex + 1];
        return lastValue + (yunit - lastValueIndex - 1) * (nextValue - lastValue);
    }

    private double CalculateYUnit(double yValue)
    {
        double lastValue = 0;
        int i = -1;
        foreach (var item in YCollection)
        {
            i++;
            if (item < yValue)
            {
                lastValue = item;
                continue;
            }
            return i + (yValue - lastValue) / (item - lastValue);
        }

        return 0;
    }

    private void AddSelectEllipse(Ellipse ellipse, double xUnit, double xValue)
    {
        if (X_YResultCollection.Where(n => n.Key == xValue).Any())
        {
            var yValue = X_YResultCollection.Single(n => n.Key == xValue).Value;
            double yUnit = CalculateYUnit(yValue);
            SetSelectEllipsePosition(ellipse, GetActualCanvasTop(yUnit), GetActualCanvasLeft(xUnit));
        }
        else
        {
            X_YResultCollection.Add(xValue, 1);
            AddSelectEllipse(ellipse, xUnit, xValue);
        }
    }

    private void SetSelectEllipsePosition(Ellipse ellipse, double top, double? left = null)
    {
        Canvas.SetTop(ellipse, top);

        var textBox = _selectEllipse_TextBoxMap[ellipse];
        Canvas.SetTop(textBox, top);

        //改变对应textblock值
        var yUnit = (_originPoint.Y - top) / _ySingleDistance;
        double yValue = CalculateYValue(yUnit);
        textBox.Text = yValue.ToString("F2");

        //改变连接线位置
        ChangeConnectionPosition(ellipse, top);

        if (left.HasValue)
        {
            Canvas.SetLeft(ellipse, (double)left);
            Canvas.SetLeft(textBox, (double)left);
        }
    }

    private double CalculateSingleDistance(double totalDistance, IEnumerable<double> values)
    {
        return (totalDistance - _spacedPoints) / values.Count();
    }

    private void Canvas_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (e.PreviousSize.Width != 0 && e.PreviousSize.Height != 0)
        {
            Initialize();
        }
        e.Handled = true;
    }

    private double GetActualCanvasLeft(double unit) => _originPoint.X + _xSingleDistance * unit;

    private double GetActualCanvasTop(double unit) => _originPoint.Y - _ySingleDistance * unit;

    private void RaiseEventInvokeCommand()
    {
        ResultCollectionChangedEventArgs args = new(ResultCollectionChangedEvent, this, X_YResultCollection);

        RaiseEvent(args);

        if (Command?.CanExecute(null) ?? true)
        {
            Command?.Execute(X_YResultCollection);
        }
    }

    public static double CalculateYValue(Dictionary<double, double> dic, double xValue)
    {
        double lastX = 0;
        double lastY = 0;
        foreach (var point in dic)
        {
            if (xValue > point.Key)
            {
                lastX = point.Key;
                lastY = point.Value;
                continue;
            }
            else if (xValue == point.Key)
            {
                return point.Value;
            }

            return (point.Value - lastY) / (point.Key - lastX) * (xValue - lastX) / (point.Key - lastX) + lastY;
        }
        return dic[dic.Max(n => n.Key)];
    }
}

Demo

在拖动完选择点后会触发命令和抛出路由事件

  • View
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="400" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <local:CoordinateSystemControl
        Command="{Binding TestCommand}"
        X_AxisName="X"
        XCollection="{Binding XCollection}"
        YCollection="{Binding YCollection}"
        X_YResultCollection="{Binding Datas}"
        Y_AxisName="Y" />
    <Button
        Grid.Row="1"
        Width="40"
        Height="20"
        Command="{Binding ClickCommand}"
        Content="click me" />
</Grid>
  • ViewModel
  public ObservableCollection<double> XCollection { get; set; } = [1, 3, 5, 7, 9];
  public ObservableCollection<double> YCollection { get; set; } = [1, 3, 5, 7, 9];
  public ObservableDictionary<double, double> Datas { get; set; } = [];

//构造函数
  public ViewModel()
  {
      Datas.Add(1, 1);
      Datas.Add(3, 1);
      Datas.Add(5, 1);
      Datas.Add(7, 1);
      Datas.Add(9, 1);
  }

  [RelayCommand]
  public async Task Test(IDictionary<double, double> data)
  {
  }

  [RelayCommand]
  private void Click()
  {
      Datas[1.0] = 5;
  }

注意

ObservableDictionary可查看[https://www.cnblogs.com/yinyuessh/p/18205333]

希望能给你带来帮助 --来自.net菜鸡粉丝的祝愿

评论