EventTrigger not firing for Loaded event of UserControl - wpf

I have a UserControl with the following event trigger:
<UserControl x:Class="TestApp.Views.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding OnLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
It is being set via the constructor of the UserControl (please ignore the ServiceLocator ..this is just a quick prototype):
public MyUserControl()
{
InitializeComponent();
DataContext = ServiceLocator.Current.GetInstance<DirectorySearchViewModel>();
}
In my view-model I have the following:
public ICommand OnLoadedCommand { get; private set; }
public MyUserControl()
{
OnLoadedCommand = new DelegateCommand(OnLoaded);
}
public void OnLoaded()
{
}
OnLoaded never gets called. If I change the EventName to say ..MouseDown, then it works but it just won't work for Loaded
Pretty sure it's a stupid mistake (swear I've done this a million times before in the past) but can't seem to figure it out right now

public MyUserControl()
{
DataContext = ServiceLocator.Current.GetInstance<DirectorySearchViewModel>();
InitializeComponent();
}
The Aristocrats.

For anyone else that stumbles across this you can do it without code behind like this:
<UserControl x:Class="TestApp.Views.MyUserControl"
... etc...
x:Name="theControl"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction
Command="{Binding ElementName=theControl, Path=OnLoadedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>

Related

How to use a trigger to affect related grid elements?

As an exercise in WPF I am dabbling with a Sudoku-like grid.
Consider the following (simplified) example
XAML
<Window x:Class="SO_WPF_Question_Sample.MainWindow"
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"
xmlns:local="clr-namespace:SO_WPF_Question_Sample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style x:Key="MouseOverStyle" TargetType="{x:Type Label}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
<!-- I want something to happen here -->
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Path=Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1,1,1,1">
<Label Content="{Binding}"
Style="{StaticResource MouseOverStyle}"
/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
Viewmodel
public class MainWindowViewModel // INotifyPropertyChanged omitted for simplicity
{
public MainWindowViewModel()
{
PopulateElements(4);
}
public IEnumerable<Cell> Items { get; set; }
private void PopulateElements(int size)
{
int div = (int)Math.Sqrt(size);
IList<Cell> items = new List<Cell>();
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
items.Add(new Cell()
{
X = i,
Y = j,
Z = j / div + div * (i / div)
});
}
}
Items = items;
}
}
POCO class
public class Cell
{
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
public override string ToString()
{
return "Row: " + X + " Col: " + Y + " Box: " + Z;
}
}
The only interesting bit: whenever I hover over a grid element, it turns red.
What I want: whenever I hover over a grid element, I want to highlight all 'sibling' cells as well. I.e. the ones where X, Y and Z are equal to those of the active cell.
I don't know how to achieve that, but I can think about some strategies.
Active strategy:
Upon the Trigger, somehow fire some method (something like a Command?) (or something else?), have that parse all Cells in the grid, if they are siblings, set some property on them, and define a Style to respond to a DataTrigger.
Passive strategy:
Upon the trigger, set some ViewModel property that triggers an event that the Cells subscribe to (INotifyPropertChanged springs to mind), have an event handler in Cell set a property and again have a Style with a DataTrigger. I can see this work in my mind but there is a coupling issue with this approach. Also, how would I convey the info about the grid element being hovered over?
While researching this I came across EventTrigger and Interaction but those seem to be primarily geared to animations. There is probably a better/simpler approach to this. I have the feeling I am overthinking this.
I would appreciate advice nudging me to an approach on how to tackle this and if possible why that approach would be favorable.
Update it turns out that what my question really boiled down to was how to call a Command (with a CommandParameter) using EventTrigger. There are a lot of answers on SO on this one, a lot of them involving MVVM Light. I solved it by using Microsoft.Xaml.Behaviors.Wpf and can now see how MVVM Light might have been the better/easier way to go.
From experience alone I'm biased towards something along the lines of the first proposed solution.
Add the following namespaces to the xaml:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
And then use EventToCommand:
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<cmd:EventToCommand Command="{Binding HoverCellCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In the view model you'd have something like...
public RelayCommand<MouseEventArgs> HoverCellCommand {get; private set;}
HoverCellCommand = new RelayCommand<MouseEventArgs>(p =>
{
//find cell siblings, etc...
});
For future reference for myself, this is how I did it without using MVVM Light. Instead, I installed the Microsoft.Xaml.Behaviors.Wpf NuGet package to get the Behaviors stuff (known as Interactivity in Blend)
First the basic stuff: create a command:
public class ChangeSiblingColorCommand : ICommand
{
private readonly Action<Cell> _action;
#pragma warning disable CS0067 // The event is never used
public event EventHandler CanExecuteChanged;
#pragma warning restore CS0067 // The event is never used
public ChangeSiblingColorCommand(Action<Cell> action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return (parameter is Cell c);
}
public void Execute(object parameter)
{
_action((Cell)parameter);
}
}
Then include the command in the viewmodel so it can be bound to:
public MainWindowViewModel()
{
this.ChangeSiblingColorCommand = new ChangeSiblingColorCommand(HighlightSiblings);
}
public ICommand ChangeSiblingColorCommand { get; private set; }
private void HighlightSiblings(Cell cell)
{
var siblings = cell.Siblings(Items);
foreach (Cell c in siblings)
{
c.IsSiblingHighlighted = !c.IsSiblingHighlighted;
}
}
Finally, hook the command up in the view using the following rather scary XAML:
<ItemsControl ItemsSource="{Binding Path=Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Style="{StaticResource MouseOverStyle}" Content="{Binding Mode=OneWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UniformGrid,
Mode=FindAncestor},
Path=DataContext.ChangeSiblingColorCommand }"
CommandParameter="{Binding}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UniformGrid,
Mode=FindAncestor},
Path=DataContext.ChangeSiblingColorCommand }"
CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Be sure to include this reference in the view:
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

How to Add a Colmmand to a ComboBox

I am attempting to add command capability to a ComboBox. After some searching, I decided on the following approach as being the simplist:
1) Add System.Windows.Interactivity.dll to my References
2) Add the following to my XAML
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
3) Add the following to my ComboBox
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding ChangePlanner}" />
</i:EventTrigger>
</i:Interaction.Triggers>
I have two questions:
A) Is this the most straightforward approach? If not, what is?
B) If this is the right approach, why does it not work? That is, my ChangePlanner Sub is not being invoked.
Here is a quick working sample using the triggers with a ComboBox:
ViewModel
public class ShellViewModel : BindableBase
{
private string _selectedItem;
public string Title => "Sample";
public ObservableCollection<string> Items
{
get;
} = new ObservableCollection<string>(new[] { "A", "B", "C" });
public string SelectedItem
{
get => _selectedItem;
set => SetProperty(ref _selectedItem, value);
}
public ICommand ChangeCommand => new DelegateCommand<string>(s => Debug.WriteLine($"Command Executed: {s}"));
}
View
<Window x:Class="Poc.MainWindow"
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"
xmlns:viewModels="clr-namespace:Poc.ViewModels"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
Title="{Binding Title}" Height="350" Width="525">
<Window.DataContext>
<viewModels:ShellViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding ChangeCommand}" CommandParameter="{Binding SelectedItem}"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</Grid>
Haven't seen your code posted yet, but I am going to guess that you were trying to bind to a method (and not an ICommand).
Is this the most straightforward approach? If not, what is?
The most straightforward and MVVM friendly approach would be to bind the SelectedItem of the ComboBox to a source property of your view model and handle any logic, or invoke the command, in the setter of this one:
private object _selectedItem;
public object SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
ChangePlanner.Execute(null);
}
}
why does it not work?
Impossible to say based on the information you have provided. Make sure that ChangePlanner is a public property of the DataContext of the ComboBox that returns an ICommand to begin with.

How to use LostFocus as a Command in WPF

I am trying to use LostFocus event as Command in DataGridTextColumn and can't find an example on how to use it in WPF.
Can anyone help me use it as I am new to WPF.
Thank you.
My xaml looks like:
`
The c# code:
public partial class Myogg : UserControl {
MyLogg _viewModel;
public MyLogg()
{
InitializeComponent();
_viewModel = new MyLoggUCViewModel();
DataContext = _viewModel;
}
}`
you can use interaction triggers to do this ..
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding Path=LostFocusCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>

Handle MainWindow events in ViewModel - WPF

I want to handle Windows event like Closing, SourceInitialized in my viewModel. I don't want to handle them in my code behind. How can I do that?
Thanks in advance.
Simply use EventToCommand.
ViewModel:
public ICommand WindowClosing
{
get
{
return new RelayCommand<CancelEventArgs>(
(args) =>{
});
}
}
and in XAML:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding WindowClosing}" />
</i:EventTrigger>
</i:Interaction.Triggers>

PropertyChanged event on viewmodel property

i'm trying to Raise a PropertyChanged event on a Property in my ViewModel
using interaction triggers .
CS :
public string MyContentProperty
{
get { return "I Was Raised From an outside Source !";}
}
XAML :
<Button Content="{Binding MyContentProperty}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Button.Click">
< .... what needs to be done ?>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
of course if there was any doubt for this question you have references to
xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
at your disposal , thanks in advance .
You can use a normal command or Expression Blend's CallMethodAction, InvokeCommandAction or ChangePropertyAction.
Here are four ways to do what you want:
<Button Content="Button" Height="23" Width="100" Command="{Binding RaiseItCmd}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding RaiseItCmd}"/>
<ei:CallMethodAction MethodName="RaiseIt" TargetObject="{Binding}"/>
<ei:ChangePropertyAction Value=""
PropertyName="MyContentProperty" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Here I'm using MVVM Light's ViewModelBase:
using System.Windows.Input;
using GalaSoft.MvvmLight;
using Microsoft.Expression.Interactivity.Core;
public class ViewModel : ViewModelBase
{
public ViewModel()
{
RaiseItCmd = new ActionCommand(this.RaiseIt);
}
public string MyContentProperty
{
get
{
return "property";
}
set
{
this.RaiseIt();
}
}
public void RaiseIt()
{
RaisePropertyChanged("MyContentProperty");
}
public ICommand RaiseItCmd { get; private set; }
}

Resources