Using interaction trigger to call visibility changed method WPF - c#

What I would like to figure out is two things, how to get a trigger occurring when a user control's visibility is changed and passing the value of visibility through as a parameter.
For whatever reason the trigger doesn't seem to be firing. I have only just added in the ControlVisible parameter to show what I would like to happen, when testing it was not there and just had a messagebox inside to catch when visibility changed, as in the commented out method.
I am using 4.0 with Visual Studio 2010
Main Window View which contains the user control
<Window x:Class="bt.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:vm="clr-namespace:bt"
xmlns:ctrls="clr-namespace:bt.Controls"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
mc:Ignorable="d">
<Grid>
<ctrls:Login Visibility="{Binding DataContext.Vis,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},Converter={StaticResource BooleanToVisibilityConverter}}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="IsVisibleChanged">
<ei:CallMethodAction MethodName="VisibleTrigger" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ctrls:Login>
</Grid>
</Window>
UserControl View Model:
namespace bt.Controls
{
class LoginViewModel
{
public LoginViewModel()
{
}
public void VisibleTrigger(bool ControlVisible)
{
if (ControlVisible)
{
MessageBox.Show("Start timer");
}
else
{
MessageBox.Show("Stop timer");
}
}
//public void VisibleTrigger()
//{
// MessageBox.Show("Changed");
//}
}
}

First, we need to set TargetObject property to viewmodel/DataContext, because method to be invoked is available in the viewmodel :
......
<i:Interaction.Triggers>
<i:EventTrigger EventName="IsVisibleChanged">
<ei:CallMethodAction MethodName="VisibleTrigger" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
......
Second, EventTrigger doesn't seems to work specifically with IsVisibleChanged event. So code snippet above works for other event, but not IsVisibleChanged. We can find a workaround in the answer to this SO question, by using PropertyChangedTrigger to listen to Visibility property changed, instead of listening to IsVisibleChanged event :
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding Visibility, ElementName=MyControlName}">
<ei:CallMethodAction MethodName="VisibleTrigger" TargetObject="{Binding}"/>
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
Third, CallMethodAction doesn't seems to provide a way to pass parameter to the method. To be able to invoke a method with parameter we better use InvokeCommandAction instead of CallMethodAction as suggested here and also suggested by #Rohit in your previous question.

Related

It's possible to convert event triggers to viewmodel using converters?

I'm writing WPF app in MVVM using MVVM Light. I have an event trigger in DataGrid to detecting the cell editing ends.
In viewmodel I have command which needs a DataGrid binding item as param. I did it using casting DataGridCellEditEndingEventArgs.EditingElement.DataContext to my model. It's work as I want but it's hard to VM testing.
Here's View's trigger
// xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<DataGrid x:Name="PeopleDataGrid" ItemsSource="{Binding People}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="CellEditEnding">
<cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding EditPersonRowCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
And in VM here's the command
public RelayCommand<DataGridCellEditEndingEventArgs> EditPersonRowCommand
{
get
{
return editPersonRowCommand ??
(editPersonRowCommand =
new RelayCommand<DataGridCellEditEndingEventArgs>(param => this.EditPersonRow(param.EditingElement.DataContext as PersonForListDto), this.editPersonRowCommandCanExecute));
}
}
It's possible to using IValueConverter or something to have model right way without control casting?
The PassEventArgsToCommand dependency property pass the event argument to command. Instead of using PassEventArgsToCommand, you can define the binding for CommandParameter to pass the DataContext. With this, at VM, the RelayCommand can define with actual type. The code at View and ViewModel will be as follows:
<i:Interaction.Triggers>
<i:EventTrigger EventName="CellEditEnding">
<cmd:EventToCommand Command="{Binding EditPersonRowCommand}" CommandParameter="{Binding //Since you have not given the full code so not sure how Binding is cascading so if you require to use ReleativeSource to bind to DataContext then use that.}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
And
public RelayCommand<PersonForListDto> EditPersonRowCommand
{
get
{
return editPersonRowCommand ??
(editPersonRowCommand =
new RelayCommand<PersonForListDto>(param => this.EditPersonRow(param), this.editPersonRowCommandCanExecute));
}
}
With above, your VM would be cleaner and can easily be unit tested.

Binding custom events of custom elements in MVVM Pattern

I'm trying to bind the "DataClick" event of LiveChart's Cartesian Chart element using MVVM pattern.
I have my Charts.xml like this:
<ContentControl Grid.Row="0">
<lvc:CartesianChart x:Name="ContrastChart" Series="{Binding ContrastSeriesCollection}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DataClick">
<i:InvokeCommandAction Command="{Binding ChartDataClick}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</lvc:CartesianChart>
</ContentControl>
This is my ICommand ChartDataClick on my ViewModel:
public ICommand ChartDataClick {
get
{
if(_dataClickCommand == null)
{
_dataClickCommand = new DelegateCommand(
() =>
{
MessageBox.Show("Data Clicked!");
}
);
}
return _dataClickCommand;
}
}
If I switch e.g "DataClick" for "MouseEnter" I get my command fired.
So I'm assuming that the problem is that the DataClick is a custom event.
Anybody knows a workaround for this?
I really tried everything I could find on Google that could help, but nothing so far...
LiveCharts Events: Events Documentation
The EventTrigger doesn't discriminate.
We can check this by implementing MyButtonSimple which has a custom Routed Event Tap.
We can go from handler in code behind
<custom:MyButtonSimple
x:Name="mybtnsimple" Tap="mybtnsimple_Tap"
Content="Click to see Tap custom event work">
</custom:MyButtonSimple>
To a ViewModel ICommand
<custom:MyButtonSimple
x:Name="mybtnsimple"
Content="Click to see Tap custom event work">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<i:InvokeCommandAction Command="{Binding Command}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</custom:MyButtonSimple>
And everything works as expected
The shortcoming of these triggers is that they have to be placed on the UIElement raising the event.
In other words, they ignore Bubbling or Tunneling events. That's why there is no Interaction.Triggers alternative for:
<Grid custom:MyButtonSimple.Tap="mybtnsimple_Tap">
<custom:MyButtonSimple
x:Name="mybtnsimple"
Content="Click to see Tap custom event work">
</custom:MyButtonSimple>
</Grid>
To sum it up, the DataClick event isn't raised on the CartesianChart (but further down the Logical Tree) and therefore you can't handle it this way.

WPF Interaction Trigger CallMethodAction

I have Used Event Trigger in my View part given code as below. Almost all bindings are properly with ViewModel class - MainWindowViewModel, but for the method "CustomRibbonWindow_Loaded", its throwing runtime exception like :
An exception of type 'System.ArgumentException' occurred in Microsoft.Expression.Interactions.dll but was not handled in user code
Additional information: Could not find method named 'CustomRibbonWindow_Loaded' on object of type 'MainWindow' that matches the expected signature.
If there is a handler for this exception, the program may be safely continued.
I have tried putting putting TargetObject="{Binding ElementName=RR}" as well as TargetObject="{Binding}" also. But none of seems working.
My method way in VM is as below,
private void CustomRibbonWindow_Loaded()
{
...
}
Please guide how to resolve.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<si:CallMethodAction MethodName="CustomRibbonWindow_Loaded"/>
</i:EventTrigger>
</i:Interaction.Triggers>
MainWindow.xaml
<custom:CustomRibbonWindow x:Class="gDispatchApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Fluent="clr-namespace:Fluent;assembly=Fluent"
xmlns:custom="clr-namespace:gDispatchAppLib.Helpers.CustomUIControls;assembly=gDispatchAppLib"
xmlns:DockUI="clr-namespace:DockingLibrary;assembly=DockingLibrary"
xmlns:UserControls="clr-namespace:gDispatchAppLib.View.UserControls;assembly=gDispatchAppLib"
xmlns:AppWindows="clr-namespace:gDispatchAppLib.View.AppWindows;assembly=gDispatchAppLib"
xmlns:VM="clr-namespace:gDispatchAppLib.ViewModel.AppWindows;assembly=gDispatchAppLib"
xmlns:VM2="clr-namespace:gDispatchAppLib.ViewModel;assembly=gDispatchAppLib"
xmlns:PE="clr-namespace:gDispatchAppLib.ViewModel.CADQueues;assembly=gDispatchAppLib"
xmlns:wpfHelper="clr-namespace:gDispatchAppLib.WPFHelpers;assembly=gDispatchAppLib"
xmlns:conv="clr-namespace:gDispatch.MvvmValidation.WPF;assembly=gDispatch.MvvmValidation"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:si="http://schemas.microsoft.com/expression/2010/interactions"
Title="SERIS CAD"
MinHeight="300"
WindowState="Maximized" HorizontalContentAlignment="Stretch"
FlowDirection="LeftToRight" CaptionHeight="50"
IsIconVisible="False" WindowStyle="SingleBorderWindow"
xmlns:my="clr-namespace:System;assembly=mscorlib"
x:Name="RR">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<si:CallMethodAction MethodName="CustomRibbonWindow_Loaded" TargetObject="{Binding ElementName=RR}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
...
</custom:CustomRibbonWindow>
Do your method subscription like this.
private void Window_Loaded(object sender, RoutedEventArgs e)
// add the proper parameters
{
// code here
}
Here is the documentation: Link
I had the same problem. Just make it public. Thanks for your comment on the other answer :)
public void CustomRibbonWindow_Loaded()

How to Bind to window's close button the X-button

How I can bind one of my buttons on control to X Button that closes the window ?
I just want to create cancel button that just closes the window.
I am using MVVM in my code.
If possible to do it only in xaml, I just dont have any special code with the button click.
You can just call the Close() method, which will close the window.
private void MyButton_Click(object s, RoutedEventArgs e)
{
Close();
}
If it's WPF (and provided I remember right) you can just use CallMethodAction from the parent as a behavior and utilize Close() method via just XAML. Something like;
Parent Window x:Name="window"
namespaces;
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
-
<Button Content="Cancel">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction
TargetObject="{Binding ElementName=window}"
MethodName="Close"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Hope this helps.
MVVM solution without code-behind could also look like this:
View:
<Button Content="Cancel" Command="{Binding CloseWindowCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
ViewModel:
public ICommand CloseWindowCommand
{
get
{
return new RelayCommand<Window>(SystemCommands.CloseWindow);
}
}
But SystemCommands is from .net-4.5 so if you rock in some older version of .net you can also use following.
public ICommand CloseWindowCommand
{
get
{
return new RelayCommand<Window>((window) => window.Close());
}
}

Use a Command with TabItem

I would like to call a Command when a TabItem of my TabControl is selected.
Is there a way to do it without breaking the MVVM pattern ?
Use an AttachedCommand Behavior, which will let you bind a Command to WPF events
<TabControl ...
local:CommandBehavior.Event="SelectionChanged"
local:CommandBehavior.Command="{Binding TabChangedCommand}" />
Of course, if you're using the MVVM design pattern and binding SelectedItem or SelectedIndex, you could also run the command in the PropertyChanged event
void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedIndex")
RunTabChangedLogic();
}
It can be done using the following classes together:
EventTrigger class from the System.Windows.Interactivity namespace (System.Windows.Interactivity assembly).
EventToCommand class from the GalaSoft.MvvmLight.Command namespace (MVVM Light Toolkit assembly, for example, GalaSoft.MvvmLight.Extras.WPF4):
XAML:
<Window ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command
...>
...
<TabControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding TabSelectionChangedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TabItem>...</TabItem>
<TabItem>...</TabItem>
</TabControl>
...
</Window>
Create an instance of the command in the ViewModel constructor:
TabSelectionChangedCommand = new RelayCommand<SelectionChangedEventArgs>(args =>
{
// Command action.
});

Categories