Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add trigger to every UserControl

Tags:

wpf

xaml

My application has about 15 different UserControls that are uniformly loaded into a content-area at runtime.

My project is 100% MVVM-compliant, so I have the following XAML inserted into every UserControl's XAML:

<UserControl
    ...
    xmlns:intr="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
>

    <intr:Interaction.Triggers>
        <intr:EventTrigger EventName="Loaded">
            <intr:InvokeCommandAction Command="{Binding ViewLoadedCommand}"/>
        </intr:EventTrigger>
    </intr:Interaction.Triggers>

    <!-- Rest of UserControl content here -->

</UserControl>

Obviously this isn't ideal and is also a violation of DRY.

What is the best way to have this XAML applied to this set of UserControls? (but not every UserControl, so a simple <Style Type="UserControl" /> would be inappropriate).

like image 640
Dai Avatar asked Sep 27 '22 16:09

Dai


1 Answers

I use behavior implemented as attached property. It has two major advantages over System.Windows.Interactivity:

  • it can be defined in style.
  • much less xaml code in the views

in your case, the view could look like:

<UserControl ...
             my:AttachedCommands.LoadedCommand="{Binding ViewLoadedCommand}">

In my solution, I'm not using commands, but I call methods on viewmodel if the viewmodel implements IViewModelLifeCycle interface:

public interface IViewModelLifeCycle
{
    void Activate(object extraData);
    void Deactivate();
}

All my views uses this style:

<Style x:Key="ViewBaseStyle">
    <Setter Property="my:ViewModelLifeCycleBehavior.ActivateOnLoad" Value="True" />

and the behavior:

public static class ViewModelLifeCycleBehavior
{
    public static readonly DependencyProperty ActivateOnLoadProperty = DependencyProperty.RegisterAttached("ActivateOnLoad", typeof (bool), typeof (ViewModelLifeCycleBehavior),
        new PropertyMetadata(ActivateOnLoadPropertyChanged));

    public static void SetActivateOnLoad(FrameworkElement element, bool value)
    {
        element.SetValue(ActivateOnLoadProperty, value);
    }

    public static bool GetActivateOnLoad(FrameworkElement element)
    {
        return (bool)element.GetValue(ActivateOnLoadProperty);
    }


    private static void ActivateOnLoadPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    { 
        if (DesignerProperties.GetIsInDesignMode(obj)) return;
        var element = (FrameworkElement)obj;

        element.Loaded -= ElementLoaded;
        element.Unloaded -= ElementUnloaded;

        if ((bool) args.NewValue == true)
        {
            element.Loaded += ElementLoaded;
            element.Unloaded += ElementUnloaded;
        }
    }



    static void ElementLoaded(object sender, RoutedEventArgs e)
    {
        var element = (FrameworkElement) sender;
        var viewModel = (IViewModelLifeCycle) element.DataContext;
        if (viewModel == null)
        {
            DependencyPropertyChangedEventHandler dataContextChanged = null;
            dataContextChanged = (o, _e) =>
            {
                ElementLoaded(sender, e);
                element.DataContextChanged -= dataContextChanged;
            };
            element.DataContextChanged += dataContextChanged;
        }
        else if (element.ActualHeight > 0 && element.ActualWidth > 0) //to avoid activating twice since loaded event is called twice on TabItems' subtrees
        {
            viewModel.Activate(null);
        } 
    }

    private static void ElementUnloaded(object sender, RoutedEventArgs e)
    {
        var element = (FrameworkElement)sender;
        var viewModel = (IViewModelLifeCycle)element.DataContext;
        viewModel.Deactivate();
    }


}

TIP: Create your custom Item Template in Visual Studio for View and ViewModel. its very easy and saves a lot of time. The item template can contain xaml code with the trigger/behaviour, pointing to your base style, your d:DataContext definition and your viewmodel class.

like image 105
Liero Avatar answered Sep 29 '22 06:09

Liero