【WPF】环形进度条

版权声明:本文为博主原创文章,转载注明出处 https://blog.csdn.net/u010875635/article/details/84197393

WPF中自带有长条形的进度条,大部分场景都算适用,但是仍然有一部分空间小的场景不太合适,此时我们想到安卓上常用的环形进度条,美观,又不占空间。

那么WPF中的环形进度条控件在哪呢?

很遗憾,自带组件中没有,这需要我们自己绘制。

环形进度条的核心在于根据百分比绘制弧形长度,在WPF中,PathGeometry可以绘制复杂形状,其中ArcSegment专门用于画弧形。

示例工程直接下载:https://download.csdn.net/download/u010875635/10791096

示例动画:

示例动画

 

ArcSegment的弧形有几个重要参数,起点(一般在PathFigure中设置,ArcSegment属于PathFigure一部分)、两个半径是否优势弧(大于180度为优势弧)、终点旋转角度等。

我们根据角度百分比计算弧长终点,由于弧长虽然是线性增长,但对应的终点X、Y坐标并非是线性变化,我们需要区分4个象限来计算终点。

我们将弧形放置在一个方形的Grid中,半边长为R,设置弧形进度条的粗细为x,设置弧形半径为r(r要小于R-x),起始点坐标为 (leftStart, topStart)

一、第一象限终点计算


                         
 

第一象限终点坐标为:X = leftStart + r*cos(α);  Y = topStart + r*sin(α);

 

二、第二象限终点计算

第二象限终点坐标为:X = leftStart + r*cos(α);  Y = topStart + r + r*sin(α);

 

三、第三象限终点计算

第三象限终点坐标为:X = leftStart  - r*sin(α);  Y = topStart + r + r*cos(α);

 

四、第四象限终点计算

第四象限终点坐标为:X = leftStart  - r*cos(α);  Y = topStart + r - r*sin(α);

注意100%时,终点与起点不要重合,最好偏移极小数值(太大弧形不好看),然后闭合图形,否则无法组合成圆。

 

详细代码如下:

一、自定义控件

前台:

<UserControl x:Class="BeatfanControls.ProcessBars.CycleProcessBar1"
             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:BeatfanControls.ProcessBars"
             mc:Ignorable="d" >
    <Viewbox>
        <Grid Width="34" Height="34">
            <Path Name="myCycleProcessBar1" Data="M17,3 A14,14 0 0 1 16,3 " Stroke="Green" StrokeThickness="3" Height="34" Width="34" VerticalAlignment="Center" HorizontalAlignment="Center">
            </Path>
            <Label Name="lbValue" Content="50%" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="9" />
        </Grid>
    </Viewbox>
</UserControl>

后台:

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 BeatfanControls.ProcessBars
{
    /// <summary>
    /// CycleProcessBar1.xaml 的交互逻辑
    /// </summary>
    public partial class CycleProcessBar1 : UserControl
    {
        public CycleProcessBar1()
        {
            InitializeComponent();
        }

        public double CurrentValue1
        {
            set { SetValue(value); }
        }

        /// <summary>
        /// 设置百分百,输入小数,自动乘100
        /// </summary>
        /// <param name="percentValue"></param>
        private void SetValue(double percentValue)
        {
            /*****************************************
              方形矩阵边长为34,半长为17
              环形半径为14,所以距离边框3个像素
              环形描边3个像素
            ******************************************/
            double angel = percentValue * 360; //角度

            

            double radius = 14; //环形半径

            //起始点
            double leftStart = 17;
            double topStart = 3;

            //结束点
            double endLeft = 0;
            double endTop = 0;

            

            //数字显示
            lbValue.Content = (percentValue*100).ToString("0") + "%";

            /***********************************************
            * 整个环形进度条使用Path来绘制,采用三角函数来计算
            * 环形根据角度来分别绘制,以90度划分,方便计算比例
            ***********************************************/

            bool isLagreCircle = false; //是否优势弧,即大于180度的弧形

            //小于90度
            if (angel <= 90)
            {
                /*****************
                          *
                          *   *
                          * * ra
                   * * * * * * * * *
                          *
                          *
                          *
                ******************/
                double ra = (90 - angel) * Math.PI / 180; //弧度
                endLeft = leftStart + Math.Cos(ra) * radius; //余弦横坐标
                endTop = topStart + radius - Math.Sin(ra) * radius; //正弦纵坐标
                
            }

            else if (angel <= 180)
            {
                /*****************
                          *
                          *  
                          * 
                   * * * * * * * * *
                          * * ra
                          *  *
                          *   *
                ******************/
                
                double ra = (angel - 90) * Math.PI / 180; //弧度
                endLeft = leftStart + Math.Cos(ra) * radius; //余弦横坐标
                endTop = topStart + radius + Math.Sin(ra) * radius;//正弦纵坐标
            }

            else if (angel <= 270)
            {
                /*****************
                          *
                          *  
                          * 
                   * * * * * * * * *
                        * *
                       *ra*
                      *   *
                ******************/
                isLagreCircle = true; //优势弧
                double ra = (angel - 180) * Math.PI / 180;
                endLeft = leftStart - Math.Sin(ra) * radius;
                endTop = topStart + radius + Math.Cos(ra) * radius;
            }

            else if (angel < 360)
            {
                /*****************
                      *   *
                       *  *  
                     ra * * 
                   * * * * * * * * *
                          *
                          *
                          *
                ******************/
                isLagreCircle = true; //优势弧
                double ra = (angel - 270) * Math.PI / 180;
                endLeft = leftStart - Math.Cos(ra) * radius;
                endTop = topStart + radius - Math.Sin(ra) * radius;
            }
            else
            {
                isLagreCircle = true; //优势弧
                endLeft = leftStart-0.001; //不与起点在同一点,避免重叠绘制出非环形
                endTop = topStart;
            }

            Point arcEndPt = new Point(endLeft, endTop); //结束点
            Size arcSize = new Size(radius, radius);
            SweepDirection direction = SweepDirection.Clockwise; //顺时针弧形
            //弧形
            ArcSegment arcsegment = new ArcSegment(arcEndPt, arcSize, 0, isLagreCircle, direction, true);

            //形状集合
            PathSegmentCollection pathsegmentCollection = new PathSegmentCollection();
            pathsegmentCollection.Add(arcsegment);

            //路径描述
            PathFigure pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(leftStart, topStart); //起始地址
            pathFigure.Segments = pathsegmentCollection;

            //路径描述集合
            PathFigureCollection pathFigureCollection = new PathFigureCollection();
            pathFigureCollection.Add(pathFigure);

            //复杂形状
            PathGeometry pathGeometry = new PathGeometry();
            pathGeometry.Figures = pathFigureCollection;

            //Data赋值
            myCycleProcessBar1.Data = pathGeometry;
            //达到100%则闭合整个
            if (angel == 360)
                myCycleProcessBar1.Data = Geometry.Parse(myCycleProcessBar1.Data.ToString() + " z");
        }
    }
}

 

二、调用:

前台:

<Window x:Class="CircleProgressBar.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:CircleProgressBar"
        xmlns:MyProgress="clr-namespace:BeatfanControls.ProcessBars"
        mc:Ignorable="d" Closing="Window_Closing"
        Title="环形进度条" Height="350" Width="525">
    <Grid >
        <Grid.RowDefinitions>
            <RowDefinition Height="50*"/>
            <RowDefinition Height="10*"/>
        </Grid.RowDefinitions>
        <MyProgress:CycleProcessBar1 x:Name="circleProgressBar" CurrentValue1="0.9" />
        <Button Name="btnStart" Content="开始" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Click="Button_Click" />
    </Grid>
</Window>

后台:

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 CircleProgressBar
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// 定时器
        /// </summary>
        private System.Windows.Threading.DispatcherTimer m_Timer1 = new System.Windows.Threading.DispatcherTimer();

        double m_Percent = 0;
        bool m_IsStart = false;

        public MainWindow()
        {
            InitializeComponent();
            m_Timer1.Interval = new TimeSpan(0, 0, 0,0,100);
            m_Timer1.Tick += M_Timer1_Tick;
        }

        private void M_Timer1_Tick(object sender, EventArgs e)
        {
            m_Percent += 0.01;
            if (m_Percent > 1)
            {
                m_Percent = 1;
                m_Timer1.Stop();
                m_IsStart = false;
                StartChange(m_IsStart);
            }
            circleProgressBar.CurrentValue1 = m_Percent;
        }

        /// <summary>
        /// UI变化
        /// </summary>
        /// <param name="bState"></param>
        private void StartChange(bool bState)
        {
            if (bState)
                btnStart.Content = "停止";
            else
                btnStart.Content = "开始";
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (m_IsStart)
            {
                m_Timer1.Stop();
                m_IsStart = false;
                
            }
            else
            {
                m_Percent = 0;
                m_Timer1.Start();
                m_IsStart = true;

            }
            StartChange(m_IsStart);
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            m_Timer1.Stop();
        }
    }
}

 

 

 

猜你喜欢

转载自blog.csdn.net/u010875635/article/details/84197393