How to find the ICommand binding target (the control) - wpf

We use the MVVM pattern. In the View, I have binding the save command to a button:
In the view model, I would like to find out the save command binding target, does it possible?
private Button GetBindingControl(ICommand command)
{
// What should I do here:
return button;
}

It's not possible, and it defeats the purpose of MVVM (having the UI logic in the VM regardless of the controls used)
Maybe you could ask instead what problem you are trying to solve.

As #Diego said, this defats the purpose of MVVM because we must try hard not to include visuals or controls in the view models in MVVM...
Having said that there are two options...
Using RoutedCommands
Using Attached Behaviors.
RoutedCommands are not readily allowed in MVVM as they need to be closely command bound to the UI element i.e. in our case the Button. Hence they too defeat the purpose of MVVM.
But MVVM happily co-exists with the Attached Behaviors.
Many developers shy away from this immensely powerful feature. And we can use it along with RoutedCommands.
In your case
Attach to the Button, with a Action delegate.
Attach the string object as command parameter.
Inside the behavior, set the Button.Command with some Routed command.
In the executed event handler, get the button action delegate from the sender / originalsource / source as the button and then call your Action<> accordingly by using e.Parameter string value.
Sample code below...
Assume you have common button utilities of signature Action<Button, string>
public static class ButtonActionUtilities
{
public static Action<Button, string> ButtonActionDelegate
{
get
{
return ExecuteButtonClick;
}
}
public static void ExecuteButtonClick(Button btn, string param)
{
MessageBox.Show(
"You clicked button " + btn.Content + " with parameter " + param);
}
}
Then the attched behavior is as below...
public static class ButtonAttachedBehavior
{
public static readonly DependencyProperty ActionDelegateProperty
= DependencyProperty.RegisterAttached(
"ActionDelegate",
typeof(Action<Button, string>),
typeof(ButtonAttachedBehavior),
new PropertyMetadata(null, OnActionDelegatePropertyChanged));
public static Action<Button, string> GetActionDelegate(
DependencyObject depObj)
{
return (Action<Button, string>)depObj.GetValue(
ActionDelegateProperty);
}
public static void SetActionDelegate(
DependencyObject depObj, Action<Button, string> value)
{
depObj.SetValue(ActionDelegateProperty, value);
}
private static void OnActionDelegatePropertyChanged(
DependencyObject depObj,
DependencyPropertyChangedEventArgs e)
{
if (depObj is Button
&& e.NewValue is Action<Button, string>)
{
((Button)depObj).Command
= new RoutedCommand(
"ActionRoutedCommand",
typeof(ButtonAttachedBehavior));
((Button) depObj).CommandBindings.Add(
new CommandBinding(
((Button) depObj).Command,
OnActionRoutedCommandExecuted));
}
}
private static void OnActionRoutedCommandExecuted(
object sender, ExecutedRoutedEventArgs e)
{
var actionDelegate = GetActionDelegate((Button)e.Source);
actionDelegate((Button) e.Source, (string)e.Parameter);
}
}
And on XAML it will look like this....
<StackPanel>
<Button x:Name="TestButton" Content="Test Me"
local:ButtonAttachedBehavior.ActionDelegate
="{x:Static local:ButtonActionUtilities.ButtonActionDelegate}"
CommandParameter
="{Binding Text, ElementName=ParameterTextBox}"/>
<TextBox x:Name="ParameterTextBox"/>
</StackPanel>
So with the code above you will need to just set the ActionDelegate attached property to approapriate delegate and it will execute that.
I would still suggest you to revamp your existing code setup to separate button specific behaviors to make it more MVVM friendly.

Related

Another WPF Set focus problem with Button Click (control not a child at design time)

I have a button on a WPF window that adds a tabitem to a tabcontrol on the window. A user control that fills that tabitem is also created in that event. I want to set the focus to a textbox on that user control. I've tried all kinds of txt.Focus code and so forth but the button always still have focus after the click event is executed.
I can't use this as the textbox is not part of the xaml on this window at design time.
MyAttachedProps:EventFocusAttachment.ElementToFocus="{Binding ElementName=NotAvailableAtDesignTime}"
The only thing I can think of is a timer to execute after the button click but there has to be a better way.
You could use a bool attached dependency property.
Bind this to a public bool property in your viewmodel.
Set that to true when you want to focus the control.
I have a bit of code. Can't recall if I wrote it or grabbed it off the web. And I can't recall actually using it either.
public static class FocusExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static async void OnIsFocusedPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
await Task.Delay(200);
uie.Focus();
Keyboard.Focus(uie);
}
}
}
This the await task delay introduces a 200ms wait so other stuff can finish whatever it's doing. You could instead defer the focussing using dispatcher.
Application.Current.Dispatcher.InvokeAsync(new Action(() =>
{
uie.Focus();
Keyboard.Focus(uie);
}), DispatcherPriority.ContextIdle);
Due to closures, that code will capture whatever uie is so long as it's in scope.

How to access ShowMessageAsync method of MetroWindow from ViewModel

I am using MahApps.metro WPF library with MVVM. I have a ViewModel from which I need to display a Dialog. The MetroWindow has ShowMessageAsync. But what is the proper way to access it from the ViewModel? As I understand I need a View instance but passing that into the ViewModel doesn't seem like a good approach.
Use following approach:
Take an Action<T> ShowMessageAsync in your ViewModel which you are binding with window.
Now create a behaviour for Window and use following code in behaviour
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded;
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
if (this.AssociatedObject.DataContext is WindowViewModel)
{
WindowViewModel vm = this.AssociatedObject.DataContext as WindowViewModel;
vm.ShowMessageAsync = OnShowMessageAsync;
}
}
private void OnShowMessageAsync(T param)
{
//Write your logic to call ShowMessageAsync method.
}
Now in this way, from the ViewModel of your MainWindow you will have ability to open another child window.

How to receive the InkCanvas.StrokeCollected event in the view model

In using MVVM pattern, I have a custom inkcanvas with:
protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
{
CustomStroke newStroke = new CustomStroke(e.Stroke.StylusPoints, e.Stroke.DrawingAttributes);
this.Strokes.Remove(e.Stroke);
this.Strokes.Add(newStroke);
InkCanvasStrokeCollectedEventArgs eNew = new InkCanvasStrokeCollectedEventArgs(newStroke);
// Raises the System.Windows.Controls.InkCanvas.StrokeCollected event.
base.OnStrokeCollected(eNew);
}
How do I get the view model to receive the InkCanvas.StrokeCollected event?
I can not bind the XAML to the strokes as the StrokeCollection.CollectionChanged event will be called three times by the custom inkcanvas.
Any help is appreciated.
Try this
public Window3()
{
InitializeComponent();
var vm=new ViewModel();
this.DataContext = vm;
canvas.StrokeCollected += vm.OnStrokeCollected;
}
ViewModel
public class ViewModel
{
public void OnStrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
}
}
Edit
if you want to do it without codebehind see the article EventTrigger
You simply bind it via XAML as you already did, which is the correct way to do it.
That you get 3 events, doesn't matter. Just handle the one you need.
For example, if you are only interested in the StrokeCollectedEvent, then just do
protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
{
if(e.RoutedEvent != InkCanvas.StrokeCollectedEvent)
return;
// handle the event
}
For a full list of Events, consult the "Fields" Section of InkCanvas MSDN documentation. The fields ending with "Event" are RoutedEvent constants, which are passed in the InkCanvasStrokeCollectedEventArgs.

What is the best way to handel click-events in MVVM?

What is the best way to handel click-events in MVVM? Are there a best way?
I have found two solutions:
with a relaycommand:
RelayCommand buttonAddCategory_Click;
public ICommand ButtonAddCategory_Click
{
get
{
return buttonAddCategory_Click ?? (buttonAddCategory_Click = new RelayCommand(param => this.AddCategory(),
param => true));
}
}
pro: ?; contra: need workaround with events if i would change ui elements like focus
with attached behaviour:
public static bool GetIsResetMouseLeftButtonDown(TreeView treeView)
{
return (bool)treeView.GetValue(IsResetMouseLeftButtonDownProperty);
}
public static void SetIsResetMouseLeftButtonDown(TreeView treeViewItem, bool value)
{
treeViewItem.SetValue(IsResetMouseLeftButtonDownProperty, value);
}
public static readonly DependencyProperty IsResetMouseLeftButtonDownProperty =
DependencyProperty.RegisterAttached("PreviewMouseLeftButtonDown", typeof(bool), typeof(TreeViewBehavior),
new UIPropertyMetadata(false, OnIsMouseLeftButtonDownChanged));
pro: you have RoutedEventArgs for changes on the ui; contra: access to other controls?
Right now i use both solutions. The RellayCommand in Buttons (with events for ui updates) and the attached behaviour for a treeview to deselect the treeviewitem if a user clicks.
To me there is no simple answer to this question.
That's the way I see it:
if you have a defined state-change on the VM, expose a RelayCommand which then can be bound to something the triggers it. In 99,9% percent of the cases this is a button/menu-entry. Something where it can be easily used. The cases that are left -> well some workaround might be needed, like calling a method from the view.
So a RelayCommand should imho be used if you are really targeting the VM.
Focus-changes on the other hand are view-related functionality. Imho this has nothing todo with the WM. That means for me it should be implemented in the view. So to me I'd even go for a straight-forward eventhandler that does the job.
hth,
Martin
I like this idea:
UI logic, such as opening new windows, showing/hiding elements, etc. You keep that on the code-behind.
When this 'click' should do something with the model, invoke the action.
So, a button that closes the window and saves something would be defined like this:
<Button Name="SaveBtnr" VerticalAlignment="Bottom"
Command="{Binding Save}" Click="OnSaveClick"
CommandParameter="{Binding}">Save</Button>
And the handler would be:
private void OnSaveClick(object sender, RoutedEventArgs e)
{
//Do UI Stuff
}
And then your command:
public void SaveCommand(object parameter)
{
//SaveStuff
}

Adding programatically a command to a listbox in WPF

In my WPF application there is a listbox with items. The listbox is populated via a xmldataprovider from XAML and then binding it to Itemssource property of the listbox.
Well, from XAML, I bind a comand to the listbox by doing:
<ListBox.CommandBindings>
<CommandBinding
Command="{x:Static local:mainApp.MyCmd}"
CanExecute="CanExecute"
Executed ="Executed" />
</ListBox.CommandBindings>
but I don't know how to programatically bind a command to each listboxitem. How to do it?
Thanks in advance.
First sorry by not posting it as a comment. I can't put all this in a comment.
Ok, yes I am not using the Executed and CanExecute properties of the ICommandSource despite I have registered and implemented them in custom class (in xaml they are commented too). I have specified them in routedCommand but not in custom class, I have done it in the constructor of the window by doing this:
WinMain code behind:
public WinMain()
{
InitializeComponent();
// Command binding. If I don't do this Executed and CanExecute are not executed
CommandBindings.Add(new CommandBinding(rcmd,
CommandBinding_Executed, CommandBinding_CanExecute));
}
and then I implement these methods in WinMain code behind too as it:
// ExecutedRoutedEventHandler
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Do stuff
}
// CanExecuteRoutedEventHandler
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// cBgWorkers is a class that check if a background worker is running
e.CanExecute = !cBgWorkers.isRunning;
//if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
}
and In WinMain XAML I invoke the command like this:
<Classes:CommandListBox x:Name="LayoutListBox"
Command="{x:Static local:WinMain.rcmd}"
... >
<...>
</Classes:CommandListBox>
And in my custom class CommandListBox I have a CanExecuteChanged in which you can see I enabled or disabled the control depending on whether the background worker is finished or not:
private void CanExecuteChanged(object sender, EventArgs e)
{
this.Enabled = !cBgWorkers.isRunning;
}
but in custom class I haven't implement the event handler you say, OnSelected.
WIthout implementing it all goes ok, the custom control invoke command, and CanExecute method is reached, and CanExecute gets correct value, true or false depending on background worker is finished or not, and the CanExecuteChanged in custom control is raised when CanExecute changes its value. When background worker starts it gets disabled but when it finished it doesn't get enabled. I have debugged, and when background worker finishes I can see CanExecuteChanged is executed and this.Enabled is getting correct value (true) but for some reason in the UI the control continues disabled despite it gets its correct value and despite in RunWOrkerCompleted (in background worker) I force to update UI with CommandManager.InvalidateRequerySuggested().
I solve this by uncomment line:
if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
in CanExecute method. I don't understand what happens.
Then If I do what you say It is not necessary to do it:
CommandBindings.Add(new CommandBinding(rcmd,
CommandBinding_Executed, CommandBinding_CanExecute));
and CommandBinding_Executed & CommandBinding_CanExecute implementations. Am I right?
but if I remove these methods where can I set this.enabled = !cBgWorkers.isRunning ?
I would like WPF to set automatically isEnabled property for my custom control. How to do this?
Thanks in advance.
I am applying the article you say about attached behaviors with a few changes to adapt it to my ListBox. It doesn't work well or perhaps I am doing something wrong. What I want is avoid that ListBox members (listBoxItems) can be select when a long task (background worker) is running. So one of the method of the article that I have modified is:
static void OnListBoxItemSelected(object sender, RoutedEventArgs e)
{
// Only react to the Selected event raised by the ListBoxItem
// whose IsSelected property was modified. Ignore all ancestors
// who are merely reporting that a descendant's Selected fired.
if (!Object.ReferenceEquals(sender, e.OriginalSource))
return;
ListBoxItem item = e.OriginalSource as ListBoxItem;
if (item != null)
{
// (*) See comment under
item.IsEnabled = !cBgWorkers.isRunning;
if (!cBgWorkers.isRunning)
{
item.BringIntoView();
}
}
}
(*) cBgWorkers is a public static Class that has some methods and properties.
One of the properties is isRunning that indicates no background workers is currently running. Then If no background workers is running, listbox members have to be enabled otherwise they have to be disabled so when user clicks over one listbox item the current page don't change to another one because I disabled it before (each listBox items has attached one page in my main application).
When one of the background workers (bw) or all are running and I select listbox item all is ok: listbox item is disabled because there are bw running and it avoid tho change the current page to another one. Of course, If I disabled the listbox item (or listbox items) I can't select it again because it is disabled and that is my problem, because I want that when bw finish the listbox items that have been disabled while bw were running, they get enabled again. Unfortunately with attached behavior as I see it isn't done by WPF automatically and commands have this advantage (controls update automatically by WPF). so, how to disabled/re-enabled listbox items when bw is running or not respectively?
As far as I know and see, one advantage of attached behaviors is that I think it is more efficient because they are not invoking actions constantly (only when the action, for example, selection, is produced). Commands are constantly (not often) checking if actions binded to controls can be executed (so If they can be executed, WPF automatically enables controls otherwise they appear disabled), right?
Thanks.
you could try creating a custom control that derives from ListBoxItem and implementing the ICommandSource interface. I can't think of a more simple solution as of now.
I have done your solution. I have done a custom user control deriving from listbox and implementing ISourceCommand as you said and it works now!!!! ;)
My custom class:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Input;
namespace GParts.Classes
{
public class CommandListBox : ListBox, ICommandSource
{
public CommandListBox() : base()
{
}
// ICommand Interface Members
// Make Command a dependency property so it can use databinding.
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(CommandListBox),
new PropertyMetadata((ICommand)null,
new PropertyChangedCallback(CommandChanged)));
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
// Make Command a dependency property so it can use databinding.
public static readonly DependencyProperty ExecutedProperty =
DependencyProperty.Register(
"Executed",
typeof(object),
typeof(CommandListBox),
new PropertyMetadata((object)null));
public object Executed
{
get
{
return (object)GetValue(ExecutedProperty);
}
set
{
SetValue(ExecutedProperty, value);
}
}
// Make Command a dependency property so it can use databinding.
public static readonly DependencyProperty CanExecuteProperty =
DependencyProperty.Register(
"CanExecute",
typeof(object),
typeof(CommandListBox),
new PropertyMetadata((object)null));
public object CanExecute
{
get
{
return (object)GetValue(CanExecuteProperty);
}
set
{
SetValue(CanExecuteProperty, value);
}
}
// Make CommandTarget a dependency property so it can use databinding.
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register(
"CommandTarget",
typeof(IInputElement),
typeof(CommandListBox),
new PropertyMetadata((IInputElement)null));
public IInputElement CommandTarget
{
get
{
return (IInputElement)GetValue(CommandTargetProperty);
}
set
{
SetValue(CommandTargetProperty, value);
}
}
// Make CommandParameter a dependency property so it can use databinding.
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(CommandListBox),
new PropertyMetadata((object)null));
public object CommandParameter
{
get
{
return (object)GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
// Command dependency property change callback.
private static void CommandChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
CommandListBox clb = (CommandListBox)d;
clb.HookUpCommand((ICommand)e.OldValue,(ICommand)e.NewValue);
}
// Add a new command to the Command Property.
private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
{
// If oldCommand is not null, then we need to remove the handlers.
if (oldCommand != null)
{
RemoveCommand(oldCommand, newCommand);
}
AddCommand(oldCommand, newCommand);
}
// Remove an old command from the Command Property.
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
//newCommand.Execute(null);
//newCommand.CanExecute(null);
}
// Add the command.
private void AddCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
//newCommand.Execute(Executed);
//newCommand.CanExecute(CanExecute);
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.Command != null)
{
RoutedCommand command = this.Command as RoutedCommand;
// If a RoutedCommand.
if (command != null)
{
if (command.CanExecute(CommandParameter, CommandTarget))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
// If a not RoutedCommand.
else
{
if (Command.CanExecute(CommandParameter))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
}
// Keep a copy of the handler so it doesn't get garbage collected.
private static EventHandler canExecuteChangedHandler;
}
}
and in my WinMain.xaml:
<Classes:CommandListBox x:Name="LayoutListBox"
Command="{x:Static local:WinMain.rcmd}"
<!-- These lines doesn't work I explain it following
Executed="CommandBinding_Executed"
CanExecute="CommandBinding_CanExecute"
-->
... >
<...>
</Classes:CommandListBox>
and window code behind:
public WinMain()
{
InitializeComponent();
// Command binding. If I don't do this Executed and CanExecute are not executed
CommandBindings.Add(new CommandBinding(rcmd,
CommandBinding_Executed, CommandBinding_CanExecute));
}
public static RoutedCommand rcmd = new RoutedCommand();
// ExecutedRoutedEventHandler
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Do stuff
}
// CanExecuteRoutedEventHandler
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !cBgWorkers.isRunning;
//if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
}
but I have the same problem as the another solution. If I don't place the last line (here appears commented in CommandBinding_CanExecute) the listbox doesn't enable automatically by wpf when background worker finishes. If I put this line it works. What's happens?
Another thing, as you can see in my code snippet I would like to do the same as I do with a button where you can indicate command, executed and canexecute. I have registered them in the class, and in listbox I checked to pass the methods but it didn't work. How can I do this?
Thanks very much.
Take a look at attached behaviors.
According to the first question I posted, using CommandBindings in listbox it doesn't work. The implementation of CanExecute was:
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !cBgWorkers.isRunning;
}
By doing WPF doesn't enable/disabled the listbox control automatically depending on the background worker state (Running or not) and I don't understand why because I have other controls like buttons with commands binded and WPF automatically enable/disable them.
So I have done the following modification:
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !cBgWorkers.isRunning;
if (LayoutListBox != null) LayoutListBox.IsEnabled = !cBgWorkers.isRunning;
}
Now, it works. Listbox is enable when no background worker is running and disabled otherwise bu what I don't like is the last line placed into the method in where I enable/disable manually the property isEnabled of the listbox. It is inefficient so I would like to change isEnabled property of the listbox only when CanExecute changes its value. As far as I know there is a event to do this, it is CanExecuteChanged but I don't know how to implement it. Any ideas?
Now, after trying several solutions, I am implementing the Mike's solution because I think it is easier and clearer and it can be re-used for other controls with only a few changes.
I haven't been able the whole thread. It's quite long. Anyway, I thought you want to put a command on a ListBoxItem? From what I see, you inherited from ListBox. You do not need to specify the Executed and CanExecute properties of the ICommandSource. This should be specified in your RoutedCommand, not in your custom control. To get your command executed, you need to provide an event handler in your custom control. As an example, if an item gets selected, then you execute the command. Here's an example.
protected override void OnSelected(RoutedEventArgs e)
{
base.OnSelected(e);
if (this.Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
{
command.Execute(CommandParameter, CommandTarget);
}
else
{
((ICommand)Command).Execute(CommandParameter);
}
}
}

Resources