Why does WPF Loaded event not trigger?

173 Views Asked by At

In WPF, I have a DataGrid, and the DataGrid has an attached property (LoadingProperty). When LoadingProperty value is True, I set the DataGrid background with VisualBrush, why does the LoadingCircle UserControl Loaded event not Trigger?

DataGrid Style

 <Style TargetType="{x:Type DataGrid}">
        <Setter Property="IsReadOnly" Value="True" />
        <Setter Property="AutoGenerateColumns" Value="False" />
        <Setter Property="CanUserAddRows" Value="False" />
        <Setter Property="HeadersVisibility" Value="Column" />
        <Setter Property="Foreground" Value="{StaticResource ForegroundLightBrush}" />
        <Setter Property="Background" Value="{StaticResource BackgroundLoadingBrush}" />
        <Setter Property="BorderBrush" Value="{StaticResource BackgroundDarkBrush}" />
        <Setter Property="BorderThickness" Value="2" />
        <Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BackgroundDarkBrush}" />
        <Setter Property="VerticalGridLinesBrush" Value="{StaticResource BackgroundDarkBrush}" />
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
        <Setter Property="EnableRowVirtualization" Value="True" />
        <Setter Property="EnableColumnVirtualization" Value="True" />
        <Setter Property="materialDesign:DataGridAssist.CellPadding" Value="16,8,16,8" />

        <Style.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 Property="Background">
                    <Setter.Value>
                        <VisualBrush Stretch="None">
                            <VisualBrush.Visual>
                                <local:LoadingCircle Width="50" Height="50" />
                            </VisualBrush.Visual>
                        </VisualBrush>
                    </Setter.Value>
                </Setter>
            </Trigger>

        </Style.Triggers>
    </Style>

LoadingProperty Code

public class LoadingProperty : BaseAttachedProperty<LoadingProperty, bool>
{
}

BaseAttachedProperty Code

using System;
using System.Windows;

namespace TransportsSys.App
{
    /// <summary>
    /// A base attached property to replace the vanilla WPF attached property
    /// </summary>
    /// <typeparam name="Parent">The parent class to be the attached property</typeparam>
    /// <typeparam name="Property">The type of this attached property</typeparam>
    public abstract class BaseAttachedProperty<Parent, Property>
        where Parent : new()
    {
        #region Public Events

        /// <summary>
        /// Fired when the value changes
        /// </summary>
        public event Action<DependencyObject, DependencyPropertyChangedEventArgs> ValueChanged = (sender, e) => { };

        /// <summary>
        /// Fired when the value changes, even when the value is the same
        /// </summary>
        public event Action<DependencyObject, object> ValueUpdated = (sender, value) => { };

        #endregion

        #region Public Properties

        /// <summary>
        /// A singleton instance of our parent class
        /// </summary>
        public static Parent Instance { get; private set; } = new Parent();

        #endregion

        #region Attached Property Definitions

        /// <summary>
        /// The attached property for this class
        /// </summary>
        public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached(
            "Value",
            typeof(Property),
            typeof(BaseAttachedProperty<Parent, Property>),
            new UIPropertyMetadata(
                default(Property),
                new PropertyChangedCallback(OnValuePropertyChanged),
                new CoerceValueCallback(OnValuePropertyUpdated)
                ));

        /// <summary>
        /// The callback event when the <see cref="ValueProperty"/> is changed
        /// </summary>
        /// <param name="d">The UI element that had it's property changed</param>
        /// <param name="e">The arguments for the event</param>
        private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // Call the parent function
            (Instance as BaseAttachedProperty<Parent, Property>)?.OnValueChanged(d, e);

            // Call event listeners
            (Instance as BaseAttachedProperty<Parent, Property>)?.ValueChanged(d, e);
        }

        /// <summary>
        /// The callback event when the <see cref="ValueProperty"/> is changed, even if it is the same value
        /// </summary>
        /// <param name="d">The UI element that had it's property changed</param>
        /// <param name="e">The arguments for the event</param>
        private static object OnValuePropertyUpdated(DependencyObject d, object value)
        {
            // Call the parent function
            (Instance as BaseAttachedProperty<Parent, Property>)?.OnValueUpdated(d, value);

            // Call event listeners
            (Instance as BaseAttachedProperty<Parent, Property>)?.ValueUpdated(d, value);

            // Return the value
            return value;
        }

        /// <summary>
        /// Gets the attached property
        /// </summary>
        /// <param name="d">The element to get the property from</param>
        /// <returns></returns>
        public static Property GetValue(DependencyObject d) => (Property)d.GetValue(ValueProperty);

        /// <summary>
        /// Sets the attached property
        /// </summary>
        /// <param name="d">The element to get the property from</param>
        /// <param name="value">The value to set the property to</param>
        public static void SetValue(DependencyObject d, Property value) => d.SetValue(ValueProperty, value);

        #endregion

        #region Event Methods

        /// <summary>
        /// The method that is called when any attached property of this type is changed
        /// </summary>
        /// <param name="sender">The UI element that this property was changed for</param>
        /// <param name="e">The arguments for this event</param>
        public virtual void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { }

        /// <summary>
        /// The method that is called when any attached property of this type is changed, even if the value is the same
        /// </summary>
        /// <param name="sender">The UI element that this property was changed for</param>
        /// <param name="e">The arguments for this event</param>
        public virtual void OnValueUpdated(DependencyObject sender, object value) { }

        #endregion
    }
}

LoadingCircle UserControl Code

// LoadingCircle.xmal
<UserControl
    x:Class="TransportsSys.App.LoadingCircle"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid>
        <TextBlock FontSize="{StaticResource FontSizeXXXXLarge}" Foreground="{StaticResource ForegroundLightBrush}" Style="{StaticResource DataGridSpinningText}" />
    </Grid>
</UserControl>

// LoadingCircle.xmal.cs
using System.Windows.Controls;

namespace TransportsSys.App
{
    /// <summary>
    /// LoadingCircle.xaml 的交互逻辑
    /// </summary>
    public partial class LoadingCircle : UserControl
    {
        public LoadingCircle()
        {
            InitializeComponent();
            Loaded += (s, e) =>
            {
                // can not trigger
            };
        }
    }
}

I wonder why that is. When I use LoadingCircle alone in a page, its Loaded event fires normally.

1

There are 1 best solutions below

1
On

This looks like a WPF bug.
A workaround is to put the VisualBrush in resources and use it in the trigger:

<UserControl.Resources>
    <VisualBrush x:Key="Brush" Stretch="None">
        <VisualBrush.Visual>
            <local:LoadingCircle Width="50" Height="50" />
        </VisualBrush.Visual>
    </VisualBrush>
</UserControl.Resources>

...

<Trigger Property="local:LoadingProperty.Value" Value="True">
    <Setter Property="Background" Value="{StaticResource Brush}" />
</Trigger>

A MRE is available here.