王大爷文明城市分爷 发表于 2024-5-22 09:08:28

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

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

目录


[*]路由参数
[*]坐标轴控件定义
[*]Demo
路由参数

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 ??= ;
      XCollection ??= ;
      //清理缓存数据
      _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.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;
            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 = 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 : 0;
      double nextValue = yArray;
      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;
      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;
    }
}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; } = ;
public ObservableCollection<double> YCollection { get; set; } = ;
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);
}


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


private void Click()
{
      Datas = 5;
}注意

ObservableDictionary可查看[https://www.cnblogs.com/yinyuessh/p/18205333]
希望能给你带来帮助 --来自.net菜鸡粉丝的祝愿

来源:https://www.cnblogs.com/yinyuessh/p/18205346
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 自定义可移动点二维坐标轴控件