0

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.

Palle Due
  • 5,929
  • 4
  • 17
  • 32
knab
  • 21
  • 3
  • There is no logic to make that `Value` attached property true. Have you not implemented yet? – emoacht Jan 10 '23 at 09:08
  • Why do you have this dependency property at all? – Andy Jan 10 '23 at 09:38
  • Your question is about the `LoadingCircle.Loaded` event, all the code about the datagrid is useless. – Orace Jan 10 '23 at 10:21
  • @emoacht I can make sure **Value** is True – knab Jan 11 '23 at 02:53
  • @Andy I want to extend a Loading property to the DataGrid – knab Jan 11 '23 at 02:54
  • Why do you want to do that? There is already hasitems. – Andy Jan 11 '23 at 06:31
  • @Andy Because I want to display a "No record found" background for the DataGrid when HasItems is False, and a Loading background when LoadingProperty Value is True. It doesn't really matter, you can ignore this, because when you replace LoadingProperty with HasItems, the problem will still occur. The problem is that UserControl in VisualBrush cannot fire Loaded event. If you're interested, you can take a look at this [code](https://github.com/HubTonight/SO_75067133_1) – knab Jan 11 '23 at 07:23

1 Answers1

0

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.

Orace
  • 7,822
  • 30
  • 45
  • Thank you for your answer. It does work when putting Style into Resources in Control, but I have a requirement to put Style into Application.Resources in App.xaml, and it doesn't work. [Here's](https://github.com/HubTonight/SO_75067133_1) the code – knab Jan 11 '23 at 02:47