You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF中LoadingCircle在VisualBrush内Loaded事件不触发的原因

WPF中VisualBrush内控件Loaded事件不触发的问题解决

核心原因

在WPF中,VisualBrush的Visual属性引用的元素不会被纳入可视化树。而Loaded事件的触发依赖元素完成可视化树的挂载流程,脱离可视化树的元素即使执行了InitializeComponent,也不会触发Loaded事件——这就是单独使用LoadingCircle正常、放在VisualBrush里就失效的根本原因。

解决方案

方案一:用覆盖层替代VisualBrush(推荐)

这是实现加载状态的常规方案,通过在DataGrid上层添加一个可控制可见性的ContentControl作为覆盖层,完全规避VisualBrush的局限性:

修改DataGrid样式,添加带覆盖层的ControlTemplate:

<Style TargetType="{x:Type DataGrid}">
    <!-- 原有Setter配置保持不变 -->
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGrid}">
                <Grid>
                    <!-- 保留DataGrid原有模板结构 -->
                    <Border BorderBrush="{TemplateBinding BorderBrush}" 
                            BorderThickness="{TemplateBinding BorderThickness}" 
                            Background="{TemplateBinding Background}">
                        <ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
                            <ScrollViewer.Template>
                                <ControlTemplate TargetType="{x:Type ScrollViewer}">
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="Auto"/>
                                            <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="Auto"/>
                                        </Grid.ColumnDefinitions>
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto"/>
                                            <RowDefinition Height="*"/>
                                            <RowDefinition Height="Auto"/>
                                        </Grid.RowDefinitions>
                                        <Button Command="{x:Static DataGrid.SelectAllCommand}" 
                                                Focusable="false" 
                                                Style="{DynamicResource {ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle, TypeInTargetAssembly={x:Type DataGrid}}}" 
                                                Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.All}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" 
                                                Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                        <DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter" 
                                                                        Grid.Column="1" 
                                                                        Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                        <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" 
                                                                CanContentScroll="{TemplateBinding CanContentScroll}" 
                                                                Grid.ColumnSpan="2" 
                                                                Grid.Row="1"/>
                                        <ScrollBar x:Name="PART_VerticalScrollBar" 
                                                    Grid.Column="2" 
                                                    Maximum="{TemplateBinding ScrollableHeight}" 
                                                    Orientation="Vertical" 
                                                    Grid.Row="1" 
                                                    Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" 
                                                    Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" 
                                                    ViewportSize="{TemplateBinding ViewportHeight}"/>
                                        <Grid Grid.Column="1" Grid.Row="2">
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                                <ColumnDefinition Width="*"/>
                                            </Grid.ColumnDefinitions>
                                            <ScrollBar x:Name="PART_HorizontalScrollBar" 
                                                        Grid.Column="1" 
                                                        Maximum="{TemplateBinding ScrollableWidth}" 
                                                        Orientation="Horizontal" 
                                                        Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" 
                                                        Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" 
                                                        ViewportSize="{TemplateBinding ViewportWidth}"/>
                                        </Grid>
                                    </Grid>
                                </ControlTemplate>
                            </ScrollViewer.Template>
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </ScrollViewer>
                    </Border>
                    <!-- 加载状态覆盖层 -->
                    <ContentControl 
                        x:Name="LoadingOverlay"
                        Visibility="Collapsed"
                        Background="#AAFFFFFF"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center">
                        <local:LoadingCircle Width="50" Height="50"/>
                    </ContentControl>
                </Grid>
                <ControlTemplate.Triggers>
                    <!-- 原有无数据触发器保持不变 -->
                    <Trigger Property="HasItems" Value="False">
                        <Setter Property="Background">
                            <Setter.Value>
                                <VisualBrush Stretch="None">
                                    <VisualBrush.Visual>
                                        <TextBlock Foreground="{StaticResource ForegroundLightBrush}" Text="No record found" />
                                    </VisualBrush.Visual>
                                </VisualBrush>
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                    <!-- 修改为控制覆盖层可见性 -->
                    <Trigger Property="local:LoadingProperty.Value" Value="True">
                        <Setter TargetName="LoadingOverlay" Property="Visibility" Value="Visible"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

方案二:手动触发加载逻辑(适合必须用VisualBrush的场景)

如果一定要保留VisualBrush的实现,可以把原本在Loaded事件中的逻辑直接移到构造函数,或者在附加属性变化时手动触发:

方式1:修改LoadingCircle构造函数

public partial class LoadingCircle : UserControl
{
    public LoadingCircle()
    {
        InitializeComponent();
        // 直接执行原本在Loaded中的逻辑
        RunLoadingLogic();
    }

    private void RunLoadingLogic()
    {
        // 这里写原本在Loaded事件里的代码
    }
}

方式2:在附加属性回调中触发

修改LoadingProperty的OnValueChanged方法,主动找到VisualBrush内的LoadingCircle并触发逻辑:

public class LoadingProperty : BaseAttachedProperty<LoadingProperty, bool>
{
    public override void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        base.OnValueChanged(sender, e);
        if ((bool)e.NewValue && sender is DataGrid dataGrid)
        {
            if (dataGrid.Background is VisualBrush visualBrush && visualBrush.Visual is LoadingCircle loadingCircle)
            {
                loadingCircle.RunLoadingLogic();
            }
        }
    }
}

需要给LoadingCircle添加RunLoadingLogic公共方法,暴露原本的加载逻辑。

内容的提问来源于stack exchange,提问作者knab

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 11:10:15