WPF随笔(三)--绘制可拖拽的线段

最近在项目中有这样一个需求,在界面上添加一条线段,可以拖拽移动两个端点来改变线段位置。对于这种需求,考虑采用自定义控件来做。

界面结构

这个控件的界面结构可以分解为一个画板、一条线段和两个端点,端点用于拖拽移动,线段用于连接端点,画板用于盛放端点和线段。由于在实际使用时,需要线段有一定的宽度,所以两个端点都是有一定的宽高,线段本身更像是长方形。

<UserControl x:Class="WPFDemo.Metro.MarkLine"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WPFDemo.Metro"
             mc:Ignorable="d" >
    <Canvas x:Name="cvs">
        <Polyline x:Name="lineMain" Stroke="Blue" StrokeThickness="2" Points="{Binding Path=DataPoints,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}}"></Polyline>
        <Thumb x:Name="thmStart" Canvas.Left="0" Canvas.Top="0" Cursor="Hand" IsEnabled="{Binding Path=IsEdit,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}}" DragDelta="thmStart_DragDelta">
            <Thumb.Template>
                <ControlTemplate>
                    <Ellipse Width="16" Height="16" Fill="Blue"></Ellipse>
                </ControlTemplate>
            </Thumb.Template>
        </Thumb>
        <Thumb x:Name="thmEnd" Canvas.Left="0" Canvas.Top="0" Cursor="Hand" IsEnabled="{Binding Path=IsEdit,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}}" DragDelta="thmEnd_DragDelta">
            <Thumb.Template>
                <ControlTemplate>
                    <Ellipse Width="16" Height="16" Fill="Red"></Ellipse>
                </ControlTemplate>
            </Thumb.Template>
        </Thumb>
    </Canvas>
</UserControl>

为了提高绘制效率,线段采用了PolyLine。因为两个端点在形式是宽高分别x的圆形,所以线段实际端点的位置应该是在圆形的圆心,相差(x/2,x/2)。这里为了明显x=16。
同时考虑到美观性,要把两个端点圆放在线段的上一层,确保端点圆能够遮住线段边缘的棱角。

数据结构

理论上只需要确定两个端点的位置,就能绘制出整条线段。因此数据结构只需要提供两个端点坐标即可。下面就为这个控件添加一个自定义属性。

        #region 端点数据集


        public PointCollection DataPoints
        {
            get { return (PointCollection)GetValue(DataPointsProperty); }
            set { SetValue(DataPointsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DataPoints.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataPointsProperty =
            DependencyProperty.Register("DataPoints", typeof(PointCollection), typeof(MarkLine), new PropertyMetadata(new PointCollection() { new Point(8, 8), new Point(16, 16) }, DataPointsChanged));

        private static void DataPointsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MarkLine line = (MarkLine)d;
            if (e.NewValue != e.OldValue)
            {
            	//数据集改变时重新绘制线段和设置端点圆形位置
                PointCollection points = (PointCollection)e.NewValue;
                Canvas.SetTop(line.thmStart, points[0].Y - 8);
                Canvas.SetLeft(line.thmStart, points[0].X - 8);
                Canvas.SetTop(line.thmEnd, points[1].Y - 8);
                Canvas.SetLeft(line.thmEnd, points[1].X - 8);
            }
        }


        #endregion

端点拖拽方法

线段两端的圆形使用的是可拖拽控件Thumb,拖拽功能的实现很简单,关键点在于拖拽过程中更改数据集并重新绘制线段。

        private PointCollection _points = new PointCollection();

        private void thmStart_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
        {
            Thumb thm = (Thumb)sender;
            double y = Canvas.GetTop(thm) + e.VerticalChange;
            double x = Canvas.GetLeft(thm) + e.HorizontalChange;
            Canvas.SetTop(thm, Canvas.GetTop(thm) + e.VerticalChange);
            Canvas.SetLeft(thm, Canvas.GetLeft(thm) + e.HorizontalChange);
            _points = this.DataPoints;
            this.DataPoints = new PointCollection()
            {
                new Point(x+thm.ActualWidth/2,y+thm.ActualHeight/2),
                _points[1]
            };
        }

        private void thmEnd_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
        {
            Thumb thm = (Thumb)sender;
            double y = Canvas.GetTop(thm) + e.VerticalChange;
            double x = Canvas.GetLeft(thm) + e.HorizontalChange;
            Canvas.SetTop(thm, Canvas.GetTop(thm) + e.VerticalChange);
            Canvas.SetLeft(thm, Canvas.GetLeft(thm) + e.HorizontalChange);
            _points = this.DataPoints;
            this.DataPoints = new PointCollection()
            {
                _points[0],
                new Point(x+thm.ActualWidth/2,y+thm.ActualHeight/2)
            };
        }

测试示例

编写测试数据,添加一条线段到前台。

        private void AddMarkLine()
        {
            MarkLine line = new MarkLine();
            line.DataPoints = new PointCollection()
            {
                new Point(30,24),
                new Point (160,180)
            };
            this.gridMain.Children.Add(line);
        }

保存编译并运行,最终呈现效果如下:
在这里插入图片描述


从上面的示例继续拓展,因为PolyLine是折线,修改数据集的结构,可以实现多端折线且每个端点均可以拖拽。

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值