WPF Button Command not firing ICommand in ViewModel - c#

I have a View with a button as follows:
<Button Grid.Column="2" Grid.Row="1" Content="Test" Margin="10,4"
Command="{Binding DataContext.CmdTestButtonClicked}"
CommandParameter="{Binding}" />
In the view's code-behind, I set the DataContext to the ViewModel:
public GlobalSettings()
{
InitializeComponent();
...
DataContext = Helpers.IoCHelper.GlobalSettingsVM;
...
}
My ViewModel derives from a base class which exposes the ICommand:
public class GlobalSettingsVM : CollectionViewModel<GlobalSettings> { ... }
public abstract class CollectionViewModel<TModel> : IInstallModuleViewModel, INotifyPropertyChanged,
INotifyDataErrorInfo where TModel : Model, new()
{
...
public ICommand CmdTestButtonClicked
{
get
{
return _testButtonClicked ??
(_testButtonClicked = new RelayCommand(TestButtonClicked));
}
}
protected virtual void TestButtonClicked(object o)
{
// I never get here
}
}
I don't have any other issues using this pattern throughout my application, however all my other implementations have the Button within a ListView, so there I have to use RelativeSource={RelativeSource AncestorType={x:Type ListView}}.
Why would this command never fire? Do I need to set a RelativeSource here as well?

This
Command="{Binding DataContext.CmdTestButtonClicked}"
Implies that the Command will look for a property called DataContext in the object to which the button is bound.
If the DataContext of the button is a GlobalSettingsVM this should work:
Command="{Binding CmdTestButtonClicked}"

You could also use the MVVM Light toolkit wich is very convenient and helping on these situations.
You would get Something like this :
<Button Grid.Column="2" Grid.Row="1" Content="Test" Margin="10,4"
<i:Interaction.Triggers>
<i:EventTrigger EventName="OnClick" >
<Command:EventToCommand Command="{Binding DataContext.CmdTestButtonClicked}" CommandParameter="{Binding}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>

In my case, I was listening to PreviewMouseLeftButtonDown under constructor of xaml.cs class which was stopping command event callback to the view model.
this.PreviewMouseLeftButtonDown += (s, e) => DragMove();
Instead of above line, in xaml file for window added MouseLeftButtonDown="Window_MouseLeftButtonDown" click handler and handled window drag within it, as below
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}

Related

WPF InvokeCommandAction with ContentControl Changed

I have a content control:
<ContentControl Content="{Binding PageViewModel}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContentChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ContentControl>
I need to invoke MyCommand every time PageViewModel changes.
However the command is not getting triggered.
Is this possible to do?
You should handle this in the setter of the PageViewModel property:
private object pageViewModel;
public object PageViewModel
{
get { return pageViewModel; }
set
{
pageViewModel = value;
OnPropertyChanged();
MyCommand.Execute(null);
}
}
A ContentControl has no ContentChanged event. It has a protected OnContentChanged method that you could override if you create a custom ControlControl class though:
WPF: How to created a routed event for content changed?
You can create a custom event that you raise in this method.
But the MVVM way of doing this would be to invoke the command in the view model when the PageViewModel property is set.

How to pass the updated text by CommandParameter in TextChagned event?

This is an interesting case. I use MVVMLight to catch the event TextChagned of a textbox and pass it to a command in ViewModel and somehow the text value passed by CommandParameter is still the old text before the update.
Anyone knows how to get the new text?
<Grid>
<TextBox x:Name="myTextBox" HorizontalAlignment="Left"
Height="23" Margin="10,10,0,0" TextWrapping="Wrap" Text="Hello"
VerticalAlignment="Top" Width="120">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding Mode=OneWay,Path=TextChangedCommand}"
PassEventArgsToCommand="True"
CommandParameter="{Binding Path=Text,ElementName=myTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="Button" HorizontalAlignment="Left" Margin="352,214,0,0"
VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
public class MainViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real"
////}
}
private RelayCommand<string> _textChangedCommand;
public RelayCommand<string> TextChangedCommand
{
get
{
if (this._textChangedCommand == null)
{
this._textChangedCommand = new RelayCommand<string>(this.TextChanged);
}
return this._textChangedCommand;
}
}
private void TextChanged(string input)
{
MessageBox.Show(input);
}
}
I was digging into the code of MVVM Light and in EventToCommand.cs's Invoke method it seems that the parameter still has its old value. I didn't look further, but maybe this is a bug in MVVM.
There is a workaround you can do though.
Create a new class implementing IEventArgsConverter, something similar to this:
public class TextChangedEventArgsConverter : IEventArgsConverter
{
public object Convert(object value, object parameter)
{
var textChangedEventArgs = (TextChangedEventArgs) value;
return ((TextBox) textChangedEventArgs.Source).Text;
}
}
Add it to the Resources collection of your Window or ResourceDictionary where you have the EventToCommand that's not working.
<Window.Resources>
<textChanged:TextChangedEventArgsConverter x:Key="TextChangedEventArgsConverter" />
</Window.Resources>
And change the EventToCommand to the following:
<cmd:EventToCommand Command="{Binding Mode=OneWay,Path=TextChangedCommand}"
PassEventArgsToCommand="True"
EventArgsConverter="{StaticResource TextChangedEventArgsConverter}" />
So the specified EventArgsConverter will receive the actual TextChangedEventArgs and will extract the Text itself, which will be the correct value.
With these changes, I was able to achieve what you are looking for.
By the way: don't use CommandParameter and PassEventArgsToCommand="True" at the same time. As the documentation says, if you are setting PassEventArgsToCommand to true, the type parameter of your RelayCommand should be the event argument type of the event which is TextChangedEventArgs in this case.

Disposing a timer inside a ViewModel, upon closing the main window

Currently in my application, I have a RelayCommand which calls an action which starts a Timer. I have a separate command which the calls the Dispose() method to get rid of/release the timer. This all sits in the ViewModel counterpart of the project.
Using the MVVM pattern, how would I dispose of this Timer upon closing the window, as well as carrying out the regular/default operation of closing the window?
I am also using the MVVM-Light toolkit, if this is of any help.
An example of my current solution is shown below:
ViewModel
private static Timer dispatchTimer;
public MainViewModel()
{
this.StartTimerCommand = new RelayCommand(this.StartTimerAction);
this.StopTimerCommand = new RelayCommand(this.StopTimerAction);
}
public RelayCommand StartTimerCommand { get; private set; }
public RelayCommand StopTimerCommand { get; private set; }
private void StartTimerAction()
{
dispatchTimer = new Timer(new TimerCallback(this.IgnoreThis), null, 0, 15);
}
private void StopTimerAction()
{
dispatchTimer.Dispose();
}
View (xaml)
....
Height="896"
Width="1109"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid x:Name="mainGrid"
Width="Auto"
Height="Auto">
<Button x:Name="startTimerButton"
Content="Start Timer"
Command="{Binding StartTimerCommand}"/>
<Button x:Name="stopTimerButton"
Content="Stop Timer"
Command="{Binding StopTimerCommand}"/>
</Grid>
</Window>
View (Code-Behind)
public MainWindow()
{
this.InitializeComponent();
//Would a 'Closing +=' event be called here?
}
Closing the windows may or may not dispose your viewmodel, depending how you register it. One approach would be bind to the Loaded/Unloaded events from the view and do the work there:
View:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding PageLoadedCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unloaded">
<i:InvokeCommandAction Command="{Binding PageUnLoadedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
ViewModel:
public RelayCommand PageUnLoadedCommand { get; private set; }
...
PageUnLoadedCommand = new RelayCommand(() => OnPageUnLoadedCommand());
...
private void OnPageUnLoadedCommand()
{
//Unsubscribe and dispose here
}
You can add a destructor for your ViewModel like below
~public MainViewModel()
{
dispatchTimer.Dispose();
}

How to rewrite this DataGrid MouseLeftButtonUp binding to MVVM?

I have a working MouseLeftButtonUp binding that I works from the View.cs but that I cannot get working from the Viewmodel.cs
XAML:
<DataGrid x:Name="PersonDataGrid" AutoGenerateColumns="False"
SelectionMode="Single" SelectionUnit ="FullRow" ItemsSource="{Binding Person}"
SelectedItem="{Binding SelectedPerson}"
MouseLeftButtonUp="{Binding PersonDataGrid_CellClicked}" >
View.cs:
private void PersonDataGrid_CellClicked(object sender, MouseButtonEventArgs e)
{
if (SelectedPerson == null)
return;
this.NavigationService.Navigate(new PersonProfile(SelectedPerson));
}
PersonDataGrid_CellClicked method will not work from the ViewModel.cs. I've tried reading about Blend System.Windows.Interactivity but have not tried it as I wanted to avoid it while I'm still learning MVVM.
I've tried DependencyProperty and tried RelativeSource binding but could not get PersonDataGrid_CellClicked to navigate to the PersonProfile UserControl.
by using the Blend System.Windows.Interactivity assembly you are not violating any of the MVVM principles as long as no logic related directly to the view is used inside the defined command in the VM, here how to used it with the MouseLeftButtonUp Event:
<DataGrid>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp" >
<i:InvokeCommandAction
Command="{Binding MouseLeftButtonUpCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
and in the ViewModel define the MouseLeftButtonUpCommand :
private RelayCommand _mouseLeftButtonUpCommand;
public RelayCommand MouseLeftButtonUpCommand
{
get
{
return _mouseLeftButtonUpCommand
?? (_mouseLeftButtonUpCommand = new RelayCommand(
() =>
{
// the handler goes here
}));
}
}

How do I pass the Button as CommandParameter from XAML in a Xamarin.Forms Page?

I would like to pass a Xamarin.Forms.Button in it's own Command as the CommandParameter to my ViewModel. I know how to achieve this from the code behind e.g. ...
XAML (with most properties missed out for brevity)
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"/>
XAML.cs
public partial class MyTestPage
{
public MyTestPage()
{
InitializeComponent();
myButton.CommandParameter = myButton;
}
}
ViewModel
public class MyViewModel : ViewModelBase
{
public MyViewModel()
{
ButtonClickCommand = new Command(
(parameter) =>
{
var view = parameter as Xamarin.Forms.Button;
if (view != null)
{
// Do Stuff
}
});
}
public ICommand ButtonClickCommand { get; private set; }
}
... BUT is it possible to declare the CommandParameter in the XAML itself? Or in other words what is the binding syntax to set the parameter to the button itself?
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter="{[WHAT WOULD GO HERE]}"/>
btw I've already tried CommandParameter="{Binding RelativeSource={RelativeSource Self}}" and that didn't work.
Thanks,
Xamarin.Forms has a Reference markup extension that does just that:
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter="{x:Reference myButton}"/>
Although, this is the first time I'm seeing this need, and you probably can better separate your Views from your ViewModels and solve this by using a cleaner pattern, or by not sharing a command across buttons.
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter="{x:Reference myButton}"/>
In your ViewModel
public YourViewModel()
{
ButtonClickCommand= new Command(ButtonClicked);
}
private async void ButtonClicked(object sender)
{
var view = sender as Xamarin.Forms.Button;
}
A simple method would be:
In XAML:
<Button Text="BUTTON-TEST"
Clicked="Avaliar"
CommandParameter="like"/>
In C#:
private void Avaliar(object sender, EventArgs e)
{
Console.WriteLine(((Button)sender).CommandParameter);
}
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter={Binding RelativeSource=
{RelativeSource
Mode=FindAncestor,
AncestorType={x:Type Button}}/>
Should work, but im still at a loss why you need the button? The point of MVVM is to seperate Data and UI. everything you should need todo to the button can be done via DataBindings.
If the above doesnt work, the only other thing to try is to give the button an x:Key and CommandParamter = {StaticResource 'x:Key'}

Categories

Resources