Propagate shortcut from window to UserControls in WPF - c#

I want to catch shortcuts at the window level with KeyBindings and then raise an event that all UserControls can somehow subscribe to in order to get notified when a shortcut has been issued.
I tried to do this on the window:
<Window.InputBindings>
<KeyBinding Key="M"
Command="{x:Static someNamespace:RoutedCommands.ShortcutSingleKeyM}" />
</Window.InputBindings>
And then add CommandBindings in the usercontrol to "catch" the command:
<UserControl.CommandBindings>
<CommandBinding Command="{x:Static someNamespace:RoutedCommands.ShortcutSingleKeyM}" Executed="OnShortcutSingleKeyM"></CommandBinding>
</UserControl.CommandBindings>
Method OnShortcutSingleKeyMin UserControl's is not getting hit. After some reading I now understand RouteCommands bubble up the tree and that might be the reason this approach didn't work.
I need the UserControl to be able to listen to "OnShortcut" events coming from the window. I'm currently implementing it this way:
Add an attached property to each user control that wants to listen to such events. Have the container pass a higher level delegate kind of thing to notify the Usercontrol.
Does this make sense? I'm getting the feeling that I'm overthinking this, it should be simpler to achieve.

Propagating from the Window down the tree to UserControls it's not going to work (at least not without major plumbing). I implemented it this way:
View listens to input (via both KeyBindings and KeyDown events).
View owns an object (ShortcutMcShortcutFace) that allows UserControls to subscribe to a OnShortcutEvent kind of thing.
UserControls expose DependencyProperties of type ShortcutMcShortcutFace. The view passes its ShortcutMcShortcutFace instance to them. They subscribe to OnShortcutEvent.
UserControls handle the OnShortcutEvent (args include a shortcut identifier) whichever way they want.

Related

WPF Mvvm move mouse button event to Command

I am starting to use MVVM but I'm finding difficult to replicate simple things that I do with events: I have a canvas and I want to get the position of the mouse click so I did a command and the xaml is this
<Canvas x:Name="cnvLeft">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseDown">
<cmd:EventToCommand Command="{Binding CanvasClick}"
PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
However it pass only the mouse arguments, which is not enough because i need the sender, how can i fix this?
As recommended already: register a common event handler for the mouse click event.
MVVM is not concerned with code-behind. It's absolutely fine and even necessary to use code-behind.
Code-behind files are a compiler feature i.e. language feature (partial classes). They have nothing to do with application architecture. MVVM does not care about compilers - no design pattern does.
MVVM is also not concerned with commands (or data binding or any framework concept in general). Commanding is part of the framework's infrastructure and MVVM does not care about frameworks - no design pattern does.
MVVM does not mean to use commands. Events are usually just as good. So don't force commands. Instead of using interaction behaviors to convert an input event to a command, simply handle the event directly (of course in the view).
Controls must always be handled in the View of an MVVM application. The code-behind file of a control is a partial class. It's part of the control and therefore part of the View.
Implement the user input event handler in the hosting control's code-behind. Here you must implement the Canvas related logic (UI logic).
If you want to encapsulate the logic, you can move it along with the Canvas to a new custom Control (or UserControl).
MainWindow.xaml
<Window>
<Canvas PreviewMouseDown="OnCanvasePreviewMouseDown" />
</Window>
MainWindow.xaml.cs
private void OnCanvasePreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var canvas = sender as Canvas;
Point canvasClickPosition = e.GetPosition(canvas);
}

Access DataContext of Page from MainWindow (with Telerik)

I am relatively new to WPF and I have stumbled across a problem that I just can't seem to find a solution for.
I am sure that there is already a thread concerning a problem like that but in regard of my lacking knowledge it is very likely that I haven't found it or simply did not understand it.
My problem:
I am developing a WPF-application in C#. It's an Outlook-Styled application with a big MainWindow with a huge ViewModel and XAML.
What I was trying to do, is to split up the single codefiles a bit to make it a little bit more modular and compact.
I am using Telerik Controls and tried to outsource the content of single SplitContainers into Pages, which worked fine until now.
Today, a new situation came up which is somehow stupid and wasn't looking too complicated, but somehow I can't get it to work.
Situation:
I have a Treeview in my "MainWindow" and whenever I change the selection in there, I want to change a property on my Page that I have made a binding to.
So, when I click on an item in "TreeView_3" I want to set a property via EventHandler (SelectionChanged_TreeView3) on the DataContext of "Page_X".
If I had to do this on the MainWindow, I would typically do it like that:
UserViewModel uvm = mainGrid.DataContext as UserViewModel;
Then just call whatever property of specific UserViewModel (ViewModel of the MainWindow) I want to access.
I can't do this the same the same way for the page obviously since "mainGrid.DataContext" will always refer to the MainWindow, since this is where the eventhandler is called.
So what I need would be a little explanation on how to access the DataContext from a page with a different ViewModel.
If you need any code in order to explain, let me know.
You need to separate your concerns. In your code behind your should have only code that handles view related stuff. Most often my codebehind is empty.
In your ViewModels you should handle your data related logic. So instead of casting the datacontext in your code behind, handle a click with a Commandin your viewmodel.
Since there is no possibility to bind a command to the SelectedItemChanged of your TreeView you can use an interaction trigger.
<TreeView xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding Path=SomeCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
Ruven it is hard to say without some example code. But it could be that you need to implement INotifyPropertyChanged on the ViewModels?
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification
By calling OnPropertyChanged("PropertyName"); in the setter of a viewmodel property the ui will pick up the change.
Also make sure both views are referencing the same object and not copies of the same object.

How to handle closing window when pressing close icon on the top-right in WPF/MVVM?

I am able to bind buttons and menu items with ICommand and close the windows.
It is exactly as described in the tutorial WPF Apps With The Model-View-ViewModel Design Pattern - via the Command property accessible in XAML.
But it is not described or implemented in the tutorial how to close by pressing the standard 'Close' icon on the top-right of the window. I need to perform some clean up in my application.
My question is how to bind a Command to the close event, so that it is executed when the user presses the close icon (not buttons or menu items - I know how to manage such cases).
How should this be handled to avoid violating the MVVM approach?
Thanks!
The MVVM Light Toolkit contains a behaviour called EventToCommand, which gives you an easy way to bind a command to an event.
The following XAML snippet shows an example of how to get a command called "CloseCommand" to execute when the window's Closed event is raised:
<Window x:Class="EventToCommand.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
Title="MainWindow" Height="300" Width="500">
<!-- Make sure to put this tag directly inside the Window,
and not inside a child element, since it is the Windows that has the Closed event -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closed">
<cmd:EventToCommand Command="{Binding CloseCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<!-- Windows contents -->
</Window>
To get access to the EventToCommand behaviour, you need to download MVVM Light Toolkit from the project downloads page, and then reference the following DLLs:
GalaSoft.MvvmLight.dll
GalaSoft.MvvmLight.Extras.dll
System.Windows.Interactivity.dll
That is all that is needed.
Further instructions of how to get started with the toolkit can be found here.
I would bind a Command to the Application's Exit event
I like using the AttachedCommand behavior found here for binding Commands to Events, although I know you can also accomplish the same thing using Blend's Interaction Triggers.

How to bind event to method on ViewModel with event args passing. Silverlight 4

I have seen somewhere this is 100% doable using blend behaviors, cannot find example.
Even better example would be to pass event args and/or sender as CommandParameter to specific command.
<i:Interaction.Triggers>
<i:EventTrigger EventName="SizeChanged">
<ei:CallMethodAction MethodName="WndSizeChanged"
TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
WndSizeChanged method should be public and have same signature as event delegte it subscribing to.
a comfortable way to create behaviors is to use the DelegateCommand approach as in Prism:
Read here: Prism behavior
Caliburn Micro has some nice ways to do this.
From the docs:
<Button Content="Remove"
cal:Message.Attach="Remove($dataContext)" />
$eventArgs – Passes the Trigger’s EventArgs or input parameter to your Action. Note: This will be null for guard methods since the trigger hasn’t actually occurred.
$dataContext – Passes the DataContext of the element that the ActionMessage is attached to. This is very useful in Master/Detail scenarios where the ActionMessage may bubble to a parent VM but needs to carry with it the child instance to be acted upon.
$source – The actual FrameworkElement that triggered the ActionMessage to be sent.
$view - The view (usually a UserControl or Window) that is bound to the ViewModel.
$executionContext - The actions's execution context, which contains all the above information and more. This is useful in advanced scenarios.
$this - The actual ui element to which the action is attached.

How to make ContentControl listen to non-routed event inside its ContentTemplate

I have some reusable ContentControl, which acts as a pseudo-modal popup inside another view.
It is always there and only its visibility makes it appear or disappear.
When instantiated, within the ContentControl there will be a custom ContentTemplate, bound to some ViewModel and representing the content of the "modal popup".
<Dialogs:ModalDialog DialogHost="{Binding ElementName=layoutRoot, Mode=OneTime}"
Content="{Binding ViewModel.CurrentEditItem}"
IsShown="{Binding ViewModel.IsInEdit}">
<Dialogs:ModalDialog.ContentTemplate>
<DataTemplate>
<ItemEditor:ItemEditorView />
</DataTemplate>
</Dialogs:ModalDialog.ContentTemplate>
</Dialogs:ModalDialog>
Now I want to reach the following: the root of the ContentTemplate (here: ItemEditorView) should implement the following interface.
public interface ICloseMe
{
event EventHandler<EventArgs> CloseMe;
}
Whenever the CloseMe-Event is fired, the surrounding ModalDialog should be "closed" by setting its VisibilityProperty to Hidden.
The view within the popup (here ItemEditorView) should not care, whether it is shown in a ModalDialog or another context, i.e. it should not even know that such class exists. This excludes a walk through the Logical or Visual tree.
It shall only fire the CloseMe-Event, when Cancel/Save-Buttons are pressed.
Further, the mechanism should not be implemented/configured in the view instantiating the ModalDialog, the view should be as dumb as possible.
Instead, the "outer" ModalDialog should do the active part and listen to the CloseMe-event.
How can I implement this in a rather clean, MVVM-compliant way and without introducing unnecessary dependencies? Is there any event, occuring after a ContentTemplate is initialized, s.t. the ModalDialog could then evaluate, if the root of it extends ICloseMe?
How about making the ICloseMe include a Closed property that you could setup a trigger against in the XAML?

Categories