幾何繪圖的部分差不多理解後,這一篇就要來完成整個控制項的設計。
Arc class 一些必要的修改
前面的繪圖其實有幾個缺點,其一是我們設定的依賴屬性是角度,老實說這麼做也沒錯,但是套上控制項的時候由於是進度,控制項對外的屬性應該會設定為百分比值。如果 Arc 用角度,就難免要寫個值轉換器來做百分比值和角度的轉換,我這人通常嫌麻煩,所以直接在 Arc 上就直接設定百分比值的依賴屬性。
第二個問題是比較嚴重的,前面的設計在動態改變屬性值的時候並不會重新渲染,所以要在中繼資料設定一個 FrameworkPropertyMetadataOptions.AffectsRender 的列舉旗標,讓屬性值變更時會重新渲染畫面。
依此要領,先移除 EndAngleProperty 相關程式碼,補上 PercentProperty :
public static readonly DependencyProperty PercentProperty =
DependencyProperty.Register(nameof(Percent), typeof(double), typeof(Arc), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender, null, new CoerceValueCallback(OnPercentChanged)));
private static object OnPercentChanged(DependencyObject d, object baseValue)
{
var value = (double)baseValue;
if (value < 0) { value = 0; }
if (value > 100) { value = 100; }
return value;
}
public double Percent
{
get => (double)GetValue(PercentProperty);
set => SetValue(PercentProperty, value);
}
當然有一些相關程式碼也要異動:
private bool IsLargeArc()
{
return Percent > 50;
}
private double EndAngleToPolarAngle()
{
var endAngle = 360.0 / 100.0 * Percent;
if (endAngle > 359.9 ) { endAngle = 359.9; }
var result = 90.0 - endAngle;
if (result < 0) { result += 360.0; }
return result;
}
設計控制項
這個控制項的範本使用 Arc class 來表達進度,最基礎的依賴屬性需求大概就是 Percent、Stroke 和 StrokeThickness,如果有其他的需求也可以自己補上。其中還會放置一個 ContentPresenter,可以讓使用者自由擺些甚麼其他的東西在裡面:
public class ProgressCircle : ContentControl
{
public static readonly DependencyProperty PercentProperty =
DependencyProperty.Register(nameof(Percent), typeof(double), typeof(ProgressCircle), new PropertyMetadata(0.0));
public double Percent
{
get => (double)GetValue(PercentProperty);
set => SetValue(PercentProperty, value);
}
public static readonly DependencyProperty StrokeProperty =
DependencyProperty.Register(nameof(Stroke), typeof(Brush), typeof(ProgressCircle), new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public Brush Stroke
{
get => (Brush)GetValue(StrokeProperty);
set => SetValue(StrokeProperty, value);
}
public static readonly DependencyProperty StrokeThicknessProperty =
DependencyProperty.Register(nameof(StrokeThickness), typeof(double), typeof(ProgressCircle), new PropertyMetadata(1.0));
public double StrokeThickness
{
get => (double)GetValue(StrokeThicknessProperty);
set => SetValue(StrokeThicknessProperty, value);
}
static ProgressCircle()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ProgressCircle), new FrameworkPropertyMetadata(typeof(ProgressCircle)));
}
}
<Style TargetType="{x:Type local:ProgressCircle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ProgressCircle}">
<Grid>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<local:Arc Percent="{TemplateBinding Percent}"
Stroke="{TemplateBinding Stroke}"
StrokeThickness="{TemplateBinding StrokeThickness}"/>
</Border>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="{TemplateBinding Padding}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
展示用的程式碼很簡單:
<Window x:Class="ProgressCircleSample.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:ProgressCircleSample"
xmlns:p="clr-namespace:ProgressCircleLibrary;assembly=ProgressCircleLibrary"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<StackPanel Orientation="Vertical" >
<p:ProgressCircle Percent="{Binding Percent}" Stroke="Brown" StrokeThickness="10"
Width="100" Height="100" Background="AliceBlue"
Content="{Binding Percent}"/>
<Button Margin="12" Content="Test" Command="{Binding TestProgress}"/>
<Button Margin="12" Content="Rest" Command="{Binding Reset}"/>
</StackPanel>
</Window>
public class MainViewModel : NotifyPropertyBase
{
private double percent;
public double Percent
{
get => percent;
set => SetProperty(ref percent, value);
}
public ICommand Reset
{
get
{
return new RelayCommand((x) => Percent = 0);
}
}
public ICommand TestProgress
{
get
{
return new RelayCommand(async (x) =>
{
for(int i = 0; i < 101; i++)
{
Percent = i;
await Task.Delay(100);
}
});
}
}
}