[WPF] 製作進度環 (1) 基本弧形繪圖

  • 85
  • 0
  • 2021-09-22

自訂控制項樂趣多,這一回要來手工製作一個進度環。

WPF 框架裡雖然有個進度條控制項 – ProgressBar class,雖然透過修改控制項範本可以做出很帥的進度條,不過要做成環形的就比較棘手,我在 github 上見過有一個解法是透過繼承 ProgressBar 然後製作控制項範本利用動畫達成這樣的需求;但我想應該有其他的方式也能搞出來,所以就實驗了一些方法,其中包含了一些使用控制項範本、使用者控制項等等不同的方式。實驗了一段時間,我發現直接利用 2D 繪圖的方式最簡單又乾脆,就用幾篇文的篇幅就來介紹一下這是怎麼做到的。

整個過程最主要的部份是透過繼承 Shape class 定義出一個弧形,有人應該想到了,WPF 2D 繪圖本來就有可以畫弧形的類別 (ArcSegment class) 啊,幹嘛要自己重新定義呢?前面說過我做了很多實驗,用 Path 與 ArcSegment 不是不能做,但是整個程式碼反而變得複雜,因此我拋棄了那個做法。繼承 Shape 產生具體型別最需要注意的第一件事情就是要覆寫 Shape.DefiningGeometry 屬性,這個屬性就是用來定義 Shape 的外觀。

下一個課題就是不用 ArcSegment 要怎麼在那個屬性內部畫弧形?框架中有個建立幾何形狀的輕量級類別 – StreamGeometry class,或許你不常見到,但我們會用到它。藉由 StreamGeometry.Open 方法,傳回 StreamGeometryContext class 的物件,透過 StreamGeometryContext 一些基礎的繪圖方法創造出我們想要的幾何形狀。

StreamGeometryContext 基礎繪圖方法很簡單,從 BeginFigure 設定起點開始 ( 這可以類比為 xaml 中使用 PathGeometry 時候的 PathFigure ),接下來就是一大堆 To,甚麼 ArcTo、BezierTo、LineTo … 等等,大概就是對應那些 xxSegement 的類別用法。

依此要領,先簡單寫一個從 12 點鐘方向到  3 點鐘方向的弧形;假設,此弧形半徑為 (50,50) – 也就是封閉起來會成為一個正圓形, 12 點鐘起點為 (50,0),則三點鐘方向的終點應為 (100,50):

public class Arc : Shape
{
    protected override Geometry DefiningGeometry
    {
        get
        {
            StreamGeometry geometry = new StreamGeometry();
            using (StreamGeometryContext context = geometry.Open())
            {
                context.BeginFigure(new Point(50, 0), false, false);
                context.ArcTo(new Point(100, 50), new Size(50, 50), 0.0, false, SweepDirection.Clockwise, true, true);
            }
            return geometry;
        }
    }
}

在畫面上新增一個寬高為 100 的 Border ,如此正好可以放下一個半徑 50 的正圓形,將自訂的 Arc 放置於其中: 

<Border Width="100" Height="100" Background="LightBlue">
    <local:Arc Stroke="Brown" StrokeThickness="1"/>
</Border>

結果應該是這個樣子

圖1:(50,0) – (100,50) 順時鐘弧線
BeginFigure 方法說明:

這個方法有三個參數

第一個參數代表幾何圖形的起點。
第二個參數代表的是這個幾何圖形是否為一個整塊區域,使用 false 代表我們不會需要繪製一整個區域。
第三個參數則代表是否最後需要把終點連結回起點,形成封閉線條。

我們試著改變一下參數內容:

public class Arc : Shape
{
    protected override Geometry DefiningGeometry
    {
        get
        {
            StreamGeometry geometry = new StreamGeometry();
            using (StreamGeometryContext context = geometry.Open())
            {
                context.BeginFigure(new Point(50, 0), true, true);
                context.ArcTo(new Point(100, 50), new Size(50, 50), 0.0, false, SweepDirection.Clockwise, true, true);
            }

            return geometry;
        }
    }
}
<Border Width="100" Height="100" Background="LightBlue">
    <local:Arc Stroke="Brown" StrokeThickness="1" Fill="Yellow"/>
</Border>
圖2:isFilled = true and isClosed = true 

 

ArcTo 方法說明:

第一個參數基本上就是終點的位置。
第二個參數是一個 System.Windows.Size 的結構,在 Arc 中這代表的是 X軸與Y軸半徑 。
第三個參數是旋轉這個弧形,這個旋轉和 RotateTransform 不太一樣,要在 XY軸不相等的時候才會有作用,詳情可以參考 ArcSegment.RotationAngle 屬性的說明。
第四個參數設定 Large Arc,說白話就是你要畫一個從 A 點到 B 點的弧形,而這個弧形是要大於還是小於 180 度,以我們第一個例子來看,順時鐘畫一個九十度的弧形,所以這個參數會是 false,如果你直接把上面的範例的這個參數改為 true,那個弧形會跑到 Border 外面。
第五個參數則是設定順時鐘還是逆時鐘方向繪圖。
第六個參數表示是否繪製線條。
第七個參數則是多區段時的連結部分視為邊角,在我們這個例子不太重要。

大小弧和順逆時鐘的設定在這系列文章是比較重要的一部分,來稍微改一下這兩個參數,xaml 的部分保持和原來第一個範例相同:

public class Arc : Shape
{
    protected override Geometry DefiningGeometry
    {
        get
        {
            StreamGeometry geometry = new StreamGeometry();
            using (StreamGeometryContext context = geometry.Open())
            {
                context.BeginFigure(new Point(50, 0), false, false);
                context.ArcTo(new Point(100, 50), new Size(50, 50), 0.0, true, SweepDirection.Counterclockwise, true, true);
            }

            return geometry;
        }
    }
}
<Border Width="100" Height="100" Background="LightBlue">
    <local:Arc Stroke="Brown" StrokeThickness="1" />
</Border>
圖3:isLargeArc = true and 
SweepDirection = SweepDirection.Counterclockwise

基本的弧形繪圖說明到這邊,範例程式碼在此