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
相关产品推荐
相关产品推荐

