在 WPF 中剪切非不透明的双进度条

Clipping non-opaque double progress bar in WPF

我正在尝试在 WPF 中创建一个具有两个值(第二个值始终等于或高于第一个值)的自定义进度条。基本栏工作正常,如下所示:

<wpft:ClippingBorder BorderBrush="{StaticResource Border}"
                     Background="{StaticResource Background}"
                     BorderThickness="1" CornerRadius="4">
    <Grid Margin="-1" x:Name="Bars">
        <Border BorderBrush="{StaticResource Border}"
                Background="{Binding Value2Brush}"
                BorderThickness="1" CornerRadius="4"
                HorizontalAlignment="Left"
                Width="{Binding Value2Width}" />
        <Border BorderBrush="{StaticResource Border}"
                Background="{Binding Value1Brush}"
                BorderThickness="1" CornerRadius="4"
                HorizontalAlignment="Left"
                Width="{Binding Value1Width}" />
    </Grid>
</wpft:ClippingBorder>

(其中ClippingBorderthis。用于防止值接近0时外角出现毛刺。)

最终结果是一个漂亮的圆形显示:

放大视图,更清楚地显示圆角:

特别注意,两个内部条共享相同的外部边框,并且它们的右边缘向左弯曲,就像外部边框一样。

之所以可行,是因为它先绘制较长的条,然后在其上绘制较短的条。但是,它只有在画笔完全不透明时才能可靠地工作——特别是如果 Value1Brush 是部分透明的,那么一些 Value2Brush 会透过它显示出来,这是我不想要的。

理想情况下,我希望较长的条形图仅绘制自身超出较短条形图的那部分——或者等效地,将较长条形图的 clipping/opacity 掩码设置为透明的区域绘制较短的条。

但我不确定如何在不丢失圆角的情况下做到这一点。

我会尝试使用网格的列。在网格中放置两列,首先是 width="auto" 第二个是“*”。将短边框放在第一列中,另一个放在第二列中。当您更改边框的宽度时,列的大小将相应地调整为边框的宽度。

不幸的是,这不是一个通用的解决方案,但它似乎适用于这种情况。我必须给每个内部边框一个 x:Name 然后把它放在后面的代码中:

构造函数:

DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(Border))
    .AddValueChanged(OuterBar, Child_ActualWidthChanged);
DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(Border))
    .AddValueChanged(InnerBar, Child_ActualWidthChanged);

处理程序:

private void Child_ActualWidthChanged(object sender, EventArgs e)
{
    var outerRect = new Rect(OuterBar.RenderSize);
    outerRect.Inflate(5, 5);
    var outer = new RectangleGeometry(outerRect);

    var corner = InnerBar.CornerRadius.TopLeft;
    var inner = new RectangleGeometry(new Rect(InnerBar.RenderSize), corner, corner);

    OuterBar.Clip = new GeometryGroup()
    {
        Children = { outer, inner }
    };
}

基本思路是从一个略大于外部条形图想要绘制的矩形开始,然后添加一个与内部条形图想要绘制的完全匹配的矩形——这会将它从几何图形中剪掉。然后将整体用作外部条的剪辑区域,这样它就不能绘制到内部条的区域内。

我最初尝试在 XAML 中使用以下方法来执行此操作,但没有成功(宽度更改时未调用转换器);我不确定为什么,但只是为了后代:

<Border.Clip>
    <GeometryGroup>
        <RectangleGeometry Rect="{Binding ElementName=OuterBar, Path=RenderSize,
            Converter={StaticResource BoundsConverter}, ConverterParameter=5.0}" />
        <RectangleGeometry Rect="{Binding ElementName=InnerBar, Path=RenderSize,
            Converter={StaticResource BoundsConverter}}" RadiusX="4" RadiusY="4" />
    </GeometryGroup>
</Border.Clip>

(此处转换器将采用 RenderSize 并生成 Rect,可选 inflation,类似于上面的代码。)