WPF实现自定义曲线视图

准备

开发环境:VisualStudio2019;程序类型:WPF应用程序。

布局

首先在项目里新建一个UserControl用户控件,命名为CurveView。简单布下局,界面分两行,上面一行固定高50px,放工具栏,下面一行放曲线的绘制区域。
代码如下:

<UserControl x:Class="CustomCurveView.CurveView"
             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:CustomCurveView"
             mc:Ignorable="d" 
             d:DesignHeight="600" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

        <ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden">
            <ToolBar>
                <Label FontSize="14" Height="30" VerticalContentAlignment="Center">X轴坐标间距:</Label>
                <TextBox x:Name="txt_SpanX" BorderBrush="Blue" Width="40" Height="30" FontSize="14" VerticalContentAlignment="Center">1</TextBox>
                <Separator Background="Purple"></Separator>
                <Label Margin="2,0,0,0" FontSize="14" Height="30" VerticalContentAlignment="Center">Y轴坐标间距:</Label>
                <TextBox x:Name="txt_SpanY" BorderBrush="Blue" Width="40" Height="30" FontSize="14" VerticalContentAlignment="Center">1</TextBox>
                <Separator Background="Purple"></Separator>
                <Label Margin="2,0,0,0" FontSize="14" Height="30" VerticalContentAlignment="Center">X轴最小最大值:</Label>
                <TextBox x:Name="txt_MinX" BorderBrush="Blue" Width="40" Height="30" FontSize="14" VerticalContentAlignment="Center">0</TextBox>
                <TextBox Margin="2,0,0,0" BorderBrush="Blue" x:Name="txt_MaxX" Width="40" Height="30" FontSize="14" VerticalContentAlignment="Center">10</TextBox>
                <Separator Background="Purple"></Separator>
                <Label Margin="2,0,0,0" FontSize="14" Height="30" VerticalContentAlignment="Center">Y轴最小最大值:</Label>
                <TextBox x:Name="txt_MinY" BorderBrush="Blue" Width="40" Height="30" FontSize="14" VerticalContentAlignment="Center">0</TextBox>
                <TextBox Margin="2,0,0,0" BorderBrush="Blue" x:Name="txt_MaxY" Width="40" Height="30" FontSize="14" VerticalContentAlignment="Center">10</TextBox>
                <Separator Background="Purple"></Separator>
                <Button Margin="5,0,0,0" Background="SkyBlue" x:Name="btn_Setting" Width="35" Height="25" 
                        FontSize="14" Click="btn_Setting_Click">确定</Button>
                <Button Margin="5,0,0,0" Background="SkyBlue" x:Name="btn_Reset" Width="35" Height="25" 
                        FontSize="14" Click="btn_Reset_Click">复位</Button>
            </ToolBar>
        </ScrollViewer>

        <ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
            <Canvas x:Name="cv_CurveRegion" Width="800" Height="550" Background="LightGray" ClipToBounds="True"
                    MouseDown="cv_CurveRegion_MouseDown" MouseMove="cv_CurveRegion_MouseMove"
                    SizeChanged="cv_CurveRegion_SizeChanged">
            </Canvas>
        </ScrollViewer>
    </Grid>
</UserControl>

布局效果如下:
曲线视图

定义外部可设的变量及全局变量

这里我们思考一下具体情况,对于我们现在做的CurveView自定义用户控件而言,当外部使用此控件时,需要设置的主要参数为:x轴与y轴的坐标间距、坐标最小最大值,以及描绘曲线的点集合。
定义外部可设变量代码如下:

		#region External settable variables.
        /// <summary>
        /// X轴坐标间距
        /// </summary>
        public double SpanX { get; set; }

        /// <summary>
        /// Y轴坐标间距
        /// </summary>
        public double SpanY { get; set; }

        /// <summary>
        /// X轴坐标最小值
        /// </summary>
        public double MinX { get; set; }

        /// <summary>
        /// X轴坐标最大值
        /// </summary>
        public double MaxX { get; set; }

        /// <summary>
        /// Y轴坐标最小值
        /// </summary>
        public double MinY { get; set; }

        /// <summary>
        /// Y轴坐标最大值
        /// </summary>
        public double MaxY { get; set; }

        /// <summary>
        /// 曲线上的点
        /// </summary>
        public PointCollection CurvePoints { get; set; }
        #endregion

全局需要使用的变量有:曲线区域的起点及终点坐标、曲线区域宽度及高度、坐标原点在Canvas里的位置坐标、开始移动的坐标。
定义全局变量的代码如下:

		private Point regionStartPosition;  //曲线区域起点坐标
        private Point regionEndPosition;  //曲线区域终点坐标
        private double regionWidth;  //曲线区域宽度
        private double regionHeight;  //曲线区域高度
        private double zeroPositionX = 0;  //原点的位置X
        private double zeroPositionY = 0;  //原点的位置Y
        private Point startMovePosition;  //鼠标按下的位置,即开始移动的位置

初始化曲线区域

初始化曲线区域,主要就是初始设置曲线区域的宽高以及区域起点与终点坐标。代码如下:

        /// <summary>
        /// 初始化曲线区域
        /// </summary>
        private void InitCurveRegion()
        {
            double boardWidth = cv_CurveRegion.Width;
            double boardHeight = cv_CurveRegion.Height;
            double boardMargin = 40;
            regionWidth = boardWidth - 2 * boardMargin;
            regionHeight = boardHeight - 2 * boardMargin;
            regionStartPosition = new Point(boardMargin, boardHeight - boardMargin);
            regionEndPosition = new Point(boardWidth - boardMargin, boardMargin);

            CurvePoints = new PointCollection();
        }

画坐标轴

接下来我们在曲线区域画出坐标轴,也就是x轴与y轴的两条线。代码如下:

        /// <summary>
        /// 画坐标轴
        /// </summary>
        private void DrawAxis()
        {
            var xAxis = new Line
            {
                X1 = regionStartPosition.X,
                Y1 = regionStartPosition.Y,
                X2 = regionStartPosition.X,
                Y2 = regionEndPosition.Y,
                Stroke = new SolidColorBrush(Colors.Black)
            };
            cv_CurveRegion.Children.Add(xAxis);

            var yAxis = new Line
            {
                X1 = regionStartPosition.X,
                Y1 = regionStartPosition.Y,
                X2 = regionEndPosition.X,
                Y2 = regionStartPosition.Y,
                Stroke = new SolidColorBrush(Colors.Black)
            };
            cv_CurveRegion.Children.Add(yAxis);
        }

画出x轴与y轴坐标点

下面要把x轴与y轴完整画出来,主要分三部分,刻度线、刻度标记、网格线。相关代码如下:

        /// <summary>
        /// 画X轴坐标点
        /// </summary>
        private void DrawPointsForAxisX()
        {
            if (MinX >= MaxX) return;
            if (SpanX < 1) SpanX = 1;

            for (double i = MinX; i <= MaxX; i += SpanX)
            {
                double x = regionStartPosition.X + i * regionWidth / (MaxX - MinX) + zeroPositionX;
                var markLine = new Line
                {
                    X1 = x,
                    Y1 = regionStartPosition.Y,
                    X2 = x,
                    Y2 = regionStartPosition.Y + 5,
                    Stroke = Brushes.Red
                };
                cv_CurveRegion.Children.Add(markLine);

                if (i > MinX)
                {
                    var gridLine = new Line
                    {
                        X1 = x,
                        Y1 = regionStartPosition.Y,
                        X2 = x,
                        Y2 = regionEndPosition.Y,
                        StrokeThickness = 1,
                        Stroke = new SolidColorBrush(Colors.ForestGreen)
                    };
                    cv_CurveRegion.Children.Add(gridLine);
                }
                
                var markText = new TextBlock
                {
                    Text = i.ToString(),
                    Width = 80,
                    Foreground = Brushes.Black,
                    VerticalAlignment = VerticalAlignment.Top,
                    HorizontalAlignment = HorizontalAlignment.Stretch,
                    TextAlignment = TextAlignment.Left,
                    FontSize = 15
                };

                cv_CurveRegion.Children.Add(markText);
                Canvas.SetTop(markText, regionStartPosition.Y + 5);
                Canvas.SetLeft(markText, x);
            }
        }

        /// <summary>
        /// 画Y轴坐标点
        /// </summary>
        private void DrawPointsForAxisY()
        {
            if (MinY >= MaxY) return;
            if (SpanY < 1) SpanY = 1;

            for (double i = MinY; i <= MaxY; i += SpanY)
            {
                double y = regionStartPosition.Y - i * regionHeight / (MaxY - MinY) + zeroPositionY;
                var markLine = new Line
                {
                    X1 = regionStartPosition.X - 5,
                    Y1 = y,
                    X2 = regionStartPosition.X,
                    Y2 = y,
                    Stroke = Brushes.Red
                };
                cv_CurveRegion.Children.Add(markLine);

                if (i > MinY)
                {
                    var gridLine = new Line
                    {
                        X1 = regionStartPosition.X,
                        Y1 = y,
                        X2 = regionEndPosition.X,
                        Y2 = y,
                        StrokeThickness = 1,
                        Stroke = new SolidColorBrush(Colors.ForestGreen)
                    };
                    cv_CurveRegion.Children.Add(gridLine);
                }
                
                var markText = new TextBlock
                {
                    Text = i.ToString(),
                    Width = 30,
                    Foreground = Brushes.Black,
                    HorizontalAlignment = HorizontalAlignment.Right,
                    TextAlignment = TextAlignment.Right,
                    FontSize = 15
                };

                cv_CurveRegion.Children.Add(markText);
                Canvas.SetTop(markText, y - 10);
                Canvas.SetLeft(markText, 0);
            }
        }

画曲线及点

现在坐标轴都有了,那么可以画曲线和具体的点了。代码如下:

        /// <summary>
        /// 画曲线点
        /// </summary>
        private void DrawCurvePoints()
        {
            var polyline = new Polyline();
            foreach (var point in CurvePoints)
            {
                Point realPoint = GetPointRealPosition(point);
                Ellipse ellipse = new Ellipse();
                ellipse.Width = 6;
                ellipse.Height = 6;
                ellipse.Margin = new Thickness(realPoint.X - 3, realPoint.Y - 3, 0, 0);
                ellipse.Stroke = new SolidColorBrush(Colors.Black);
                ellipse.Fill = Brushes.Black;
                cv_CurveRegion.Children.Add(ellipse);
                polyline.Points.Add(realPoint);
            }
            polyline.Stroke = Brushes.Blue;
            cv_CurveRegion.Children.Add(polyline);
        }

        /// <summary>
        /// 得到点在Canvas里的真实位置
        /// </summary>
        /// <param name="point">传入的点</param>
        /// <returns></returns>
        private Point GetPointRealPosition(Point point)
        {
            var realX = regionStartPosition.X + (point.X - MinX) * regionWidth / (MaxX - MinX) + zeroPositionX;
            var realY = regionEndPosition.Y + (MaxY - point.Y) * regionHeight / (MaxY - MinY) + zeroPositionY;
            return new Point(realX, realY);
        }

这里说明一下,因为我们传入点的坐标是相对于真实的坐标系,及数学上的二维坐标系而言。而我们呈现在界面上需要的是点在界面上的位置,所以这里写了GetPointRealPosition函数对点的坐标做了转换。

刷新曲线视图

由于我们的曲线一些参数是可以自设的,所以我们要根据相关参数实现曲线的重新绘制。代码如下:

        /// <summary>
        /// 刷新曲线区域
        /// </summary>
        private void RefreshCurveRegion()
        {
            cv_CurveRegion.Children.Clear();

            //获取x最大值
            if (MaxX < 1)
            {
                MaxX = CurvePoints.Max(p => p.X);
            }

            //获取y最大值
            if (MaxY < 1)
            {
                MaxY = CurvePoints.Max(p => p.Y);
            }

            if (Math.Abs(MaxX) < 1 || Math.Abs(MaxY) < 1)
            {
                return;
            }

            DrawAxis();
            DrawPointsForAxisX();
            DrawPointsForAxisY();
            DrawCurvePoints();
        }

顺便提一下,这部分代码用到了Lambda表达式,有兴趣的朋友可以自行去学习下。

实现曲线的拖动

主要思路为:鼠标按下时记录开始移动的位置,拖动时记录当前拖动的位置、更新开始移动位置、计算原点的位置并刷新曲线区域界面。相关代码如下:

        /// <summary>
        /// Canvas的MouseDown事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cv_CurveRegion_MouseDown(object sender, MouseButtonEventArgs e)
        {
            startMovePosition = e.GetPosition((Canvas)sender);
        }

        /// <summary>
        /// Canvas的MouseMove事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cv_CurveRegion_MouseMove(object sender, MouseEventArgs e)
        {
            Point currentMousePosition = e.GetPosition((Canvas)sender);

            if (e.LeftButton == MouseButtonState.Pressed)
            {
                zeroPositionX = zeroPositionX + currentMousePosition.X - startMovePosition.X;
                zeroPositionY = zeroPositionY + currentMousePosition.Y - startMovePosition.Y;

                startMovePosition = currentMousePosition;

                RefreshCurveRegion();
            }
        }

        /// <summary>
        /// Canvas的SizeChanged事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cv_CurveRegion_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            RefreshCurveRegion();
        }

曲线设置与复位功能

最后,我们把界面上设定曲线参数和复位的功能实现。代码如下:

        /// <summary>
        /// 复位按钮点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Reset_Click(object sender, RoutedEventArgs e)
        {
            zeroPositionX = 0;
            zeroPositionY = 0;
            RefreshCurveRegion();
        }

        /// <summary>
        /// 确定按钮点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Setting_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                SpanX = Convert.ToDouble(txt_SpanX.Text);
                SpanY = Convert.ToDouble(txt_SpanY.Text);
                MinX = Convert.ToDouble(txt_MinX.Text);
                MaxX = Convert.ToDouble(txt_MaxX.Text);
                MinY = Convert.ToDouble(txt_MinY.Text);
                MaxY = Convert.ToDouble(txt_MaxY.Text);
            }
            catch (Exception ex)
            {
                MessageBox.Show("请检查相关输入!\n\r" + ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            RefreshCurveRegion();
        }

其他

我们把做完的CurveVIew控件引入到项目的主界面及MainWindow.xaml文件中,并初始化一些参数,然后就可以看到运行效果了。如图:
引入曲线视图运行
本程序的源码,有需要的朋友可以点此下载

发布了10 篇原创文章 · 获赞 0 · 访问量 610
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览