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;
        }
    }

}
相关推荐
玖笙&2 天前
✨WPF编程基础【2.1】布局原则
c++·wpf·visual studio
玖笙&2 天前
✨WPF编程基础【2.2】:布局面板实战
c++·wpf·visual studio
SEO-狼术2 天前
.NET WPF 数据编辑器集合提供列表框控件
.net·wpf
FuckPatience7 天前
WPF 具有跨线程功能的UI元素
wpf
诗仙&李白7 天前
HEFrame.WpfUI :一个现代化的 开源 WPF UI库
ui·开源·wpf
He BianGu7 天前
【笔记】在WPF中Binding里的详细功能介绍
笔记·wpf
He BianGu7 天前
【笔记】在WPF中 BulletDecorator 的功能、使用方式并对比 HeaderedContentControl 与常见 Panel 布局的区别
笔记·wpf
123梦野8 天前
WPF——效果和可视化对象
wpf
He BianGu8 天前
【笔记】在WPF中Decorator是什么以及何时优先考虑 Decorator 派生类
笔记·wpf
时光追逐者8 天前
一款专门为 WPF 打造的开源 Office 风格用户界面控件库
ui·开源·c#·.net·wpf