Search code examples
wpfxamlpropertiesstylesclr

Attaching CLR event handler in xaml style in WPF


Is there a way to attach a handler for the IsVisibleChanged event for a DataGridRow in a DataGridRow style definition? That is, is there a way to do something like the following:

<DataGrid.RowStyle>
    <Style TargetType="{x:Type DataGridRow}">
        <EventSetter Event="IsVisibleChanged" Handler="OnIsVisibleChanged"/>
    </Style>
</DataGrid.RowStyle>

The above won't work because EventSetter can only be applied to RoutedEvents and not regular CLR events, like IsVisibleChanged.


Solution

  • We'll have to make an attached property and an event.

    using System;
    using System.Windows;
    
    namespace CommonCore.AttachedEvents
    {
        public static class UIElementHelper
        {
            public static readonly RoutedEvent IsVisibleChangedEvent = EventManager.RegisterRoutedEvent(
               "IsVisibleChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<bool>), typeof(UIElementHelper));
    
            public static void AddIsVisibleChangedHandler(DependencyObject dependencyObject, RoutedPropertyChangedEventHandler<bool> handler)
            {
                if (dependencyObject is not UIElement uiElement)
                    return;
    
                uiElement.AddHandler(IsVisibleChangedEvent, handler);
            }
    
            private static void RaiseIsVisibleChangedEvent(object sender, DependencyPropertyChangedEventArgs e)
            {
                ((UIElement)sender).RaiseEvent(new RoutedPropertyChangedEventArgs<bool>((bool)e.OldValue, (bool)e.NewValue, IsVisibleChangedEvent));
            }
    
            public static void RemoveIsVisibleChangedHandler(DependencyObject dependencyObject, RoutedPropertyChangedEventHandler<bool> handler)
            {
                if (dependencyObject is not UIElement uiElement)
                    return;
    
                uiElement.RemoveHandler(IsVisibleChangedEvent, handler);
            }
            public static bool GetRaiseIsVisibleChanged(UIElement uiElement)
            {
                return (bool)uiElement.GetValue(RaiseIsVisibleChangedProperty);
            }
    
            public static void SetRaiseIsVisibleChanged(UIElement uiElement, bool value)
            {
                uiElement.SetValue(RaiseIsVisibleChangedProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for RaiseIsVisibleChanged.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty RaiseIsVisibleChangedProperty =
                DependencyProperty.RegisterAttached(
                    "RaiseIsVisibleChanged",
                    typeof(bool),
                    typeof(UIElementHelper),
                    new PropertyMetadata(false, OnRaiseIsVisibleChanged));
    
            private static void OnRaiseIsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                if (d is not UIElement uiElement)
                {
                    throw new InvalidOperationException("Implemented only for UIElement.");
                }
                if ((bool)e.NewValue)
                {
                    uiElement.IsVisibleChanged += RaiseIsVisibleChangedEvent;
                }
                else
                {
                    uiElement.IsVisibleChanged -= RaiseIsVisibleChangedEvent;
                }
            }
        }
    }
    

    Their use:

    <StackPanel>
        <CheckBox x:Name="checkBox"
                  Content="Visibility"
                  IsChecked="False"/>
        <Border Background="AliceBlue" Padding="10" Margin="10">
            <Grid Height="20" Background="Aqua"
                  Visibility="{Binding IsChecked,
                                       ElementName=checkBox, Converter={commcnvs:BooleanToVisibility}}">
                <FrameworkElement.Style>
                    <Style TargetType="Grid">
                        <Setter Property="aev:UIElementHelper.RaiseIsVisibleChanged"
                                Value="True"/>
                        <EventSetter Event="aev:UIElementHelper.IsVisibleChanged"
                                     Handler="OnIsVisibleChanged"/>
                    </Style>
                </FrameworkElement.Style>
            </Grid>
        </Border>
    </StackPanel>
    
    private void OnIsVisibleChanged(object sender, RoutedPropertyChangedEventArgs<bool> args)
    {
        MessageBox.Show($"OldValu = {args.OldValue}; NewValue = {args.NewValue};");
    }