I'm using MVVM Light in WPF application and trying to bind Window's Closing event with a command implemented in ViewModel.
But when I do it in XAML the event handler is called randomly, right after starting the app it is usually firing, but after a 20 or 30 minutes of app running, the app closes immediately without firing "Closing" handler.
<Window
[...]
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"
[...]
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding Source={StaticResource Locator},
Path=Main.ExitAppCmd,
Mode=OneWay}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Window>
The command is declared in MainViewModel as:
public RelayCommand<CancelEventArgs> ExitAppCmd { get; private set; }
and is set to event handler by:
ExitAppCmd = new RelayCommand<CancelEventArgs>((args) => ExitAppHandler(args));
When the app is closed there are no exceptions.
I was trying to set breakpoint on the event handler, but in cases when the app closes immediately, the breakpoint is not executed.
When I bind the same handler in code behind, the handler is always fired.
private void Window_Closing(object sender, CancelEventArgs e)
{
var model = DataContext as MainViewModel;
model.ExitAppHandler(e);
}
How can I debug what's wrong with XAML binding ?
Related
I decided to make a custom title for my program in WPF and encountered difficulties.
I started to study MVVM pattern and its essence to get rid of using standard events in View.
I wanted to make buttons to close, minimize and maximize window, but ran into difficulties. I can't understand where the logic of these buttons should be.
If you don't use standard events, but use commands, it won't work, because ViewModel doesn't know anything about the window. And I don't want to use events.
I found this solution for window close button
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction MethodName="Close"
TargetObject="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType=Window}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
But I don't know how to do the other two buttons the same way. I tried to find other MethodNames that can be used here, but I found only method Hide, but it does not suit me, because it hides the window completely, it is neither on the taskbar nor in the tray, but it is still running and visible in the task manager.
Can you tell me how I can do the same window minimizing and resizing through XAML code?
UPD:
I found a way to minimize the window, but I still do not know how to make a button that will change the WindowsState to Normal if the WindowState Maximized and vice versa.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:ChangePropertyAction PropertyName="WindowState"
TargetObject="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Value="{Binding Source={x:Static sys:WindowState.Minimized}}"
/>
</i:EventTrigger>
</i:Interaction.Triggers>
In MVVM any UI related logic must be in the View. This should be clear.
Commands are not View Model only. Your View can also define commands. Special ICommand implementation is the RoutedCommand. Don't think that because it is a command, then it must be handled in the View Model.
You should not use the Interaction.Triggers, especially not in your scenario. Additionally, if you are not firm with MVVM, Interaction.Triggers will very likely introduce code smells.
Simply create an event handler for the Button.Click event in your Window class' code-behind e.g., MainWindow.xaml.cs file.
Also, there is no reason to bind to a static variable or a constant or an enum. Just reference it directly:
<i:ChangePropertyAction Value="{x:Static sys:WindowState.Minimized}" ... />
However, to solve your problem simply add an event handler in your code-behind:
MainWindow.xaml
<Button Click="OnMaximizeButtonClicked"
Content="Toggle Maximize" />
MainWindow.xaml.cs
// Toggle the WindowState between Maximized and Normal
private void OnMaximizeButtonClicked(object sender, RoutedEventArgs e)
=> this.WindowState = this.WindowState == WindowState.Normal
? WindowState.Maximized
: WindowState.Normal;
Alternatively, use routed commands (How to: Create a RoutedCommand):
MainWindow.xaml
<Button Command="{x:Static local:MainWindow.ToggleMaximizeStateCommand}"
Content="Toggle Maximize" />
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static RoutedCommand ToggleMaximizeStateCommand { get; } = new RoutedCommand("ToggleMaximizeStateCommand", typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
// Register the command handler
var toggleMaximizeStateCommandBinding = new CommandBinding(
ToggleMaximizeCommand,
ExecuteToggleMaximizeStateCommand,
CanExecuteToggleMaximizeStateCommand);
this.CommandBindings.Add(toggleMaximizeCommandBinding);
}
// Toggle the WindowState between Maximized and Normal
private void ExecuteToggleMaximizeStateCommand(object sender, ExecutedRoutedEventArgs e)
=> this.WindowState = this.WindowState == WindowState.Normal
? WindowState.Maximized
: WindowState.Normal;
private void CanExecuteToggleMaximizeStateCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = true;
}
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.
i want to bind an event using MetroEventToCommand library.
http://metroeventtocommand.codeplex.com/
It calls the method however it doesn't give the control as parameter. It's says it's null.
this is what i have.
<ScrollViewer x:Name="test">
<ListView/>
<metroEventToCommand:EventToCommandManager.Collection>
<metroEventToCommand:EventToCommand Command="{Binding RefreshCommand}" Event="ViewChanging" CommandParameter="{Binding ElementName=test, Mode=TwoWay}"/>
</metroEventToCommand:EventToCommandManager.Collection>
</ScrollViewer>
public RelayCommand<ScrollViewer> RefreshCommand { get; set; }
private void init()
{
RefreshCommand = new RelayCommand<ScrollViewer>(Refresh);
}
private void Refresh(ScrollViewer o)
{
if (o != null)
{
}
}
thanks in advance.
OK... not quite an answer, but I can't put code in a comment. I put together a quick app using MVVM Light and using the Interactivity library instead of metroToEventCommand.
I can pass it from hooking up an event handler (of course)
private void onViewChanging(object sender, ScrollViewerViewChangingEventArgs e)
{
vm.RefreshCommand.Execute(this.test);
}
I can also get it to fire the command WITH the parameter using the Loaded event or PointerEntered event
<ScrollViewer x:Name="test" >
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="PointerEntered">
<Core:InvokeCommandAction Command="{Binding RefreshCommand, Mode=OneWay}" CommandParameter="{Binding ElementName=test}" />
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<ListView x:Name="myList"/>
</ScrollViewer>
However, the Interactivity dll throws an exception (Cannot add instance of type 'Microsoft.Xaml.Interactions.Core.EventTriggerBehavior' to a collection of type 'Microsoft.Xaml.Interactivity.BehaviorCollection'.) when trying to use the ViewChanging, ViewChanged, DirectManipulationCompleted, etc. events - whether a parameter is used or not. ManipulationCompleted does not fire the command (I've seen hints that commands cannot be bound to routed events).
So, it would seem that the concept works - but only for certain event types. I'm sorry that isn't a solution to your challenge, but hopefully it is some additional information to help your attempts.
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.
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.
});