c# mvvm proper event disposal - c#

I have implemented MVVM pattern in my WPF windows. I have a subwindow which I call the following way (from another ViewModel):
cmd_Show = new DelegateCommand(
(sender) =>
{
frm_Strediska _window = new frm_Strediska();
frm_StrediskaViewModel vm = new frm_StrediskaViewModel(ZakladneStrediska, _window);
_window.DataContext = vm;
_window.Owner = App.Current.MainWindow;
_window.ShowDialog();
});
Here under frm_StrediskaViewModel I hook to a Window.Closing event in a following way:
public frm_StrediskaViewModel(ObservableCollection<DefaultStrediska> _Strediska, frm_Strediska _Window)
{
Window = _Window;
Strediska = _Strediska;
InitializeCommands();
Window.Closing += Window_Closing;
}
The thing I am not sure about is, that when I close this SubWindow, if my Window.Closing event unhooks automatically, or I have to override Dispose() event on the ViewModel (that's what I am doing now):
protected override void Dispose(bool disposing)
{
Window.Closing -= Window_Closing;
this.Dispose();
}
Is this a good approach, or is it completely unnecessary?

There is a way to do this with neat bindings, but it requires the Blend SDK. You need the System.Windows.Interactivity.dll for this. The first thing required is to add the namespace declaration:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
With this import you gain the possibility to bind commands to events like so:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding CloseCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Adding this to your window will cause the command bound with CloseCommand to be executed once the Closing-Event is raised by the window.
So you don't need to do any binding to the event yourself. You can just create the required property in your ViewModel that returns the command. This command has to do what ever is required during the closing. So basically what you got in your event handler currently.
public readonly ICommand CloseCommand
{
get { return /* Your closing command here */; }
}
I hope that helps.

Related

Keep a MenuItem's IsChecked property synced with a bool

I have a simple WPF Application that has a menu on top. I want to add an option to make the main window to stay on top of other windows.
I created a bool named setTopMost in Property > Settings tab for users to save this setting. So, the setting will be remembered even after the app is terminated.
Everything is working as intended, I can click on the option or use the shortcut of Ctrl+T to make the window to stay on top, but I cannot get a check mark to appear next to the option when the window is on top of other windows.
I've read several articles regarding binding IsChecked to a bool, but I could not solve this problem on my own.
Here are my codes.
MainWindow.xaml
<Window.InputBindings>
<KeyBinding Gesture="Ctrl+T" Command="{Binding TopMostCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Window.InputBindings>
<MenuItem Header="_Options">
<MenuItem x:Name="Menu_AlwaysOnTop" Header="Always On _Top" IsCheckable="True" IsChecked="{Binding isTopMost}" Command="{Binding TopMostCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" InputGestureText="Ctrl+T" />
</MenuItem>
MainWindow.xaml.cs
namespace WPF_Practice
{
public partial class MainWindow : Window
{
public bool isTopMost;
public MainWindow()
{
InitializeComponent();
DataContext = new PracticeDataContext();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
isTopMost = Properties.Settings.Default.setTopMost;
Topmost = Properties.Settings.Default.setTopMost;
}
}
public class PracticeDataContext
{
public ICommand TopMostCommand { get; } = new TopMostCommand();
}
public class TopMostCommand : ICommand
{
public void Execute(object parameter)
{
var TopMostClass = new MainWindow();
TopMostClass.WindowTopMost();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
}
Please mind that I am doing this as a hobby and quite new to this.
The main reason why your MenuItem isn't updating properly is because you set the DataContext of the Window to PracticeDataContext.
public MainWindow()
{
InitializeComponent();
DataContext = new PracticeDataContext(); <--
}
This means that your bindings in MainWindow.xaml are going to be looking for properties in PracticeDataContext.
In this case you would want to have an IsTopMost property in your PracticeDataContext class in order for the binding to work.
Since IsTopMost isn't set until the Loaded event handler fires, you should implement INotifyPropertyChanged in your PracticeDataContext class so that your IsTopMost binding will get notified when it is set from settings.
A quick search on INotifyPropertyChanged will show you lots of examples. It's pretty easy.

Commands not working / firing on events built in my customized UserControl

I'm working on this (MVVM) Windows 8.1 metro app where I'm using a UserControl that I built separately, this UserControl has its own events that I built for it.
My problem is: when I try in the global app to use commands on those they never fires, although the events work!
The UserControl and its events :
public delegate void PlayClickedEventHandler(object sender, RoutedEventArgs e);
...
public event PlayClickedEventHandler PlayClicked;
The PlayClicked event is fired when a button Inside the UserControl is clicked
private void PlayButton_OnClick(object sender, RoutedEventArgs e)
{
if (PlayClicked != null)
PlayClicked(this, e);
}
The Global Windows 8.1 MVVM App :
The View :
<mediaPlayerControl:PlayerControl x:Name="UserControl">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="PlayClicked">
<core:InvokeCommandAction Command="{Binding OnPlayClicked, Mode=TwoWay}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</mediaPlayerControl:PlayerControl>
The ViewModel :
public RelayCommand OnPlayClicked { get; set; }
OnPlayClicked = new RelayCommand(() =>
{
//Stuff to do here that seem not to be done
});
Am I missing something ??
I guess this could explain it (from the docs on EventTriggerBehavior)
The following events are supported:
(list of 11 events)
Consider implementing a custom behavior to respond to other events.
Why not use an EventTrigger instead? (Also, note that the "OnPlayClicked" binding should really be one way, although this shouldn't make a difference.)
<mediaPlayerControl:PlayerControl x:Name="UserControl">
<interactivity:Interaction.Triggers>
<uixaml:EventTrigger EventName="PlayClicked">
<core:InvokeCommandAction Command="{Binding OnPlayClicked}"/>
</uixaml:EventTrigger>
</interactivity:Interaction.Triggers>
</mediaPlayerControl:PlayerControl>

How to add event handler in MVVM in Windows Phone 8?

All
I have a issue. Now I'm using MVVM framework to develop Windows Phone 8 app. I just want to when I press the button, then begin to record something, when release the button, stop recording, I used InvokeCommandAction to bind the command in ViewModel, this is the code as follow
Xaml:
<Button x:Name="BtnRecord" Height="50" Width="180" Background="#D43637" Content="Record" Margin="20,0,0,0" Style="{StaticResource BasicButtonStyle}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding StartRecordCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding EndRecordCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
ModelView:
public ICommand StartRecordCommand
{
get
{
return new DelegateCommand(StartRecord);
}
}
public ICommand EndRecordCommand
{
get
{
return new DelegateCommand(EndRecord);
}
}
private void StartRecord(object parameter){}
private void EndRecord(object parameter){}
When I debug the app, I found it didn't fire the neither the MouseLeftButtonDown nor MouseLeftButtonUp events, so I register the two event handler as follow:
BtnRecord.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(Button_MouseLeftButtonDown), true);
BtnRecord.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(Button_MouseLeftButtonUp), true);
private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
}
private void Button_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
}
OK, keep on going, but the next problem is coming, it didn't fire the ICommand in ViewModel, it called the Button_MouseLeftButtonDown, oh, god, I crazy
Anyone know how to call the ICommand in ViewModel?
Or another way to implement it?
You can use ICommand.Execute. So, your handler should be
private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
StartRecordCommand.Execute(null);
}
Try binding the IsPressed property of the Button with a TwoWay binding to a bool property called IsRecording in your ViewModel and start/stop your recording logic from inside the setter based on the new bool value (true would mean start).
Let me know if it works.

How do I close a window from its user control view model?

I have a window which hosts various UserControl's as pages. Is it possible to close the window which I have no reference to from within the usercontrol's datacontext?
Simplified details:
SetupWindow
public SetupWindow()
{
InitializeComponent();
Switcher.SetupWindow = this;
Switcher.Switch(new SetupStart());
}
public void Navigate(UserControl nextPage)
{
this.Content = nextPage;
}
SetupStart UserControl
<UserControl x:Class="...">
<UserControl.DataContext>
<local:SetupStartViewModel/>
</UserControl.DataContext>
<Grid>
<Button Content="Continue" Command="{Binding ContinueCommand}"/>
</Grid>
</UserControl>
SetupStartViewModel
public SetupStartViewModel()
{
}
private bool canContinueCommandExecute() { return true; }
private void continueCommandExectue()
{
Switcher.Switch(new SetupFinish());
}
public ICommand ContinueCommand
{
get { return new RelayCommand(continueCommandExectue, canContinueCommandExecute); }
}
I managed to find a solution from an answer here: How to bind Close command to a button
View-Model:
public ICommand CloseCommand
{
get { return new RelayCommand<object>((o) => ((Window)o).Close(), (o) => true); }
}
View:
<Button Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Content="Close"/>
I do this by having a RequestClose event in my ViewModel that it can raise when it wants the view to close.
This is then hooked up to the window's Close() command by the code that creates the window. eg
var window = new Window();
var viewModel = new MyViewModel();
window.Content = viewModel;
viewModel.RequestClose += window.Close;
window.Show()
This way all the stuff to do with window creation is handled in one place. Neither the view, or the viewmodel know about windows.
Inside your user control you can find a reference to the window that's hosting it with a static method on the Window class.
var targetWindow = Window.GetWindow(this);
targetWindow.Close();
Edit:
If you have no reference to the user control that the data context is being used in you don't have a huge amount of options, if there is just 1 application window you can get away with
Application.Current.MainWindow.Close()
If there are many windows in your application and the one you want to close is in focus you could find that with something like
public Window GetFocusWindow()
{
Window results = null;
for (int i = 0; i < Application.Current.Windows.Count; i ++)
if (Application.Current.Windows[i].IsFocused)
{
results = Application.Current.Windows[i];
break;
}
return results;
}
Finally I guess (though this is pretty out there) you could loop through the applications window classes, checking the data context of every object in the visual tree until you find the reference you're after, the window can be closed from there.

WPF RightClick MouseBinding on release?

How can I enable a mouse binding to the release of the right button? At the moment I have the following code in xaml which is linked to closing the wpf window. The problem here is that because it reacts to the rampup of the click when closing the window it activates a context menu on the desktop.
<MouseBinding Command="Close" MouseAction="RightClick" />
The MouseBinding does not support mouse up actions, only mouse down actions, so you simply cannot do what you want to do using a MouseBinding. The simplest alternative is a code-behind event handler for the MouseRightButtonUp event on the same element you would have added the MouseBinding as an InputBinding to. But I suspect you are avoiding the event handler approach for your own reasons, but you should clarify if that is your intention.
The remaining option available to use is some form of attached behavior. There are many ways to do this but I'll use the fairly standard System.Windows.Interactivity from Blend behaviors. All you have to do is attach an event trigger for right mouse button up and invoke the close command. Everything you need to do this is in the SDK but unfortunately the feature to invoke a command called InvokeCommandAction doesn't properly support routed commands so I've written an alternative called ExecuteCommand.
Here is some sample markup:
<Grid Background="White">
<Grid.CommandBindings>
<CommandBinding Command="Close" Executed="CommandBinding_Executed"/>
</Grid.CommandBindings>
<!--<Grid.InputBindings>
<MouseBinding Command="Close" MouseAction="RightClick"/>
</Grid.InputBindings>-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonUp">
<utils:ExecuteCommand Command="Close"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel>
<TextBox Text="Some Text"/>
</StackPanel>
</Grid>
Your old method is commented out and the new method is below it.
Here is the code-behind just to hook up the routed command:
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
Close();
}
Finally, here is the implementation of ExecuteCommand:
public class ExecuteCommand : TriggerAction<DependencyObject>
{
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommand), null);
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(ExecuteCommand), null);
public UIElement CommandTarget
{
get { return (UIElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(UIElement), typeof(ExecuteCommand), null);
protected override void Invoke(object parameter)
{
if (Command is RoutedCommand)
{
var routedCommand = Command as RoutedCommand;
var commandTarget = CommandTarget ?? AssociatedObject as UIElement;
if (routedCommand.CanExecute(CommandParameter, commandTarget))
routedCommand.Execute(CommandParameter, commandTarget);
}
else
{
if (Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
}
}
}
If you are not using routed commands but are using say, an MVVM RelayCommand, you can don't need ExecuteCommand and you can use InvokeCommandAction instead.
This example uses behaviors. If you are not familiar with behaviors, install the Expression Blend 4 SDK and add this namespace:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
and add System.Windows.Interactivity to your project.

Categories