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).
I use behavior implemented as attached property. It has two major advantages over System.Windows.Interactivity:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With