Use a Command with TabItem - c#

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.
});

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.

Best practice to handle ListView ItemClick in UWP using MVVM

I need to open a new view (item details) on mouse double click in ListView in UWP using MVVM. In WPF I used a command with a parameter and EventTrigger but Microsoft does not recommended to use it in UWP:
Triggers, EventTrigger, Actions and BeginStoryboard are not commonly used. These API mainly exist for compatibility in XAML originally used for Microsoft Silverlight...For events in control templates, use visual states and VisualStateManager.
As I understood it is used when you need to change visual state of the control but I need to open a new view.
How can I use VisualStateManager for my purpose?
There is how my XAML looked in WPF:
<ListBox x:Name="PersonsListControl" Grid.RowSpan="3" Grid.Row="0" Grid.Column="2"
ItemsSource="{Binding Path=PersonsProvider}"
ItemsPanel="{StaticResource PersonsListPanelTemplate}"
ItemTemplate="{StaticResource PersonsListItemTemplate}"
SelectedItem="{Binding SelectedPerson}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction
Command="{Binding GetPersonDetailsCommand}"
CommandParameter="{Binding SelectedPerson}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
In UWP you can use {x:Bind ...} :
<ListBox ...
DoubleTapped="{x:Bind HandleDoubleTapped}" />
And in your ViewModel just create a method :
public void HandleDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
// your logic
}
References :
DoubleTapped
ListBox
EDIT:
#JörgenSigvardsson pointed out that x:Bind do not bind directly to the DataContext and you should create a proxy property/properties to access particular data from your page.
More on that can be read here

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.

Using interaction trigger to call visibility changed method WPF

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.

Bind event to ViewModel

I am using WPF and PRISM framework for my application. The pattern I am using is MVVM (Model - View - ViewModel) and I am trying to bring the MouseLeftButtonUp event from the code-behind in the View to the ViewModel (so the event will be according the MVVM rules). For now I have this:
View.xaml:
<DataGrid x:Name="employeeGrid" Height="250" Margin="25,0,10,0" ItemsSource="{Binding DetacheringenEmployeesModel}" IsReadOnly="True" ColumnHeaderStyle="{DynamicResource CustomColumnHeader}" AutoGenerateColumns="False" RowHeight="30">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding EmployeeGrid_MouseLeftButtonUp}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
View.xaml.cs (code-behind):
public partial class UC1001_DashBoardConsultants_View
{
public UC1001_DashBoardConsultants_View(UC1001_DashboardConsultantViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
ViewModel.cs:
public void EmployeeGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// insert logic here
}
The main idea is, when I click on a cell in the DataGrid, the event will fire. I first tried it in the code behind, and it worked. I got so far with the EventTriggers, but when I debug and click on a cell, my debugger doesn't come into the method.
Does anyone have an idea how to fix this? Thanks in advance!
PS: Does it also work with the (object sender) parameter when I do it like that? Because I need the DataGrid in my ViewModel to get the ActiveCell I just clicked on.
EDIT:
The event-binding worked with the Command!
I have this in my DataGrid:
<DataGridTextColumn Header="Okt" Width="*" x:Name="test" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Tag" Value="{Binding Months[9].AgreementID}"/>
How can I bind the Tag property to the ViewModel? I know it's already bound from the ViewModel, but as you can see the value comes from an Array/List and per column the value is different.
InvokeCommandAction requires the ICommand to be bound not an event handler as you've bound (EmployeeGrid_MouseLeftButtonUp).
So you can introduce a command in ViewModel and bind to it:
View Model:
public ICommand SomeActionCommand { get; set; }
XAML:
<i:InvokeCommandAction Command="{Binding SomeActionCommand}" />

Categories