强制在自定义 WPF 控件中重新绘制自定义绘制的 UIElement

Posted

技术标签:

【中文标题】强制在自定义 WPF 控件中重新绘制自定义绘制的 UIElement【英文标题】:Force a repaint of a custom drawn UIElement in a custom WPF control 【发布时间】:2017-04-09 20:10:38 【问题描述】:

我正在开发一个自定义 WPF 控件。此控件的主要目的是在可滚动区域中可视化数千个图形基元。控件模板的核心部分如下所示:

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="x:Type local:ItemVisualizer">
            <Border Background="TemplateBinding Background"
                    BorderBrush="TemplateBinding BorderBrush"
                    BorderThickness="TemplateBinding BorderThickness">

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <local:ItemAreaElement Grid.Row="0" Grid.Column="0" x:Name="PART_ItemArea" />
                    <ScrollBar Grid.Row="0" Grid.Column="1" x:Name="PART_ScrollBarVert" Orientation="Vertical" Maximum="100" />
                    <ScrollBar Grid.Row="1" Grid.Column="0" x:Name="PART_ScrollBarHorz" Orientation="Horizontal" Maximum="100" />
                    <Rectangle Grid.Row="1" Grid.Column="1" x:Name="PART_SizeGrip" Focusable="False" Fill="#F0F0F0" />
                </Grid>

            </Border>
        </ControlTemplate>
    </Setter.Value>
</Setter>

ItemAreaElement 负责绘制项目。为简单起见,我们可以认为它的核心部分是这样的:

class ItemAreaElement : FrameworkElement

    protected override void OnRender(DrawingContext drawingContext)
    
        base.OnRender(drawingContext);

        for (int i = 0; i < _data.ItemCount; i++)
        
            drawingContext.DrawLine(_penLine, new Point(0, i * 10), new Point(100, i * 10));
        
    

每次当整个 ItemVisualizer 控件中的相关属性发生更改时,我都需要重新绘制 ItemAreaElement。但是,我没有找到在 WPF 中做到这一点的方法。 Dispatcher 对象的众所周知的技巧在我的情况下不起作用:

private static void OnItemCountPropertyChanged(DependencyObject source,
        DependencyPropertyChangedEventArgs e)

    ItemVisualizer vis = (ItemVisualizer)source;
    vis._itemArea.Dispatcher.Invoke(delegate  , DispatcherPriority.Render);

,其中 _itemArea 是对 OnApplyTemplate() 中获取的 ItemAreaElement 的本地引用:

public override void OnApplyTemplate()

    base.OnApplyTemplate();

    if (this.Template != null)
    
        _itemArea = this.Template.FindName("PART_ItemArea", this) as ItemAreaElement;
        _itemArea.Grid = this;
    

还有其他方法可以在我的构造中强制更新 UIElement 吗?或者,我需要重新设计整个控件以使其成为可能?

【问题讨论】:

你不能只打电话给vis._itemArea.InvalidateArrange()vis._itemArea.InvalidateVisual()吗? 或者可能只是在 ItemVisualizer 属性注册时设置FrameworkPropertyMetadataOptions.AffectsRender @Clemens,ItemVisualizer 是一个对象,而不是属性。但是UIElement.InvalidateVisual() 有帮助,我不知道为什么我忽略了它。您可以将其发布为答案,而不是评论。 对于“ItemVisualizer 属性”,我指的是 ItemVisualizer 的 ItemCount 属性。它是一个依赖属性,因此您可以在注册时使用 FrameworkPropertyMetadata 指定 FrameworkPropertyMetadataOptions。这可能会让你不用调用任何 Invalidate 东西。 【参考方案1】:

在注册ItemCount 属性时指定FrameworkPropertyMetadataOptions.AffectsRender 通常就足够了:

public static readonly DependencyProperty ItemCountProperty =
    DependencyProperty.Register(
        "ItemCount", typeof(int), typeof(ItemVisualizer),
        new FrameworkPropertyMetadata(FrameworkPropertyMetadataOptions.AffectsRender));

如果这没有帮助,您可以通过在 ItemAreaElement 上调用 InvalidVisual() 来强制重绘:

var vis = (ItemVisualizer)source;
vis._itemArea.InvalidVisual();

【讨论】:

是的,我确实记得FrameworkPropertyMetadataOptions.AffectsRender 选项,但我需要从我的代码中重新绘制。正如我所写,UIElement.InvalidateVisual() 按预期工作。

以上是关于强制在自定义 WPF 控件中重新绘制自定义绘制的 UIElement的主要内容,如果未能解决你的问题,请参考以下文章

C# wpf 如何实现自定义控件,布局时,大小发生变化,内部绘制的曲线跟随变化?

Android 自定义控件——图片剪裁

自定义控件知识储备-View的绘制流程

自定义控件知识储备-View的绘制流程

在自定义活动中绘制 TextView?

在自定义视图中绘制描边形状