CanExecute not raised when context menu opens - c#

I know there are a handful of related questions but none of those helped me finding the issue.
Most answers suggest to implement CanExecuteChanged as shown in this answer. Well, that's not the solution to my problem. I've got an implementation of RelayCommand similar to Josh Smith's implemenation. (Similar, because our implementation adds more details but the core implementation is the same.)
While searching the Internet I also learned that if there is no focused element, the routing will stop at the ContextMenu and wouldn't reach the MenuItem. A solution that would help in that case is shown here.
However, I checked with Snoop if there really isn't any focused element and learned this is not the issue. And the fix didn't help anyway.
Besides, I simulated that issue in a test project and was able to fix it. So the fix generally works, it's just not helping me. I think there's still a chance, however, that I have to adapt the fix slightly to get it working. I tried MyControl instead of ContextMenu as AncestorType and I tried PlacementTarget.Tag instead of just PlacementTarget as Path but I wouldn't know what else to try to get it working (assuming that this is the bug).
Funny enough, it even doesn't work when I call CommandManager.InvalidateRequerySuggested() manually. I added a command that is raised on ContextMenuOpening. I thought that this would force the CanExecute to be executed but it seems I'm mistaken.
So, I'm now looking for further reasons why a CanExecute handler isn't raised when a ContextMenu is opened and how I would fix that.
Here's my XAML code (including EventTrigger for ContextMenuOpening):
<MyControl>
<MyControl.ContextMenu>
<ContextMenu>
<MenuItem Header="..."
Command="{Binding MyCommand}"
CommandParameter="{Binding}"
CommandTarget="{Binding Path=PlacementTarget,
RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</MyControl.ContextMenu>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContextMenuOpening">
<i:InvokeCommandAction Command="{Binding OnContextMenuOpening}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MyControl>
Here's the definition of MyCommand and the (Can)Execute handlers:
internal static readonly ICommandEx MyCommand =
new RelayCommand(OnMyCommand, OnCanMyCommand);
private static void OnMyCommand(object parameter) { ... }
private static bool OnCanMyCommand(object parameter) { ... }
Here's my OnContextMenuOpening handler where I tried to force MyCommand's CanExecute to be raised:
private static void OnContextMenuOpening(object parameter)
{
CommandManager.InvalidateRequerySuggested();
}

You are incorrectly listening on OnContextMenuOpening on the ContextMenu control. It will never fire! Instead, listen on this very event on your MyControl control.

Related

Is it possible to use invoke a command using an Microsoft.Xaml.Behaviors.EventTrigger with attached events?

I am trying to use an attached event to invoke an ICommand.
I am using the Microsoft.Toolkit.Mvvm NuGet package, along with the Microsoft.Xaml.Behaviors.Wpf Nuget package.
I have had success starting a Storyboard using the <BeginStoryBoardAction /> within the <FOO.Triggers /> by defining an <EventTrigger /> and setting the RoutedEvent equal to the name of the attached event in question.
However, to my knowledge, there exists no way to invoke an ICommand using anything provided within the <EventTrigger />. By which I mean, there is nothing that I can use within the body of the <EventTriggers.Actions /> block (similar to <Behaviors.InvokeCommandAction />) which will result in the ICommand being invoked.
In compliance with the Minimal, Complete and Verifiable example, to demonstrate what I am trying to achieve, you can reference the project on GitHub - https://github.com/AbbottWC/MCVE_AttachedEventFailure.
Or
Open Visual Studio. Create a WPF Application (Targeting .Net Core 3.1)
Tools -> NuGet Package Manager -> Manage Packages for this solution
Add the Microsoft.Toolkit.Mvvm (for the RelayCommand class) and the Microsoft.Xaml.Behaviors.Wpf packages.
In the App.Xaml.cs
private RelayCommand testCommand = new RelayCommand(( ) => MessageBox.Show("Event Captured!", "Success!"));
public RelayCommand TestCommand => testCommand;
In the MainWindow.xaml
Define the namespace xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
Rename local to l
Add the following to the body of the XAML before the closing </Window> tag:
<i:Interaction.Triggers>
<!--This will work, and is present only to prove the problem lies not with the Command or how it is being accessed.-->
<i:EventTrigger EventName="MouseEnter">
<i:EventTrigger.Actions>
<i:InvokeCommandAction Command="{Binding TestCommand, Source={x:Static l:App.Current}}" />
</i:EventTrigger.Actions>
</i:EventTrigger>
<!--This will not work, and is meant to provide an example of the problem.-->
<i:EventTrigger EventName="Mouse.MouseEnter">
<i:EventTrigger.Actions>
<i:InvokeCommandAction Command="{Binding TestCommand, Source={x:Static l:App.Current}}" />
</i:EventTrigger.Actions>
</i:EventTrigger>
</i:Interaction.Triggers>
So to restate my question, is what I am trying to achieve here possible? Is it just not possible to use an attached event in this way?
Thanks.
From what I understand, the EventTrigger can only listen to events that the source object "owns". So to answer your question, this is not possible using the EventTrigger class.
If you look at the source, when you pass Mouse.MouseEnter, it tries to get that event from the target type. Also, since you did not specify the target, it defaults to the AssociatedObject, which is Window in your case.
Type targetType = obj.GetType();
EventInfo eventInfo = targetType.GetEvent(eventName);
Now the problem I find, is if it cannot find the event, it only throws an exception when the source object is not null, and in your case you haven't specified one, so it silently fails. Not sure why the designers made it this way.
Now, I did find this blog post, which describes exactly how you can workaround this problem:
https://sergecalderara.wordpress.com/2012/08/23/how-to-attached-an-mvvm-eventtocommand-to-an-attached-event/
Basically, you inherit from EventTriggerBase<T> and in the OnAttached method you need to call AddHandler on your AssociatedObject to add the event.

Setting focus to textbox upon method-execution

I have 2 different commands called; whom each perform some actions and reveal a form. I want them to set focus to the first textbox in that form when they set the Visible property to true.
I've seen all kinds of SO-articles on this subject, but I just can't seem to puzzle the pieces together. I'm working with MVVM, but all MVVM-solutions look pretty extensive for something that is just a QoL-improvement. I do have some code in my codebehind file, so I assumed I could just put it there and have a quicker/cleaner solution, but those I could mostly find for start-up focus.
I've messed around with the Focusmanager, but that doesn't seem to bring me anywhere either.
The tricky part of the whole construction is the following;
<ListBox Grid.Column="0" Grid.Row="1" Margin="5" IsEnabled="{Binding IsEnabled}" ItemsSource="{Binding DisabledConfigs}" SelectionMode="Extended" SelectedItem="{Binding SelectedConfig}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding SelectionChanged}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding EditConfig}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
The EditConfig-Command triggers a method that checks some properties of the double-clicked object, and either shows a form or not. When it does, the first textbox in that form should receive focus, otherwise nothing of importance happens.
I've been struggling with this stupid QoL-issue for my entire morning now, so I'm prepared to donate my left-kidney to whomever points me in a direction I can cleanly adopt ..
Edit: After a suggestion by AdminSoftDK I tried the following
// Auto-generated
private void nameBoxEdit_IsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (nameBoxEdit.IsVisible)
{
// nameBoxEdit is (quite self-explanatory) the textbox that I want to focus on
nameBoxEdit.Focus();
}
}
I'm convinced this should be pretty close to the solution, but it's not working as is.
Huge shoutout to adminSoftDK for helping me out here
So I finally got the solution, which looks pretty weird to me, but it's working so I'm not complaining;
private void nameBoxAdd_IsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if(!nameBoxAdd.IsVisible)
{
nameBoxAdd.UpdateLayout();
// Task.Delay(500); abundant
nameBoxAdd.Focus();
// After testing some more, the Task.Delay(500) is not needed either.
// It's just the combination of UpdateLayout() and Focus()
}
}
I had quite an exstensive list of method-calls and property-checks in here to see if anything worked, an low and behold the focus was granted. I started filtering down, and for some reason the combined effort of UpdateLatyout() and the delayed task on Focus() made it work. Not either on of them (I tried having just one or the other which both put me on non-focus again), but the both of them.
Another weird thing to notice is that the IsVisibleChanged event triggers before actually changing the property. Something I ran across with the debugger..

Is accessing the ViewModel in code behind always violating the MVVM pattern?

One thing I am really not sure about is how to properly pass mouse events to the ViewModel. There is the way of binding triggers using the interactivity extension like for instance in: WPF event binding from View to ViewModel?
But this does not forward the MouseEventArgs to my knowledge, and this solution does not appear very elegant to me.
So what would be the proper solution? One way is to register an event and to handle it in the code behind, e.g.:
private void ListBox_PreviewMouseDown(object sender, System.Windows.Input.MouseEventArgs e)
{
var listbox = sender as ListBox;
if (listbox == null)
return;
var vm = listbox.DataContext as MainViewModel;
if (vm == null)
return;
// access the source of the listbox in viewmodel
double x = e.GetPosition(listbox).X;
double y = e.GetPosition(listbox).Y;
vm.Nodes.Add(new Node(x, y));
}
Here I assume that the listbox's ItemsSource is bound to the vm.Nodes property. So again the question: is it the proper way of doing it? Or is there a better one?
Good timing, I wrote some code to do exactly this about two hours ago. You can indeed pass arguments, and personally I thnk it is elegant because it allows you to fully test your user interface. MVVM Lite allows you to bind events to commands with EventToCommand, so start by adding the relevant namespaces to your control/window:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight"
Now add event triggers to the child control whose events you want to intercept:
<ItemsControl ... etc ... >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseDownCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseUp">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseUpCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseMove">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseMoveCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ItemsControl>
In my specific case I'm rendering a collection of items onto a canvas, hence my use of ItemsControl, but it'll work on anything including the parent window. It will also work for key strokes (e.g. KeyDown) but if your child control isn't focus-able then you'll have to add the trigger to the parent instead. In any case all that remains is to add the relevant handlers to your view model:
public class MyViewModel : ViewModelBase
{
public ICommand MouseDownCommand { get; set; }
public ICommand MouseUpCommand { get; set; }
public ICommand MouseMoveCommand { get; set; }
public ICommand KeyDownCommand { get; set; }
// I'm using a dependency injection framework which is why I'm
// doing this here, otherwise you could do it in the constructor
[InjectionMethod]
public void Init()
{
this.MouseDownCommand = new RelayCommand<MouseButtonEventArgs>(args => OnMouseDown(args));
this.MouseUpCommand = new RelayCommand<MouseButtonEventArgs>(args => OnMouseUp(args));
this.MouseMoveCommand = new RelayCommand<MouseEventArgs>(args => OnMouseMove(args));
this.KeyDownCommand = new RelayCommand<KeyEventArgs>(args => OnKeyDown(args));
}
private void OnMouseDown(MouseButtonEventArgs args)
{
// handle mouse press here
}
// OnMouseUp, OnMouseMove and OnKeyDown handlers go here
}
One last thing I will mention that is only a little bit off-topic is that often you'll need to communicate back to the code-behind e.g. when the user presses the left mouse button you might need to capture the mouse, but this can easily be accomplished with attached behaviors. The mouse capture behavior is simple enough, you just add a "MouseCaptured" boolean property to your view model, bind your attached behavior to it and have it's changed handler respond accordingly. For anything more complicated you might want to create an event inside your view model which your attached behaviour can then subscribe to. Either way, your UI is now fully unit-testable and your code-behind has been moved into generic behaviors for re-use in other classes.
I think your approach is good. Those events, that work with View, can be in your code-behind if you handlers work via ViewModel. However, there is an alternative use GalaSoft.MvvmLight (link to download), in which have EventToCommand, supports parameter PassEventArgsToCommand.
Example of using:
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<cmd:EventToCommand Command="{Binding FooCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
I think you can use both approaches. Your solution is simple, does not require the use of the any frameworks but uses code-behind, in this case it is not critical. One thing is certain, it is advisable not to keep ViewModel event handlers, use the command or store these handlers on View side.
Some new notes
I think, your way does not violate the principles of MVVM, all event handlers working with View, should be on the side of the View, the main thing - it's event handlers need to work with a ViewModel and have a dependency via an interface, but not directly with the UI.
The only principle MVVM that you break - is the mantra "no code" and this is not the main principle of MVVM. The main principles:
Split data Model of View
Application logic should not be tied to UI
Support testability code
Once the code-behind violate at least one of these principles, you must already see the alternatives to solve their problem.
Also, you can read opinions about it on this link:
WPF MVVM Code Behind

RelayCommand vs EventToCommand

I'm little confused about RelayCommand and EventToCommand in Mvvmlight.
It seems that EventToCommand handle the EventTrigger and call a RelayCommand to do job.
Such as:
<i:Interaction.Triggers>
<i:EventTrigger x:Uid="i:EventTrigger_1" EventName="MouseLeftButtonUp">
<cmd:EventToCommand x:Uid="cmd:EventToCommand_1" Command="{Binding Form_MouseLeftButtonUpCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Is my understanding correct?
So, can we use RelayCommand directly with EventTrigger, no need to use EventToCommand?
Thanks for your help!
EventToCommand is a custom behavior. It is first provided by Expression blend team and now Part of WPF 4. If you're not using WPF4. you require Blend SDK from here.
Behaviors encapsulates functionality as reusable components. These are to be used when feature is not present by default. For example Adding Command support to Label, Combobox etc.
can we use RelayCommand directly with EventTrigger, no need to use EventToCommand?
No. RelayCommand is a shortcut to avoid code redudnency to define custom commands.It extends ICommand whose delegates can be attached for Execute(T) and CanExecute(T). It is similar to DelegateCommand of Prism library.
<cmd:EventToCommand x:Uid="cmd:EventToCommand_1" Command="{Binding Form_MouseLeftButtonUpCommand}" PassEventArgsToCommand="True"/>
In above line cmd:EventToCommand is additional feature to the underlying control. Form_MouseLeftButtonUpCommand is the Command it executes. This command can be encapsulated as RelayCommand.
Thank Tilak for your useful answer.
With the explanation from Tilak, I did mess up like putting a binding in a event handler (such as Button GotFocus="{Binding DoJob}") --> Build failed and found that Event Handler like this does not support Binding. We can only bind in Command (such as Button Command="{Binding DoJob}" /> and default event is invoked in this situation, with button, it should be Click event).
Do something stupid will help me to understand the life more - LOL
can we use RelayCommand directly with EventTrigger, no need to use
EventToCommand?
Actually, I intend NOT to use EventToCommand, and I found the solution for that: use InvokeCommandAction instead (belongs to System.Windows.Interactivity - Mvvm-light also refers to this assembly).

RelayCommand CanExecute behavior not working

I'm having trouble getting the RelayCommand to enable/disable the attached control properly.
I've got an EventToCommand element attached to a button. The command is databound to the ViewModel. Initially, the button is disabled (expected behavior), but I cannot seem to get the CanExecute logic to check it's value. When CurrentConfigFile is set and exists, the button should be enabled. I've executed code and checked the file's value in debug to make sure it's set, but the control is still disabled. I've tried CommandManager.InvalidateRequerySuggested() and command.RaiseCanExecuteChanged(), but it will not enable.
I've wondered if lambdas don't work correctly for the CanExecute behavior (even though the examples use them) or that the CanExecute behavior needs to be databound to another element.
Here's my code:
// The FileInfo being checked for existence before the button should be enabled
public const string CurrentConfigFilePN = "CurrentConfigFile";
public FileInfo CurrentConfigFile
{
get
{
return _currentConfigFile;
}
set
{
if (_currentConfigFile == value)
{
return;
}
var oldValue = _currentConfigFile;
_currentConfigFile = value;
// Update bindings, no broadcast
RaisePropertyChanged(CurrentConfigFilePN);
}
}
public MainViewModel()
{
// snip //
SaveCommand = new RelayCommand(SaveConfiguration,
() => CurrentConfigFile != null && CurrentConfigFile.Exists);
}
private void SaveConfiguration()
{
// export model information to xml document
ExportXMLConfiguration(CurrentConfigFile);
}
and markup
<Button x:Name="SaveButton" Content="Save" Width="75" Margin="20,5">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft:EventToCommand x:Name="SaveETC"
Command="{Binding SaveCommand}"
MustToggleIsEnabledValue="true" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Update:
As per Isak Savo's suggestion, I bound the RelayCommand directly to the button with
<Button x:Name="SaveButton" Content="Save" Width="75" Margin="20,5"
Command="{Binding SaveCommand}"/>
and it started disabled and correctly enabled when the FileInfo was set. Guess I should remember not to fix what isn't broken!
Why don't you just bind to the Command directly from the Button?
<Button Command="{Binding SaveCommand}" Content="Save" />
Maybe the EventToCommand thing you are using is messing things up with the command's CanExecute notification.
And regarding the CanExecute problem - are you sure that your CanExecute handler is called after the CurrentConfigFile property is set? I've found that even though WPF mostly does a good job of requerying CanExecute, I still sometimes need to force a requery through the CommandManager.
EDIT: As pointed out in the comments, the OP has already tried the command manager approach.
In msdn is written:
When first called, FileInfo calls Refresh and caches information about the file. On subsequent calls, you must call Refresh to get the latest copy of the information.
However, I would not do such a check in the CanExecute-handler. This may slow down your UI because CanExecute is called a lot of times and I could imagine that such IO-checks can become slow, for example if the file lies on a network share.

Categories

Resources