Disposing a timer inside a ViewModel, upon closing the main window - c#

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

Related

key strokes events dont execute in C# WPF

I'm using WPF MVVM pattern with Prism
im trying to bind keybind to some command
----View---
<Canvas Background="Red" Grid.Row="1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<prism:InvokeCommandAction Command="{Binding KeyDownCmd}"/>
</i:EventTrigger>
<i:EventTrigger EventName="KeyDown">
<prism:InvokeCommandAction Command="{Binding KeyUpCmd}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
----View Model----
#region Commands
public DelegateCommand<KeyEventArgs> KeyDownCmd { get; private set; }
public DelegateCommand<KeyEventArgs> KeyUpCmd { get; private set; }
#endregion
#region Ctor
public GameViewModel()
{
KeyDownCmd = new DelegateCommand<KeyEventArgs>(KeyDownExecute);
KeyUpCmd = new DelegateCommand<KeyEventArgs>(KeyUpExecute);
}
private void KeyUpExecute(KeyEventArgs obj)
{
//some code here
}
private void KeyDownExecute(KeyEventArgs obj)
{
//some code here
}
i also tried to bind to code-behind like this KeyDown="Canvas_KeyDown" and nothing
tried to use PreviewKeyDown /PreviewKeyUp and nothing
also tried to bind the key command to the gird above the canvas and to the userControl and nothing
P.S
im navigating between pages with viewInjection as described here
A Canvas doesn't raise any key stroke events unless it's Focusable and focused.
You can make it focusable by setting the property in the XAML but you still have to focus it at some point, for example when it's clicked:
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Canvas canvas = (Canvas)sender;
Keyboard.Focus(canvas);
}
XAML:
<Canvas Focusable="True" Background="Red"
PreviewKeyDown="Canvas_PreviewKeyDown"
MouseLeftButtonDown="Canvas_MouseLeftButtonDown">
...
</Canvas>
You should probably reconsider your approach and handle the PreviewKeyDown of a parent element or a focusable child element of the Canvas instead.

WPF Button Command not firing ICommand in ViewModel

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

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.

How to Update LisBox ItemSource On Button Click

I have a ListBox;
<ListBox Grid.Row="1" Grid.ColumnSpan="2"
x:Name="customerListBox"
ItemsSource="{Binding Customers}"
DisplayMemberPath="Customername"
SelectionMode="Single" Width="200"/>
Customers is public ObservableCollection<Customer> Customers { get; private set; }
Now I am binding ListBox Selected Item to a Text Box:
<TextBox Text="{Binding ElementName=customerListBox,
Path=SelectedValue.Customername,Mode=OneWay}"/>
I have made it one-way as there I want to commit the changes only on click of my Save button and not when the value change on TextBlock.
<Button Content="Save" Grid.Column="0" Grid.Row="3" Width="80" Height="30"
Command="{Binding SaveCommand}"
You're going the wrong way about it, imho.
Don't bind TextBox directly against selected item. Rather, create a new command, SelectionChangedCommand, and new property, CurrentlyActiveText, bind it against TextBox.
The logic would be simple:
SelectionChangedCommand = new RelayCommand(selectedItem=> {
// todo: ask user if he wants to commit the previous changes?!
CurrentlyActiveText = (string)selectedItem;
})
SaveCommand = new RelayCommand(() => {
yourObservable[SelectedIndex] = CurrentlyActiveText;
});
Perhaps a nicer way of doing this is using Triggers to fire a Command on the ListBox SelectionChanged event. Putting logic in a property setter always feels a bit wrong to me
<ListBox...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding CustomerListBoxSelectionChanged}" CommandParameter="{Binding ElementName=customerListBox,Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
In your view model:
public Customer CurrentCustomer { get; set; }
public RelayCommand<Customer> CustomerListBoxSelectionChanged { get; set; }
private void OnCustomerListBoxSelectionChanged(Customer customer)
{
CurrentCustomer = customer;
NewCustomerName = customer.CustomerName;
}
private string _newCustomerName;
public string NewCustomerName
{
get { return _newCustomerName; }
set
{
if (_newCustomerName == value)
return;
_newCustomerName = value;
RaisePropertyChanged("NewCustomerName");
}
}
Your TextBox in your XAML becomes:
<TextBox Text="{Binding NewCustomerName}"/>
And finally, your SaveCommand calls a method that simply does...
private void OnSave()
{
CurrentCustomer.CustomerName = NewCustomerName;
}
Note you will also need to make sure that your CustomerName in your Customer object is raising PropertyChanged events in order to reflect the update in your ListBox
Note that doing it this way also saves you a futher lookup the ObservableCollection to perform an update. It'll save you some time - any performance gain is always good :)

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