The following RoutedCommand example works.
However, the handling for the button which executes the command is in the codebehind of the view. The way I understand MVVM, it should be in the ViewModel.
However, When I move the method to the ViewModel (and change it to public), I get the error "ManagedCustomersView does not contain a definition of OnSave". Even if I change the RoutedCommand second parameter to typeof(ManageCustomersViewModel), I get the same error.
How can I move the command handler from the View-codebehind to the ViewModel?
ManageCustomersView.xaml:
<UserControl.CommandBindings>
<CommandBinding Command="local:Commands.SaveCustomer" Executed="OnSave"/>
</UserControl.CommandBindings>
...
<Button Style="{StaticResource formButton}"
Content="Save"
Command="local:Commands.SaveCustomer"
CommandParameter="{Binding Id}"/>
ManageCustomersView.xaml.cs:
private void OnSave(object sender
, System.Windows.Input.ExecutedRoutedEventArgs e)
{
int customerId = ((int)e.Parameter);
MessageBox.Show(String.Format
("You clicked the save button for customer with id {0}.", customerId));
}
Commands.cs:
using System.Windows.Input;
using TestDynamicForm123.View;
namespace TestDynamicForm123
{
public class Commands
{
public static RoutedCommand SaveCustomer =
new RoutedCommand("SaveCustomer", typeof(ManageCustomersView));
}
}
You'll expose a property off your ViewModel that references the command.
class MyViewModel
{
public RoutedCommand SaveCmd{ get{ return Commands.SaveCustomer; } }
}
Then in the XAML
<Button Command="{Binding SaveCmd}" />
However you might find it easier to use the RelayCommand so that you can define the actual command logic in your model as well.
Related
I am trying to figure out how I should bind the click action of a button in vb.net for WPF
Here is a section of WPF code
WPF:
<TabItem Name="tab_emailSender>
<TextBox Text="{Binding Path=address}" />
<Button Command="{Binding Path=sendTestMessage}"
</TabItem>
VB
Class MainWindow
Dim thisMessage as new Message
Private Sub main() Handles Me.Loaded
tab_emailSender.DataContext = thisMessage
End Sub
End Class
Class Message
Public Property address as string
Public Sub sendTestMessage()
msgbox("it worked!")
End Sub
End Class
I am able to bind the textbox's text but I am not sure how to bind the button's click event to the sendTestMessage sub.
In order to handle the button's click event, if you want to follow MVVM, you need to create an ICommand property on your ViewModel. ICommand is the inteface, so you can do something like this (sorry for C#):
public class Message: ICommand
{
public address { get; set; }
public bool CanExecute(Object parameter)
{
return true;
}
public void Execute(Object parameter)
{
//Do the logic here.
}
}
and the xaml (as far as your DataContext is the instance of Message type):
<Button Command="{Binding}" />
Of course you can implement the interface by yourself, on another class, etc. (I've made the implementation inside the Message class because its simplier here), but you can also find useful to use DelegateCommand or RelayCommand. This classes will prevent you from re-implementing the interface involved every time you need the new command.
I am trying to create a localizable WPF menu bar with menu items that have keyboard shortcuts - not accelerator keys/mnemonics (usually shown as underlined characters that can be pressed to directly select a menu item when the menu is already open), but keyboard shortcuts (usually combinations of Ctrl + another key) that are displayed right-aligned next to the menu item header.
I am using the MVVM pattern for my application, meaning that I avoid placing any code in code-behind wherever possible and have my view-models (that I assign to the DataContext properties) provide implementations of the ICommand interface that are used by controls in my views.
As a base for reproducing the issue, here is some minimal source code for an application as described:
Window1.xaml
<Window x:Class="MenuShortcutTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MenuShortcutTest" Height="300" Width="300">
<Menu>
<MenuItem Header="{Binding MenuHeader}">
<MenuItem Header="{Binding DoSomethingHeader}" Command="{Binding DoSomething}"/>
</MenuItem>
</Menu>
</Window>
Window1.xaml.cs
using System;
using System.Windows;
namespace MenuShortcutTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
MainViewModel.cs
using System;
using System.Windows;
using System.Windows.Input;
namespace MenuShortcutTest
{
public class MainViewModel
{
public string MenuHeader {
get {
// in real code: load this string from localization
return "Menu";
}
}
public string DoSomethingHeader {
get {
// in real code: load this string from localization
return "Do Something";
}
}
private class DoSomethingCommand : ICommand
{
public DoSomethingCommand(MainViewModel owner)
{
if (owner == null) {
throw new ArgumentNullException("owner");
}
this.owner = owner;
}
private readonly MainViewModel owner;
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
// in real code: do something meaningful with the view-model
MessageBox.Show(owner.GetType().FullName);
}
public bool CanExecute(object parameter)
{
return true;
}
}
private ICommand doSomething;
public ICommand DoSomething {
get {
if (doSomething == null) {
doSomething = new DoSomethingCommand(this);
}
return doSomething;
}
}
}
}
The WPF MenuItem class has an InputGestureText property, but as described in SO questions such as this, this, this and this, that is purely cosmetic and has no effect whatsoever on what shortcuts are actually processed by the application.
SO questions like this and this point out that the command should be linked with a KeyBinding in the InputBindings list of the window. While that enables the functionality, it does not automatically display the shortcut with the menu item. Window1.xaml changes as follows:
<Window x:Class="MenuShortcutTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MenuShortcutTest" Height="300" Width="300">
<Window.InputBindings>
<KeyBinding Key="D" Modifiers="Control" Command="{Binding DoSomething}"/>
</Window.InputBindings>
<Menu>
<MenuItem Header="{Binding MenuHeader}">
<MenuItem Header="{Binding DoSomethingHeader}" Command="{Binding DoSomething}"/>
</MenuItem>
</Menu>
</Window>
I have tried manually setting the InputGestureText property in addition, making Window1.xaml look like this:
<Window x:Class="MenuShortcutTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MenuShortcutTest" Height="300" Width="300">
<Window.InputBindings>
<KeyBinding Key="D" Modifiers="Control" Command="{Binding DoSomething}"/>
</Window.InputBindings>
<Menu>
<MenuItem Header="{Binding MenuHeader}">
<MenuItem Header="{Binding DoSomethingHeader}" Command="{Binding DoSomething}" InputGestureText="Ctrl+D"/>
</MenuItem>
</Menu>
</Window>
This does display the shortcut, but is not a viable solution for obvious reasons:
It does not update when the actual shortcut binding changes, so even if the shortcuts are not configurable by users, this solution is a maintenance nightmare.
The text needs to be localized (as e.g. the Ctrl key has different names in some languages), so if any of the shortcuts is ever changed, all translations would need to be updated individually.
I have looked into creating an IValueConverter to use for binding the InputGestureText property to the InputBindings list of the window (there might be more than one KeyBinding in the InputBindings list, or none at all, so there is no specific KeyBinding instance that I could bind to (if KeyBinding even lends itself to being a binding target)). This appears to me like the most desirable solution, because it is very flexible and at the same time very clean (it does not require a plethora of declarations in various places), but on the one hand, InputBindingCollection does not implement INotifyCollectionChanged, thus the binding would not be updated when shortcuts are replaced, and on the other hand, I did not manage to provide the converter with a reference to my view-model in a tidy manner (which it would need to access the localization data). What is more, InputBindings is not a dependency property, so I cannot bind that to a common source (such as a list of input bindings located in the view-model) that the ItemGestureText property could be bound to, as well.
Now, many resources (this question, that question, this thread, that question and that thread point out that RoutedCommand and RoutedUICommand contain a built-in InputGestures property and imply that key bindings from that property are automatically displayed in menu items.
However, using either of those ICommand implementations seems to open a new can of worms, as their Execute and CanExecute methods are not virtual and thus cannot be overridden in subclasses to fill in the desired functionality. The only way to provide that seems to be declaring a CommandBinding in XAML (shown e.g. here or here) that connects a command with an event handler - however, that event handler would then be located in the code-behind, thus violating the MVVM architecture described above.
Trying nonetheless, this means turning most of the aforementioned structure inside-out (which also kind of implies that I need to make my mind up on how to eventually solve the issue in my current, comparably early stage of development):
Window1.xaml
<Window x:Class="MenuShortcutTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MenuShortcutTest"
Title="MenuShortcutTest" Height="300" Width="300">
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:DoSomethingCommand.Instance}" Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<Menu>
<MenuItem Header="{Binding MenuHeader}">
<MenuItem Header="{Binding DoSomethingHeader}" Command="{x:Static local:DoSomethingCommand.Instance}"/>
</MenuItem>
</Menu>
</Window>
Window1.xaml.cs
using System;
using System.Windows;
namespace MenuShortcutTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
void CommandBinding_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
((MainViewModel)DataContext).DoSomething();
}
}
}
MainViewModel.cs
using System;
using System.Windows;
using System.Windows.Input;
namespace MenuShortcutTest
{
public class MainViewModel
{
public string MenuHeader {
get {
// in real code: load this string from localization
return "Menu";
}
}
public string DoSomethingHeader {
get {
// in real code: load this string from localization
return "Do Something";
}
}
public void DoSomething()
{
// in real code: do something meaningful with the view-model
MessageBox.Show(this.GetType().FullName);
}
}
}
DoSomethingCommand.cs
using System;
using System.Windows.Input;
namespace MenuShortcutTest
{
public class DoSomethingCommand : RoutedCommand
{
public DoSomethingCommand()
{
this.InputGestures.Add(new KeyGesture(Key.D, ModifierKeys.Control));
}
private static Lazy<DoSomethingCommand> instance = new Lazy<DoSomethingCommand>();
public static DoSomethingCommand Instance {
get {
return instance.Value;
}
}
}
}
For the same reason (RoutedCommand.Execute and such being non-virtual), I do not know how to subclass RoutedCommand in a way to create a RelayCommand like the one used in an answer to this question based on RoutedCommand, so I do not have to make the detour over the InputBindings of the window - while explicitly reimplementing the methods from ICommand in a RoutedCommand subclass feels like I might be breaking something.
What is more, while the shortcut is automatically displayed with this method as configured in the RoutedCommand, it does not seem to get automatically localized. My understanding is that adding
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-de");
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture;
to the MainWindow constructor should make sure that localizable strings supplied by the framework should be taken from the German CultureInfo - however, Ctrl does not change to Strg, so unless I am mistaken about how to set the CultureInfo for framework-supplied strings, this method is not viable anyway if I expect the displayed shortcut to be correctly localized.
Now, I am aware that KeyGesture allows me to specify a custom display string for the keyboard shortcut, but not only is the RoutedCommand-derived DoSomethingCommand class disjoint from all of my instances (from where I could get in touch with the loaded localization) due to the way CommandBinding has to be linked with a command in XAML, the respective DisplayString property is read-only, so there would be no way to change it when another localization is loaded at runtime.
This leaves me with the option to manually dig through the menu tree (EDIT: for the sake of clarification, no code here because I am not asking for this and I know how to do this) and the InputBindings list of the window to check which commands have any KeyBinding instances associated with them, and which menu items are linked to any of those commands, so that I can manually set the InputGestureText of each of the respective menu items to reflect the first (or preferred, by whichever metric I want to use here) keyboard shortcut. And this procedure would have to be repeated every time I think the key bindings may have changed. However, this seems like an extremely tedious workaround for something that is essentially a basic feature of a menu bar GUI, so I'm convinced it cannot be the "correct" way to do this.
What is the right way to automatically display a keyboard shortcut that is configured to work for WPF MenuItem instances?
EDIT: All of the other questions I found dealt with how a KeyBinding/KeyGesture could be used to actually enable the functionality visually implied by InputGestureText, without explaining how to automatically link the two aspects in the described situation. The only somewhat promising question that I found was this, but it has not received any answers in over two years.
I'll start with the warning. It can happen that you will need not only customizable hot keys but the menu itself. So think twice before using InputBindings statically.
There is one more caution concerning InputBindings: they imply that command is tied to the element in window's visual tree. Sometimes you need global hot keys not connected with any particular window.
The above said means that you can make it another way and implement your own application wide gestures processing with correct routing to corresponding commands (don't forget to use weak references to commands).
Nonetheless the idea of gesture aware commands is the same.
public class CommandWithHotkey : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
MessageBox.Show("It Worked!");
}
public KeyGesture Gesture { get; set; }
public string GestureText
{
get { return Gesture.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); }
}
public string Text { get; set; }
public event EventHandler CanExecuteChanged;
public CommandWithHotkey()
{
Text = "Execute Me";
Gesture = new KeyGesture(Key.K, ModifierKeys.Control);
}
}
Simple View Model:
public class ViewModel
{
public ICommand Command { get; set; }
public ViewModel()
{
Command = new CommandWithHotkey();
}
}
Window:
<Window x:Class="CommandsWithHotKeys.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:commandsWithHotKeys="clr-namespace:CommandsWithHotKeys"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<commandsWithHotKeys:ViewModel/>
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding Command}" Key ="{Binding Command.Gesture.Key}" Modifiers="{Binding Command.Gesture.Modifiers}"></KeyBinding>
</Window.InputBindings>
<Grid>
<Menu HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="Auto">
<MenuItem Header="Test">
<MenuItem InputGestureText="{Binding Command.GestureText}" Header="{Binding Command.Text}" Command="{Binding Command}">
</MenuItem>
</MenuItem>
</Menu>
</Grid>
</Window>
Sure, you should somehow load the gestures information from configuration and then init commands with the data.
The next step is keystokes like in VS: Ctrl+K,Ctrl+D, quick search gives this SO question.
If I haven't misunderstood your question try this:
<Window.InputBindings>
<KeyBinding Key="A" Modifiers="Control" Command="{Binding ClickCommand}"/>
</Window.InputBindings>
<Grid >
<Button Content="ok" x:Name="button">
<Button.ContextMenu>
<local:CustomContextMenu>
<MenuItem Header="Click" Command="{Binding ClickCommand}"/>
</local:CustomContextMenu>
</Button.ContextMenu>
</Button>
</Grid>
..with:
public class CustomContextMenu : ContextMenu
{
public CustomContextMenu()
{
this.Opened += CustomContextMenu_Opened;
}
void CustomContextMenu_Opened(object sender, RoutedEventArgs e)
{
DependencyObject obj = this.PlacementTarget;
while (true)
{
obj = LogicalTreeHelper.GetParent(obj);
if (obj == null || obj.GetType() == typeof(Window) || obj.GetType() == typeof(MainWindow))
break;
}
if (obj != null)
SetInputGestureText(((Window)obj).InputBindings);
//UnSubscribe once set
this.Opened -= CustomContextMenu_Opened;
}
void SetInputGestureText(InputBindingCollection bindings)
{
foreach (var item in this.Items)
{
var menuItem = item as MenuItem;
if (menuItem != null)
{
for (int i = 0; i < bindings.Count; i++)
{
var keyBinding = bindings[i] as KeyBinding;
//find one whose Command is same as that of menuItem
if (keyBinding!=null && keyBinding.Command == menuItem.Command)//ToDo : Apply check for None Modifier
menuItem.InputGestureText = keyBinding.Modifiers.ToString() + " + " + keyBinding.Key.ToString();
}
}
}
}
}
I hope this will give you an idea.
This is how it did it:
In the loaded-event of my window I match the Command bindings of the menu items with the Command bindings of all InputBindings, much like ethicallogics's answer, but for a menu bar and it actually compares the Command bindings and not just the value, because that didn't work for me. this code also recurses into submenus.
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
// add InputGestures to menu items
SetInputGestureTextsRecursive(MenuBar.Items, InputBindings);
}
private void SetInputGestureTextsRecursive(ItemCollection items, InputBindingCollection inputBindings)
{
foreach (var item in items)
{
var menuItem = item as MenuItem;
if (menuItem != null)
{
if (menuItem.Command != null)
{
// try to find an InputBinding with the same command and take the Gesture from there
foreach (KeyBinding keyBinding in inputBindings.OfType<KeyBinding>())
{
// we cant just do keyBinding.Command == menuItem.Command here, because the Command Property getter creates a new RelayCommand every time
// so we compare the bindings from XAML if they have the same target
if (CheckCommandPropertyBindingEquality(keyBinding, menuItem))
{
// let a new Keygesture create the String
menuItem.InputGestureText = new KeyGesture(keyBinding.Key, keyBinding.Modifiers).GetDisplayStringForCulture(CultureInfo.CurrentCulture);
}
}
}
// recurse into submenus
if (menuItem.Items != null)
SetInputGestureTextsRecursive(menuItem.Items, inputBindings);
}
}
}
private static bool CheckCommandPropertyBindingEquality(KeyBinding keyBinding, MenuItem menuItem)
{
// get the binding for 'Command' property
var keyBindingCommandBinding = BindingOperations.GetBindingExpression(keyBinding, InputBinding.CommandProperty);
var menuItemCommandBinding = BindingOperations.GetBindingExpression(menuItem, MenuItem.CommandProperty);
if (keyBindingCommandBinding == null || menuItemCommandBinding == null)
return false;
// commands are the same if they're defined in the same class and have the same name
return keyBindingCommandBinding.ResolvedSource == menuItemCommandBinding.ResolvedSource
&& keyBindingCommandBinding.ResolvedSourcePropertyName == menuItemCommandBinding.ResolvedSourcePropertyName;
}
Do this one time in your Window's code-behind and every menu item has an InputGesture. Just the translation is missing
Based on Pavel Voronin's answer, I created the following. Actually I just created two new UserControls which automatically set Gesture on the command and read it.
class HotMenuItem : MenuItem
{
public HotMenuItem()
{
SetBinding(InputGestureTextProperty, new Binding("Command.GestureText")
{
Source = this
});
}
}
class HotKeyBinding : KeyBinding
{
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property.Name == "Command" || e.Property.Name == "Gesture")
{
if (Command is IHotkeyCommand hotkeyCommand)
hotkeyCommand.Gesture = Gesture as KeyGesture;
}
}
}
The used interface
public interface IHotkeyCommand
{
KeyGesture Gesture { get; set; }
}
The Command is pretty much the same, it just implements INotifyPropertyChanged.
So the usage gets a bit cleaner in my opinion:
<Window.InputBindings>
<viewModels:HotKeyBinding Command="{Binding ExitCommand}" Gesture="Alt+F4" />
</Window.InputBindings>
<Menu>
<MenuItem Header="File" >
<viewModels:HotMenuItem Header="Exit" Command="{Binding ExitCommand}" />
</MenuItem>
</Menu>
I have a custom event named OnVisualChartRangeChanged being fired from a UserControl called HistoricChartControl.
I am using the control in my main application like this:
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModels:HistoricViewModel/>
</Window.DataContext>
<Grid>
<historicChart:HistoricChartControl >
<historicChart:HistoricChartControl
behaviours:ChartBehavior.OnVisualChartRangeChanged="VisualChartRangeChanged"/>
</historicChart:HistoricChartControl>
</Grid>
I want that instead of having the event being handled in the view via the method VisualChartRangeChanged, the event be handled in the ViewModel.
How could I modify my code for this to happen? It would be helpful if you could post specific code as I am new to the WPF way of doing things.
Thanks.
The solution is to use Commands.
Since its a UserControl you may manipulate it to implement ICommandSource interface.
Then your UserControl will be able to bind a Command to ViewModel.
Once the event is being fired you simply call the command which will invoke Execute() method from the ViewModel.
For commanding in WPF I suggest you to read following link:
http://msdn.microsoft.com/en-us/library/ms752308(v=vs.110).aspx
In your ViewModel you will have to offer a property of type ICommand.
EDIT Since you cannot manipulate your UserControl you will have to attach a command on it in XAML.
Interactivity is also an alternative to solve your issue. Take a look at this code:
xmlns:I="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<ListBox ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
Interactivity is a third party dll from Microsoft Blend.
If you have nuget in visual studio you will be able to find that dll. If not here is the link: http://www.nuget.org/packages/System.Windows.Interactivity.WPF/
This answer is changed once.
Interactivity Solution:
If the used behavior is reusable (you have its source) you can simply move the logic of this behavior to ViewModel level. Follow these 4 steps and it should work if the bindings and DataContext values are correct.
Add reference of both System.Windows.Interactivity and Microsoft.Expression.Interactions to your project:
Create a Command in ViewModel
//ViewModel:
public ICommand VisualChartRangeChangedCommand
{
get { return (ICommand)GetValue(VisualChartRangeChangedCommandProperty); }
set { SetValue(VisualChartRangeChangedCommandProperty, value); }
}
public static readonly DependencyProperty VisualChartRangeChangedCommandProperty =
DependencyProperty.Register("VisualChartRangeChangedCommand", typeof(ICommand), typeof(ViewModel), new UIPropertyMetadata(null));
//In ViewModel constructor:
VisualChartRangeChangedCommand = new ActionCommand(() => doStuff());
override the Behavior and add command ability to it
public class OnVisualChartRangeChangedWithCommand : OnVisualChartRangeChanged<HistoricChartControl>
{
//MyCommand Dependency Property
public ICommand MyCommand
{
get { return (ICommand)GetValue(MyCommandProperty); }
set { SetValue(MyCommandProperty, value); }
}
public static readonly DependencyProperty MyCommandProperty =
DependencyProperty.Register("MyCommand", typeof(ICommand), typeof(OnVisualChartRangeChangedWithCommand), new UIPropertyMetadata(null));
protected override void OnAttached()
{
//replace MouseEnter with other events related to OnVisualChartRangeChanged
AssociatedObject.MouseEnter += _eh;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseEnter -= _eh;
base.OnDetaching();
}
void _eh(object sender, MouseEventArgs e)
{
if (MyCommand != null)
MyCommand.Execute(null);
}
}
Link the ViewModel's Command to the overriden Behavior
xmlns:I="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:B="clr-namespace:CoreProject.Behaviors;assembly=CoreProject"
<historicChart:HistoricChartControl>
<I:Interaction.Behaviors>
<B:OnVisualChartRangeChangedWithCommand MyCommand="{Binding VisualChartRangeChangedCommand}"/>
</I:Interaction.Behaviors>
</historicChart:HistoricChartControl>
My WPF application has a number of buttons on its main window. I'm working on an edge case right now where the buttons should be disabled if the database is down or if the application can't establish a connection to its back-end (the back-end is a Windows service we've written).
There are two classes in my View Model library called DbMonitor and ComMonitor ("Com" for "Communications"). They descend from an the same abstract class, implement the IPropertyChanged interface, and have a property called Status (inherited from the abstract base class) which is an enumeration called DeviceStatuses with values Green, Yellow, and Red. I want the Buttons to be enabled only if the Status properties of both objects are Green?
How do get this binding to work in the Xaml, or do I have to do this in my code-behind.
Thanks
Tony
Are you using Commands with these buttons? If not, how hard would it be for you to switch to commands? The CanExecute part of ICommand seems like the way to go here.
There are three ways to go about this:
1. Bind IsEnabled propetry of your button to your Status property with a Converter to map from DeviceStatus to bool (enabled or not). I wouldn't recomend this.
2. RoutedCommands:
public static RoutedCommand MyButtonCommand = new RoutedCommand();
private void CommandBinding_MyButtonEnabled(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = Db.Monitor.Status==DeviceStatuses.Green;
}
and bind to it in XAML:
<Window.CommandBindings>
<CommandBinding
Command="{x:Static p:Window1.MyButtonCommand}"
Executed="buttonMyButton_Executed"
CanExecute="CommandBinding_MyButtonEnabled" />
</Window.CommandBindings>
<Button Content="My Button" Command="{x:Static p:Window1.MyButtonCommand}"/>
3. Implement ICommand:
public class MyCmd : ICommand {
public virtual bool CanExecute(object parameter) {
return Db.Monitor.Status==DeviceStatuses.Green;
}
}
Here the Command is a property of the appropriate view model:
class MyViewModel {
public MyCmd myCcmd { get; set; }
}
and you bind to it in XAML:
<Button Content="My Button" Command="{Binding myCmd}"/>
The third approach is normally the most flexible. You would need to inject the view model that has your status properties into the the Command constructor so you can implement your CanExecute logic.
After asking the question, I did some additional research and found a solution that works for me.
I created a class that implements the IMultiConverter interface that converts my DeviceStatuses enumeration to bool. Then, in my Xaml, I did this:
<Button ....>
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource DeviceStatusToBool}">
<Binding Path="..." />
<Binding Path="..." />
</MuntiBinding>
</Button.IsEnabled>
</Button>
This works very well.
I can't convert the buttons to use ICommand at this point. Not enough time before our release date.
Tony
I am using the MVVM Light framework to build a SL4 application. My simple app is composed primarily by a single main view (shellView), which is divided into multiple UserControls. They are just a convenient separation of the UI, therefore they don't have their own ViewModel.
The ShellView contains a Keypad (custom usercontrol) that contains multiple KeypadButtons (custom usercontrols).
I am quite sure (because I've checked) that the DataContext is set properly and it is used by all usercontrols in the hierarchy. (ShellView's Datacontext is ShellViewModel, Keypad's DataContext is ShellViewModel, etc.).
In the ShellViewModel I have a ICommand (RelayCommand) that is named "ProcessKey".
In the Keypad control, I have something like:
<controls:KeypadButton x:Name="testBtn" Text="Hello">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding PressStandardKeyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</controls:KeypadButton>
The KeypadButton is basically a Grid that contains a Button. The MouseLeftButtonUp event is caught and a custom "Click" event is fired. Let me show you some code to explain easily what I am doing:
public partial class KeypadButton : UserControl
{
public delegate void KeypadButtonClickHandler(object sender, RoutedEventArgs e);
public event KeypadButtonClickHandler Click;
public KeypadButton()
{
// Required to initialize variables
InitializeComponent();
}
private void innerButton_Click(object sender, MouseButtonEventArgs e)
{
if (Click != null)
Click(sender, new KeypadButtonEventArgs());
}
}
public class KeypadButtonEventArgs : RoutedEventArgs
{
public string test { get; set; }
}
Now, if I set a breakpoint to the body of innerButton_Click, I can see the Click is properly caught and it contains points to the RelayCommand. However, nothing happens: "Click(sender, new KeypadButtonEventArgs());" is executed but nothing more.
Why is this behaving so? Shouldnt execute the target function that is defined in the RelayCommand? Is maybe a scope-related issue?
Thanks in advance,
Cheers,
Gianluca.
As noted by other comments, this is probably related to the Click event not being a RoutedEvent.
As a quick hack you might be able to use MouseLeftButtonDown instead of the Click event on your UserControl.
<!-- Kinda Hacky Click Interception -->
<controls:KeypadButton x:Name="testBtn" Text="Hello">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding PressStandardKeyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</controls:KeypadButton>
Another option you could consider is inheriting from Button instead of UserControl. Silverlight Show has an article about inheriting from a TextBox that probably is relevant for this.
Routed events should be defined like this (see documentation):
public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
"Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));
// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
add { AddHandler(TapEvent, value); }
remove { RemoveHandler(TapEvent, value); }
}