我正在尝试将《WPF 4 Unleashed》一书中找到的饼图 ProgressBar 调整为看起来像甜甜圈。我觉得我已经成功了一半,但我不知道如何解决最后一个问题。
这是一张图片,说明了我想要的以及我已经实现的目标:
所以我的问题是,如何将其修复为我想要的样子?
以下是我正在使用的相关xaml:
<ControlTemplate x:Key="DonutProgressBar" TargetType="{x:Type ProgressBar}">
<ControlTemplate.Resources>
<conv:ValueMinMaxToIsLargeArcConverter x:Key="ValueMinMaxToIsLargeArcConverter" />
<conv:ValueMinMaxToPointConverter x:Key="ValueMinMaxToPointConverter" />
</ControlTemplate.Resources>
<Grid>
<Viewbox>
<Grid Width="20" Height="20">
<Ellipse x:Name="Background"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness.Top}"
Width="20"
Height="20"
Fill="{TemplateBinding Background}" />
<Path x:Name="Donut"
Stroke="{TemplateBinding Foreground}"
StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness.Top}">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="10,0">
<ArcSegment Size="10,10" SweepDirection="Clockwise">
<ArcSegment.Point>
<MultiBinding Converter="{StaticResource ValueMinMaxToPointConverter}">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
</MultiBinding>
</ArcSegment.Point>
<ArcSegment.IsLargeArc>
<MultiBinding Converter="{StaticResource ValueMinMaxToIsLargeArcConverter}">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
</MultiBinding>
</ArcSegment.IsLargeArc>
</ArcSegment>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
</Viewbox>
</Grid>
</ControlTemplate>
...
<ProgressBar Width="70" Height="70" Value="40" Template="{StaticResource DonutProgressBar}" Background="{x:Null}" BorderBrush="#1F000000" BorderThickness="6,6,1,1" />
Run Code Online (Sandbox Code Playgroud)
...以及转换器:
public class ValueMinMaxToPointConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double value = (double)values[0];
double minimum = (double)values[1];
double maximum = (double)values[2];
double current = (value / (maximum - minimum)) * 360;
if (current == 360)
current = 359.999;
current = current - 90;
current = current * (Math.PI / 180.0);
double x = 10 + 10 * Math.Cos(current);
double y = 10 + 10 * Math.Sin(current);
return new Point(x, y);
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
public class ValueMinMaxToIsLargeArcConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double value = (double)values[0];
double minimum = (double)values[1];
double maximum = (double)values[2];
return ((value * 2) >= (maximum - minimum));
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Run Code Online (Sandbox Code Playgroud)
你的代码非常接近。问题不在于剪辑。您只是没有考虑到,当描边路径时,描边是在路径的中心绘制的。这意味着从几何角度来看,笔划本身必须位于您想要绘制的位置的中间。
在您的特定实现中,这意味着您需要在三个不同的位置考虑笔画粗细:
例如,您可以添加几个转换器:
class ThicknessToStartPointConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is double))
{
return Binding.DoNothing;
}
// Need to start the arc in the middle of the intended stroke
return new Point(10, ((double)value) / 2);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
class ThicknessToSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is double))
{
return Binding.DoNothing;
}
double widthHeight = 10 - ((double)value) / 2;
return new Size(widthHeight, widthHeight);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Run Code Online (Sandbox Code Playgroud)
然后更新您的 XAML,如下所示:
<PathFigure StartPoint="{Binding StrokeThickness, ElementName=Donut, Converter={StaticResource thicknessToStartPointConverter}}">
<ArcSegment Size="{Binding StrokeThickness, ElementName=Donut, Converter={StaticResource thicknessToSizeConverter}}" SweepDirection="Clockwise">
<ArcSegment.Point>
<MultiBinding Converter="{StaticResource ValueMinMaxToPointConverter}">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
<Binding Path="StrokeThickness" ElementName="Donut"/>
</MultiBinding>
</ArcSegment.Point>
<ArcSegment.IsLargeArc>
<MultiBinding Converter="{StaticResource ValueMinMaxToIsLargeArcConverter}">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum" />
</MultiBinding>
</ArcSegment.IsLargeArc>
</ArcSegment>
</PathFigure>
Run Code Online (Sandbox Code Playgroud)
当然,还有转换器所需的资源:
<l:ThicknessToStartPointConverter x:Key="thicknessToStartPointConverter"/>
<l:ThicknessToSizeConverter x:Key="thicknessToSizeConverter"/>
Run Code Online (Sandbox Code Playgroud)
然后你就会得到你想要的。
可能有一种方法可以将背景Ellipse元素和Path元素组合在一起,这样就可以Path在没有上述内容的情况下绘制 ,即使用硬编码大小 10,然后Grid同等地调整两个子元素的大小,使它们正确对齐。但我没有看到任何明显的解决方案,也不想花时间去弄清楚。以上应该可以很好地满足您的目的。:)
| 归档时间: |
|
| 查看次数: |
2353 次 |
| 最近记录: |