WPF 绘制过顶点的圆滑曲线(样条,贝塞尔)

项目中要用到样条曲线,必须过顶点,圆滑后还不能太走样,捣鼓一番,发现里面颇有玄机,于是把我多方抄来改造的方法发出来,方便新手:

如上图,看代码吧:


前台页面:

XML 复制代码
<Window x:Class="Wpf_north_demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Wpf_north_demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Canvas x:Name="ca1" Background="White" MouseLeftButtonDown="ca1_MouseLeftButtonDown" MouseMove="ca1_MouseMove" MouseRightButtonDown="ca1_MouseRightButtonDown">

            <Polyline x:Name="path_lines" Stroke="Silver" StrokeThickness="1" StrokeDashArray="1 1 1" IsHitTestVisible="False">
            </Polyline>

            <Path x:Name="path1" Stroke="Red" StrokeThickness="1" IsHitTestVisible="False">
                <Path.Data>
                    <PathGeometry x:Name="pathGeometry1">
                    </PathGeometry>
                </Path.Data>
            </Path>
            
            
        </Canvas>

        <Canvas x:Name="ca_top" IsHitTestVisible="False"/>

        <TextBlock  HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" Text="左键绘制,右键结束" IsHitTestVisible="False"/>
    </Grid>
</Window>

后台代码:

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 Wpf_north_demo
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            
        }

        int _num = 0;
        bool _started = false;
        List<Point> _seed = new List<Point>();
        private void ca1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if(!_started)
            {
                _num = 0;
                _seed.Clear();
                _started = true;
                ca_top.Children.Clear();
                path_lines.Points.Clear();
                pathGeometry1.Figures.Clear();
            }

            while (path_lines.Points.Count > _num && _num > 0)
            {
                path_lines.Points.RemoveAt(path_lines.Points.Count - 1);
            }

            _seed.Add(e.GetPosition(ca1));
            _num = _seed.Count;

            path_lines.Points.Add(_seed[_num - 1]);

            ca_top.Children.Add(new Ellipse
            {
                Width = 6,
                Height = 6,
                Stroke = Brushes.Blue,
                Fill = Brushes.Lime,
                Margin = new Thickness(_seed[_num - 1].X - 3, _seed[_num - 1].Y - 3, 0, 0)
            });
        }

        private void ca1_MouseMove(object sender, MouseEventArgs e)
        {
            if (_started && e.LeftButton == MouseButtonState.Released && _num > 0)
            {
                while (path_lines.Points.Count > _num)
                {
                    path_lines.Points.RemoveAt(path_lines.Points.Count - 1);
                }
                path_lines.Points.Add(e.GetPosition(ca1));
            }
        }

        private void ca1_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            if(_started)
            {
                while (path_lines.Points.Count > _num && _num > 0)
                {
                    path_lines.Points.RemoveAt(path_lines.Points.Count - 1);
                }

                _seed.Add(e.GetPosition(ca1));
                _num = _seed.Count;

                path_lines.Points.Add(_seed[_num - 1]);
                ca_top.Children.Add(new Ellipse
                {
                    Width = 6,
                    Height = 6,
                    Stroke = Brushes.Blue,
                    Fill = Brushes.Lime,
                    Margin = new Thickness(_seed[_num - 1].X - 3, _seed[_num - 1].Y - 3, 0, 0)
                });

                BezierHelper.DrawBezierPolyline(pathGeometry1, _seed, false);
            }
            else
            {
                _num = 0;
                _seed.Clear();
                ca_top.Children.Clear();
                path_lines.Points.Clear();
                pathGeometry1.Figures.Clear();
            }

            _started = false;
        }
       
    }

    public class BezierHelper
    {
        public static void DrawBezierPolyline(PathGeometry geo, List<Point> list, bool close)
        {
            geo.Figures.Clear();
            if (list.Count > 0)
            {
                PathFigure pf = new PathFigure() { IsClosed = close };

                pf.StartPoint = list[0];
                List<Point> controls = new List<Point>();
                for (int i = 0; i < list.Count; i++)
                {
                    Point control_01, control_02;
                    GetControlPoint(list, i, out control_01, out control_02);
                    controls.Add(control_01);
                    controls.Add(control_02);
                }

                for (int i = 1; i < list.Count; i++)
                {
                    BezierSegment bs = new BezierSegment(controls[i * 2 - 1], controls[i * 2], list[i], true);
                    bs.IsSmoothJoin = true;

                    pf.Segments.Add(bs);
                }
                geo.Figures.Add(pf);
            }
        }

        static void GetControlPoint(List<Point> list, int idx, out Point control_01, out Point control_02)
        {
            if (idx == 0)
            {
                control_01 = list[0];
            }
            else
            {
                control_01 = GetAverage(list[idx - 1], list[idx]);
            }
            if (idx == list.Count - 1)
            {
                control_02 = list[list.Count - 1];
            }
            else
            {
                control_02 = GetAverage(list[idx], list[idx + 1]);
            }
            Point ave = GetAverage(control_01, control_02);
            Point sh = Sub(list[idx], ave);
            control_01 = Mul(Add(control_01, sh), list[idx], 0.6);
            control_02 = Mul(Add(control_02, sh), list[idx], 0.6);
        }

        static Point GetAverage(Point x, Point y)
        {
            return new Point((x.X + y.X) / 2, (x.Y + y.Y) / 2);
        }

        static Point Add(Point x, Point y)
        {
            return new Point(x.X + y.X, x.Y + y.Y);
        }

        static Point Sub(Point x, Point y)
        {
            return new Point(x.X - y.X, x.Y - y.Y);
        }

        static Point Mul(Point x, Point y, double d)
        {
            Point temp = Sub(x, y);
            temp = new Point(temp.X * d, temp.Y * d);
            temp = Add(y, temp);
            return temp;
        }
    }

}
相关推荐
Zhen (Evan) Wang3 小时前
.NET 6 + Dapper + User-Defined Table Type
sqlserver·c#·.net·wpf
界面开发小八哥1 天前
界面控件DevExpress WPF v25.1预览 - 支持Windows 11系统强调色
windows·wpf·界面控件·devexpress·ui开发·.net 9
军训猫猫头1 天前
89.WPF 中实现便捷的数字输入框:DecimalUpDown 控件的使用 WPF例子 C#例子.
开发语言·c#·wpf
Pasregret1 天前
缓存与数据库一致性深度解析与解决方案
数据库·缓存·wpf
Java林间3 天前
Zookeeper是什么?基于zookeeper实现分布式锁
分布式·zookeeper·wpf
zizisuo3 天前
1.微服务拆分与通信模式
微服务·wpf
程序员秘密基地3 天前
基于c#,wpf,ef框架,sql server数据库,音乐播放器
sql·sqlserver·c#·.net·wpf
Zhen (Evan) Wang3 天前
.NET 6 WPF 利用CefSharp.Wpf.NETCore显示PDF文件
.net·wpf·.netcore
冰茶_4 天前
WPF特性分析
学习·microsoft·c#·wpf
qq_196055875 天前
最快打包WPF 应用程序
wpf