如何在WPF中创建类似于XY控制器的用户界面?

5

我正在寻找类似于这样的东西。

我应该能够使用鼠标拖动坐标系内部的坐标。 坐标的位置确定了X和Y的值。

是否有一个现成的控件可以重复使用? 如果没有,我该如何编写一个?

我正在寻找类似于这样的东西

3个回答

4

我没有见过这样的控件,我猜你得自己编写代码。有几个要实现的东西,我只谈论图形部分。

首先,你应该定义一个清单,说明这个控件应该如何行动(即,只有在按下鼠标按钮时才能用光标移动线条),完成后就可以开始乐趣了!

编辑:好的,现在是一个初步版本,但是当我说初步时,我的意思是粗略的。我将其放入窗口中而不是用户控件,你可以将其复制粘贴到你的控件中。它有很多缺陷,应该在解决出现的所有问题之后才能用于生产。此外,你必须小心使用像Stretch-Alignment这样的像素设计与灵活/相对设计混合的情况。我通过使窗口不可调整大小来将其限制为像素精度。

<Window x:Class="graphedit.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" 
    x:Name="window"
    MouseMove="Window_MouseMove"
    Height="400" Width="400"
    ResizeMode="NoResize">
<Canvas x:Name="canvas"
        HorizontalAlignment="Stretch"
        VerticalAlignment="Stretch">
    <Canvas.Background>
        <RadialGradientBrush>
            <GradientStop Color="#333333" Offset="1"></GradientStop>
            <GradientStop Color="#666666" Offset="0"></GradientStop>
        </RadialGradientBrush>
    </Canvas.Background>
    <Border BorderThickness="0,0,1,1"
            BorderBrush="White"
            Margin="0,0,0,0"
            Width="{Binding Path=Point.X}"
            Height="{Binding Path=Point.Y}"></Border>

    <Border BorderThickness="1,1,0,0"
            BorderBrush="White"
            Margin="{Binding Path=BottomRightBoxMargin}"
            Width="{Binding Path=BottomRightBoxDimensions.X}"
            Height="{Binding Path=BottomRightBoxDimensions.Y}"></Border>
    <Border BorderThickness="1"
            BorderBrush="White"
            Margin="{Binding Path=GripperMargin}"
            Background="DimGray"
            CornerRadius="4"               
            Width="10"
            x:Name="gripper"
            MouseDown="gripper_MouseDown"
            MouseUp="gripper_MouseUp"
            Height="10"></Border>
    <TextBox Text="{Binding Path=Point.X}" Canvas.Left="174" Canvas.Top="333" Width="42"></TextBox>
    <TextBox Text="{Binding Path=Point.Y}" Canvas.Left="232" Canvas.Top="333" Width="45"></TextBox>
    <TextBlock Foreground="White" Canvas.Left="162" Canvas.Top="336">X</TextBlock>
    <TextBlock Canvas.Left="222" Canvas.Top="336" Foreground="White" Width="13">Y</TextBlock>
</Canvas>

代码后台看起来像这样:

using System.ComponentModel;
using System.Windows;

namespace graphedit
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
    private bool isDragging;

    private Point mousePositionBeforeMove;

    private Point point;

    public Point Point
    {
        get { return this.point; }
        set
        {
            this.point = value;
            this.InvokePropertyChanged(null);
        }
    }

    public Thickness BottomRightBoxMargin
    {
        get
        {
            Thickness t = new Thickness()
                          {
                              Left = this.Point.X,
                              Top = this.Point.Y
                          };
            return t;
        }
    }

    public Thickness GripperMargin
    {
        get
        {
            Thickness t = new Thickness()
            {
                Left = this.Point.X - 5,
                Top = this.Point.Y - 5
            };
            return t;
        }
    }

    public Point BottomRightBoxDimensions
    {
        get
        {
            return new Point(this.Width - this.Point.X,
                             this.Height - this.Point.Y);
        }
    }

    public MainWindow()
    {
        InitializeComponent();
        this.Point = new Point(100, 80);
        this.DataContext = this;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void InvokePropertyChanged(string name)
    {
        PropertyChangedEventArgs args = new PropertyChangedEventArgs(name);
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            handler(this, args);
        }
    }

    private void gripper_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        this.isDragging = true;
        this.mousePositionBeforeMove = e.GetPosition( this.canvas );
    }

    private void gripper_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        this.isDragging = false;
    }

    private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
    {
        if(this.isDragging)
        {
            Point currentMousePosition = e.GetPosition( this.canvas );
            double deltaX = currentMousePosition.X - this.mousePositionBeforeMove.X;
            double deltaY = currentMousePosition.Y - this.mousePositionBeforeMove.Y;

            double newPointX = (this.Point.X + deltaX < 0 ? 0 : (this.Point.X + deltaX > this.Width ? this.Width : this.Point.X + deltaX)) ;
            double newPointY = (this.Point.Y + deltaY < 0 ? 0 : (this.Point.Y + deltaY > this.Width ? this.Width : this.Point.Y + deltaY)) ;

            this.Point = new Point(newPointX,newPointY);
            this.mousePositionBeforeMove = currentMousePosition;
        }
    }
}
}

1

你可以看一下绘图库DynamicDataDisplay。这是微软的一个研究项目创建的库(据我所知),可能会提供你需要的功能。

首先,在你的项目中引用DynamicDataDisplay dll,然后在你的xaml中创建以下命名空间:

xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"

然后,您可以将ChartPlotter对象添加到XAML中,并从中删除您不需要的所有元素(轴线、图例等)。 您可以使用CursorCoordinateGraph跟踪鼠标。 如果您想更改布局等,则可以使用VerticalRange对象。
    <d3:ChartPlotter Width="500" Height="300" 
                     MainHorizontalAxisVisibility="Collapsed" 
                     MainVerticalAxisVisibility="Collapsed"
                     LegendVisibility="Collapsed" NewLegendVisible="False"> 
        <!--This allows you to track the mouse-->       
        <d3:CursorCoordinateGraph x:Name="cursorGraph"/>
        <!-- this range does nothing more then make the background gray,
             there are other ways to achieve this too-->
        <d3:VerticalRange Value1="-300" Value2="300" Fill="Gray"/>
    </d3:ChartPlotter>

如果你想追踪鼠标的位置,你可以使用代码后台:

Point current = cursorGraph.Position;

或将Position属性绑定到您的视图模型:

<d3:CursorCoordinateGraph x:Name="cursorGraph" Position="{Binding CurrentMousePosition}"/>

如果您想在单击时实际修复位置,我猜您将不得不在ChartPlotter的OnClick或MouseClick事件处理程序中创建一个新的CursorCoordinateGraph,并计算该点,并为新的图形提供它:

//pseudo code!!!
mainGraph.DoubleClick += HandleDoubleClick;

private void HandleDoubleClick(object sender, MouseButtonEventArgs e){
    //get position from current graph:
    var position = cursor.Position;
    //you have to calculate the "real" position in the chart because 
    //the Point provided by Position is not the real point, but screen coordinates
    //something like cursor.TranslatePoint(cursor.Position, mainGraph);
    var newCoord = new CursorCoordinateGraph { Position = position };
    mainGraph.Children.Add(newCoord);
}

你可能需要一些工作来让它看起来像你想要的样子。我建议你浏览一下在codeplex页面上提供的示例,并查看讨论页面。这个库非常庞大,有很多可能性,但实际上提供了很少的文档资料...

希望这能指引你朝着正确的方向前进!


1

我已经制作了一个简单可重用的ControllerCanvas控件的演示。希望它能对你有所帮助。你可以在XAML中设置所有属性。

ControllerCanvas.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace XYControllerDemo
{
    public class ControllerCanvas : Canvas
    {
        #region Dependency Properties

        public Point Point
        {
            get { return (Point)GetValue(PointProperty); }
            set 
            {
                if (value.X < 0.0)
                {
                    value.X = 0.0;
                }

                if (value.Y < 0.0)
                {
                    value.Y = 0.0;
                }

                if (value.X > this.ActualWidth)
                {
                    value.X = this.ActualWidth;
                }

                if (value.Y > this.ActualHeight)
                {
                    value.Y = this.ActualHeight;
                }

                SetValue(PointProperty, value);
            }
        }

        public static readonly DependencyProperty PointProperty =
            DependencyProperty.Register("Point", typeof(Point), typeof(ControllerCanvas), new FrameworkPropertyMetadata(new Point(),
                FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));

        public Brush ControllerStroke
        {
            get { return (Brush)GetValue(ControllerStrokeProperty); }
            set { SetValue(ControllerStrokeProperty, value); }
        }

        public static readonly DependencyProperty ControllerStrokeProperty =
            DependencyProperty.Register("ControllerStroke", typeof(Brush), typeof(ControllerCanvas), new FrameworkPropertyMetadata(Brushes.Red,
                FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));

        public double ControllerStrokeThickness
        {
            get { return (double)GetValue(ControllerStrokeThicknessProperty); }
            set { SetValue(ControllerStrokeThicknessProperty, value); }
        }

        public static readonly DependencyProperty ControllerStrokeThicknessProperty =
            DependencyProperty.Register("ControllerStrokeThickness", typeof(double), typeof(ControllerCanvas), new FrameworkPropertyMetadata(1.0,
                FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));

        public Brush GridStroke
        {
            get { return (Brush)GetValue(GridStrokeProperty); }
            set { SetValue(GridStrokeProperty, value); }
        }

        public static readonly DependencyProperty GridStrokeProperty =
            DependencyProperty.Register("GridStroke", typeof(Brush), typeof(ControllerCanvas), new FrameworkPropertyMetadata(Brushes.LightGray,
                FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));

        public double GridStrokeThickness
        {
            get { return (double)GetValue(GridStrokeThicknessProperty); }
            set { SetValue(GridStrokeThicknessProperty, value); }
        }

        public static readonly DependencyProperty GridStrokeThicknessProperty =
            DependencyProperty.Register("GridStrokeThickness", typeof(double), typeof(ControllerCanvas), new FrameworkPropertyMetadata(1.0,
                FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));

        public bool GridVisible
        {
            get { return (bool)GetValue(GridVisibleProperty); }
            set { SetValue(GridVisibleProperty, value); }
        }

        public static readonly DependencyProperty GridVisibleProperty =
            DependencyProperty.Register("GridVisible", typeof(bool), typeof(ControllerCanvas), new FrameworkPropertyMetadata(false,
                FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));

        public Thickness GridMargin
        {
            get { return (Thickness)GetValue(GridMarginProperty); }
            set { SetValue(GridMarginProperty, value); }
        }

        public static readonly DependencyProperty GridMarginProperty =
            DependencyProperty.Register("GridMargin", typeof(Thickness), typeof(ControllerCanvas), new FrameworkPropertyMetadata(new Thickness(0.0, 0.0, 0.0, 0.0),
                FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));

        public double GridSize
        {
            get { return (double)GetValue(GridSizeProperty); }
            set { SetValue(GridSizeProperty, value); }
        }

        public static readonly DependencyProperty GridSizeProperty =
            DependencyProperty.Register("GridSize", typeof(double), typeof(ControllerCanvas), new FrameworkPropertyMetadata(30.0,
                FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));

        #endregion

        #region Drawing Context

        Pen penGrid = null;
        Pen penController = null;

        Brush previousGridStroke = null;
        double previousGridStrokeThickness = double.NaN;
        double penGridHalfThickness = double.NaN;

        Brush previousControllerStroke = null;
        double previousControllerStrokeThickness = double.NaN;
        double penControllerHalfThickness = double.NaN;

        Point p1 = new Point();
        Point p2 = new Point();
        Point p3 = new Point();
        Point p4 = new Point();

        double width = double.NaN;
        double height = double.NaN;

        void DrawGrid(DrawingContext dc)
        {
            width = this.ActualWidth;
            height = this.ActualHeight;

            // draw vertical grid lines
            for (double y = GridMargin.Top; y <= height - GridMargin.Bottom; y += GridSize)
            {
                p1.X = GridMargin.Left;
                p1.Y = y;
                p2.X = width - GridMargin.Right;
                p2.Y = y;

                GuidelineSet g = new GuidelineSet();
                g.GuidelinesX.Add(p1.X + penGridHalfThickness);
                g.GuidelinesX.Add(p2.X + penGridHalfThickness);
                g.GuidelinesY.Add(p1.Y + penGridHalfThickness);
                g.GuidelinesY.Add(p2.Y + penGridHalfThickness);
                dc.PushGuidelineSet(g);
                dc.DrawLine(penGrid, p1, p2);
                dc.Pop();
            }

            // draw horizontal grid lines
            for (double x = GridMargin.Left; x <= width - GridMargin.Right; x += GridSize)
            {
                p1.X = x;
                p1.Y = GridMargin.Top;
                p2.X = x;
                p2.Y = height - GridMargin.Bottom;

                GuidelineSet g = new GuidelineSet();
                g.GuidelinesX.Add(p1.X + penGridHalfThickness);
                g.GuidelinesX.Add(p2.X + penGridHalfThickness);
                g.GuidelinesY.Add(p1.Y + penGridHalfThickness);
                g.GuidelinesY.Add(p2.Y + penGridHalfThickness);
                dc.PushGuidelineSet(g);
                dc.DrawLine(penGrid, p1, p2);
                dc.Pop();
            }
        }

        void DrawController(DrawingContext dc)
        {
            width = this.ActualWidth;
            height = this.ActualHeight;

            // draw vertical controller line
            p1.X = 0.0;
            p1.Y = Point.Y;
            p2.X = width;
            p2.Y = Point.Y;

            GuidelineSet g1 = new GuidelineSet();
            g1.GuidelinesX.Add(p1.X + penControllerHalfThickness);
            g1.GuidelinesX.Add(p2.X + penControllerHalfThickness);
            g1.GuidelinesY.Add(p1.Y + penControllerHalfThickness);
            g1.GuidelinesY.Add(p2.Y + penControllerHalfThickness);
            dc.PushGuidelineSet(g1);
            dc.DrawLine(penController, p1, p2);
            dc.Pop();

            // draw horizontal controller line
            p3.X = Point.X;
            p3.Y = 0.0;
            p4.X = Point.X;
            p4.Y = height;

            GuidelineSet g2 = new GuidelineSet();
            g2.GuidelinesX.Add(p3.X + penControllerHalfThickness);
            g2.GuidelinesX.Add(p4.X + penControllerHalfThickness);
            g2.GuidelinesY.Add(p3.Y + penControllerHalfThickness);
            g2.GuidelinesY.Add(p4.Y + penControllerHalfThickness);
            dc.PushGuidelineSet(g2);
            dc.DrawLine(penController, p3, p4);
            dc.Pop();
        }

        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);

            // create ord update grid pen
            if (penGrid == null)
            {
                penGrid = new Pen(GridStroke, GridStrokeThickness);

                previousGridStroke = GridStroke;
                previousGridStrokeThickness = GridStrokeThickness;

                penGridHalfThickness = penGrid.Thickness / 2.0;
            }
            else
            {
                if (GridStroke != previousGridStroke || GridStrokeThickness != previousGridStrokeThickness)
                {
                    previousGridStroke = GridStroke;
                    previousGridStrokeThickness = GridStrokeThickness;
                    penGrid.Brush = GridStroke;
                    penGrid.Thickness = GridStrokeThickness;

                    penGridHalfThickness = penGrid.Thickness / 2.0;
                }
            }

            // create ord update controller pen
            if (penController == null)
            {
                penController = new Pen(ControllerStroke, ControllerStrokeThickness);

                previousControllerStroke = ControllerStroke;
                previousControllerStrokeThickness = ControllerStrokeThickness;

                penControllerHalfThickness = penController.Thickness / 2.0;
            }
            else
            {
                if (ControllerStroke != previousControllerStroke || ControllerStrokeThickness != previousControllerStrokeThickness)
                {
                    previousControllerStroke = ControllerStroke;
                    previousControllerStrokeThickness = ControllerStrokeThickness;
                    penController.Brush = ControllerStroke;
                    penController.Thickness = ControllerStrokeThickness;

                    penControllerHalfThickness = penController.Thickness / 2.0;
                }
            }

            // drag grid
            if (GridVisible)
            {
                DrawGrid(dc);
            }

            // draw controller
            DrawController(dc);
        }

        #endregion

        #region Mouse Events

        protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            if (!this.IsMouseCaptured)
            {
                this.Point = e.GetPosition(this);

                this.Cursor = Cursors.Hand;
                this.CaptureMouse();
            }

            base.OnMouseLeftButtonDown(e);
        }

        protected override void OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e)
        {
            if (this.IsMouseCaptured)
            {
                this.Cursor = Cursors.Arrow;
                this.ReleaseMouseCapture();
            }

            base.OnMouseLeftButtonUp(e);
        }

        protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
        {
            if (this.IsMouseCaptured)
            {
                this.Point = e.GetPosition(this);
            }

            base.OnMouseMove(e);
        }

        #endregion
    }
}

MainWindow.xaml

<Window x:Class="XYControllerDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:XYControllerDemo"
        Title="XYControllerDemo" Height="410" Width="680">
    <Grid>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50*"/>
            <ColumnDefinition Width="50*"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <local:ControllerCanvas x:Name="controller1" 
                                Margin="10" Grid.Column="0" Grid.Row="0" 
                                Background="Transparent" Width="300" Height="300" 
                                GridMargin="0,0,0,0" GridVisible="True" GridSize="30" 
                                GridStroke="LightGray" GridStrokeThickness="1.0"
                                ControllerStroke="Red" ControllerStrokeThickness="1.0"
                                Point="50,50"/>

        <TextBox Grid.Row="1" Grid.Column="0" Margin="10" Width="100" 
                 Text="{Binding ElementName=controller1, Path=Point, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

        <local:ControllerCanvas x:Name="controller2" 
                                Margin="10" Grid.Column="1" Grid.Row="0" 
                                Background="Transparent" Width="300" Height="300" 
                                GridMargin="0,0,0,0" GridVisible="True" GridSize="30" 
                                GridStroke="LightGray" GridStrokeThickness="1.0"
                                ControllerStroke="Blue" ControllerStrokeThickness="1.0"
                                Point="90,250"/>

        <TextBox Grid.Row="1" Grid.Column="1" Margin="10" Width="100" 
                 Text="{Binding ElementName=controller2, Path=Point, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace XYControllerDemo
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接