I have a problem with my XAML. I have a Menu Component, and I would like that it would work in Shortcut key too. I have XAML code, which doesn't work:
<MenuItem Header="_New" Name="New" Click="New_Click" InputGestureText="Ctrl+N">
<MenuItem.InputBindings>
<KeyBinding Key="N" Modifiers="control"/>
</MenuItem.InputBindings>
</MenuItem>
What is the solution? New_Click event works, but Shortcut key doesn't...
Using InputGestureText is only going to add text to the menu item per the documentation. You need to specify what needs to happen when the shortcut is actually performed. To do that you need to create an ICommand in your ViewModel, preferably, then bind that command to the MenuItem.Command
So your resulting code should look like this:
<MenuItem Header="_New" Name="New" Command="{Binding NewCommand}" InputGestureText="Ctrl+N">
assuming you have a public ICommand NewCommand {...} in your view model.
EDIT
Doing this requires a command because that's how WPF works. WPF != WinForms, where in WinForms you would use events and in WPF you want to try to use ICommand bindings. This is proven, and answers your question on why Command is required: it is because InputBinding implements the Command design pattern, so you're not going to really get a way to work around it.
So there isn't really a way to work around using the Click event handler instead of a Command with input gestures. If you're not in the position to use a Command as they are intended to be use (like in MVVM), then you will have to add an ICommand in code-behind, then programmatically set up the binding.
private RelayCommand qatRemoveItemCommand;
public ICommand RemoveItemCommand
{
get
{
if (this.RemoveItemCommand == null)
{
this.RemoveItemCommand = new RelayCommand(param => this.RemoveItem(), param => CanRemoveItem);
}
return this.RemoveItemCommand;
}
}
private void RemoveItem()
{
this.DeleteItem();
}
private bool CanRemoveItem
{
get
{
return true;
}
}
KeyBinding RemoveItemCmdKeyBinding = new KeyBinding(
this.RemoveItemCommand,
Key.N,
ModifierKeys.Control);
New.InputBindings.Add(OpenCmdKeyBinding);
<MenuItem Header="_New" Name="New" InputGestureText="Ctrl+N">
Note it may be required that you Remove or clear the InputBindings when the control is unloaded, but I think this will be as close as you can get, not to mention my original answer answer's your question; your request for additional information is a separate question in itself.
Also, do some research on the classes that implement inherit from InputBinding, KeyBinding and MouseBinding
It is possible to apply shortcut on click.We need to use command binding to achieve this functionality through XAML.
First of all,you need to bind command to the MenuItem and then bind the same command to keybinding.
Following is the working code for that:
<MenuItem Header="_New" Name="New" Command="{Binding NewCommand, Mode=TwoWay}" InputGestureText="Ctrl+N">
<MenuItem.InputBindings>
<KeyBinding Key="N" Modifiers="control" Command="{Binding NewCommand, Mode=TwoWay}"/>
</MenuItem.InputBindings>
</MenuItem>
NewCommand will be of Icommand type in your ViewModel.
I am providing C# code for your understanding of command binding:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyData();
}
}
public class MyData
{
public MyData()
{
this.NewCommand = new DelegateCommand(ExecuteShowMessage);
}
private void ExecuteShowMessage(object obj)
{
MessageBox.Show("Test");
}
public ICommand NewCommand { get; set; }
}
Above code will work as per your requirement.Please Let me know if you have any queries regarding this.
Thank You
Here's my utility class for that:
using System;
using System.Windows;
using System.Windows.Input;
namespace NNN
{
/// <summary>This utility class translates ICommand calls to RoutedEventHandler calls</summary>
class c2e : ICommand
{
readonly RoutedEventHandler eh;
public c2e( RoutedEventHandler eh )
{
this.eh = eh;
}
public event EventHandler CanExecuteChanged;
bool ICommand.CanExecute( object parameter )
{
return true;
}
void ICommand.Execute( object parameter )
{
var a = new RoutedEventArgs();
this.eh( this, a );
}
}
static class Hotkey
{
/// <summary>Register event handler for hotkey</summary>
public static void registerHotkey( this Window wnd, Key key, ModifierKeys modifier, RoutedEventHandler handler )
{
ICommand cmd = new c2e( handler );
InputBinding ib = new InputBinding( cmd,new KeyGesture( key, modifier ) );
wnd.InputBindings.Add( ib );
}
}
}
Usage example:
public MainWindow()
{
InitializeComponent();
this.registerHotkey( Key.O, ModifierKeys.Control, menuOpen );
}
As non of the above solutions worked for me, I would like to suggest another solution here, by using RoutedCommand.
XAML
<Window x:Class="CH02.ContextMenuDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CH02.ContextMenuDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:MainWindow.MenuItemClickCommand}"
CanExecute="CanExecute"
Executed="OnMenuItemClicked"/>
</Window.CommandBindings>
<Grid>
<TextBlock Text="Right Click here to open Context Menu!"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="My Menu Item"
Name="MyMenuItem"
Command="{x:Static local:MainWindow.MenuItemClickCommand}">
</MenuItem>
<Separator />
<MenuItem Header="Another Menu Item"
IsCheckable="True"
IsChecked="True"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Grid>
</Window>
CS
public partial class MainWindow : Window
{
private static ICommand _clickCommand;
public static ICommand
MenuItemClickCommand => _clickCommand ??
(_clickCommand = new RoutedUICommand(
text: "Options",
name: "MenuItemClickCommand",
ownerType: typeof(MainWindow),
inputGestures: new InputGestureCollection(
inputGestures: new InputGesture[] {
new KeyGesture(Key.N, ModifierKeys.Control)
})));
public MainWindow()
{
InitializeComponent();
Focus();
}
private void OnMenuItemClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("Context menu item clicked!");
}
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true; // or other logic
}
}
Related
Revised: I apologize for missing some important descriptions in the first version, now the problem should be well-defined:
so I'm making a toy CAD program with following views:
MainWindow.xaml
CustomizedUserControl.xaml
CustomizedUserControl is a Tab within MainWindow, and its DataContext is defined in MainWindow.xaml as:
<Window.Resources>
<DataTemplate DataType="{x:Type local:CustomizedTabClass}">
<local:UserControl1/>
</DataTemplate>
</Window.Resources>
And CustomizedUserControl.xaml provides a canvas and a button, so when the button is pressed the user should be able to draw on the canvas. As the following code shows, the content of Canvas is prepared by the dataContext, "tabs:CustomizedTabClass".
CustomizedUserControl.xaml
<CustomizedUserControl x:Name="Views.CustomizedUserControl11"
...
>
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
...
<canvas x:Name="CADCanvas"
Drawing="{Binding Drawing ,Mode=TwoWay}" >
</canvas>
It is also notable that I used an external library, Fody/PropertyChanged, in all classes so property notifications would be injected without further programming.
CustomizedUserControl.xaml.cs
using PropertyChanged;
using System.ComponentModel;
using System.Windows.Controls;
[AddINotifyPropertyChangedInterface]
public partial class CustomizedUserControl: Usercontrol, INotifyPropertyChanged{
public CADDrawingCommands DrawingCommands { get; set; }
public CustomizedUserControl()
{
InitializeComponent();
DrawingCommands = new CADDrawingCommands(this);
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
CADDrawingCommands.cs
using PropertyChanged;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows;
[AddINotifyPropertyChangedInterface]
public class CADDrawingCommands : INotifyPropertyChanged{
UserControl _drawableTab;
public string Button1Name { get; set; } = "TestForDataBinding";
public RoutedCommand LinesChainCommand { get; set; } = new RoutedCommand();
public CADDrawingCommands(UserControl dTab){
_drawableTab = dTab;
CommandBinding lineCommandBinding = new CommandBinding(LinesChainCommand,
(object sender, ExecutedRoutedEventArgs e) =>
{
MessageBox.Show("Test");
//Draw on canvas inside CustomizedUserControl (modify Drawing property in CustomizedTabClass)
}, (object sender, CanExecuteRoutedEventArgs e) => { e.CanExecute = true; });
_drawableTab.CommandBindings.Add(lineCommandBinding);
}
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
The Content of Button is set correctly, as I can read the string defined in Button1Name:
Therefore I suppose the Data Binding for Command is also ok. IsEnabled has been set to true and CanExecute of the CommandBinding would only return true.
Why is my button still greyed out and not clickable?
If I define the button inside a Window instead of UserControl (and set the datacontext of the Window to its own code behind, the button will be clickable! Why?
Thank you for your time! Hopefully would somebody help me cuz I've run out of ideas and references.
Made the simplest example.
Everything works as it should.
BaseInpc is my simple INotifyPropertyChanged implementation from here: BaseInpc
using Simplified;
using System.Windows;
using System.Windows.Input;
namespace CustomizedUserControlRoutedCommand
{
public class CADDrawingCommands : BaseInpc
{
UIElement _drawableTab;
private string _button1Name = "TestForDataBinding";
public string Button1Name { get => _button1Name; set => Set(ref _button1Name, value); }
public static RoutedCommand LinesChainCommand { get; } = new RoutedCommand();
public CADDrawingCommands(UIElement dTab)
{
_drawableTab = dTab;
CommandBinding lineCommandBinding = new CommandBinding(LinesChainCommand,
(object sender, ExecutedRoutedEventArgs e) =>
{
MessageBox.Show("Test");
//Draw on canvas inside CustomizedUserControl (modify Drawing property in CustomizedTabClass)
}, (object sender, CanExecuteRoutedEventArgs e) => { e.CanExecute = true; });
_drawableTab.CommandBindings.Add(lineCommandBinding);
}
}
}
<UserControl x:Name="CustomizedUserControl11" x:Class="CustomizedUserControlRoutedCommand.CustomizedUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CustomizedUserControlRoutedCommand"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{x:Static local:CADDrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
</Grid>
</UserControl>
using System.Windows.Controls;
namespace CustomizedUserControlRoutedCommand
{
public partial class CustomizedUserControl : UserControl
{
public CADDrawingCommands DrawingCommands { get; }
public CustomizedUserControl()
{
DrawingCommands = new CADDrawingCommands(this);
InitializeComponent();
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
}
}
<Window x:Class="CustomizedUserControlRoutedCommand.TestCustomizedUserControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CustomizedUserControlRoutedCommand"
mc:Ignorable="d"
Title="TestCustomizedUserControlWindow" Height="450" Width="800">
<Grid>
<local:CustomizedUserControl/>
</Grid>
</Window>
If you showed your code in full, then I see the following problems in it:
You are setting the value incorrectly for the DrawingCommands property.
In this property, you do not raise PropertyChanged.
The binding in the Button is initialized in the InitializeComponent() method. At this point, the property is empty, and when you set a value to it, the binding cannot find out.
There are two ways to fix this:
Raise PropertyChanged in the property;
If you set the property value once in the constructor, then set it immediately in the initializer. Make the property "Read Only". This way, in my opinion, is better.
public CADDrawingCommands DrawingCommands { get; }
public FileEditTabUserControl()
{
DrawingCommands = new CADDrawingCommands(this);
InitializeComponent();
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
You have a button bound to a command in the DrawingCommands.LinesChainCommand property.
But to this property, you assign an empty instance of the = new RoutedCommand () routing command.
This looks pointless enough.
If you need a routable command, create it in the "Read Only" static property.
This will make it much easier to use in XAML:
public static RoutedCommand LinesChainCommand { get; } = new RoutedCommand();
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{x:Static local:DrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
Raising PropertyChanged in CADDrawingCommands properties is also not visible in your code.
If it really does not exist, then the binding is also unaware of changing property values.
I am trying to assign a shortcut to a WPF radio button which is inside a grid which is inside a tab item. I tried simply using the underline character as shown which marks the label with an underline on the letter "F" but when sending the keys "Alt+f" it simply will not select the radio button.
<RadioButton Name="DesktopRadioButtonFlags" Content="_Flags" HorizontalAlignment="Left"
Margin="39,39,0,0" Foreground="White" VerticalAlignment="Top" FlowDirection="RightToLeft"/>
You should use input bindings
xaml
<Window.InputBindings>
<KeyBinding Modifiers="Alt" Key="F" Command="{Binding CheckRadioButton1Command}"/>
</Window.InputBindings>
<Grid>
<RadioButton Content="_Flags" IsChecked="{Binding IsRadioChecked}"/>
</Grid>
viewmodel
public class MyViewModel : INotifyPropertyChanged
{
private bool _isRadioChecked;
public bool IsRadioChecked
{
get => _isRadioChecked;
set
{
if (_isRadioChecked == value)
return;
_isRadioChecked = value;
OnPropertyChanged(nameof(IsRadioChecked));
}
}
private ICommand _checkRadioButton1Command;
public ICommand CheckRadioButton1Command => _checkRadioButton1Command ?? (_checkRadioButton1Command = new ActionCommand(CheckRadioButton1));
private void CheckRadioButton1()
{
IsRadioChecked = true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
control or windows code to set ViewModel as DataContext (you should pass your initial data to windows or control constructor)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
I am trying to assign a shortcut to a WPF radio button which is inside a grid which is inside a tab item. I tried simply using the underline character as shown which marks the label with an underline on the letter "F" but when sending the keys "Alt+f" it simply will not select the radio button.
<RadioButton Name="DesktopRadioButtonFlags" Content="_Flags" HorizontalAlignment="Left"
Margin="39,39,0,0" Foreground="White" VerticalAlignment="Top" FlowDirection="RightToLeft"/>
You should use input bindings
xaml
<Window.InputBindings>
<KeyBinding Modifiers="Alt" Key="F" Command="{Binding CheckRadioButton1Command}"/>
</Window.InputBindings>
<Grid>
<RadioButton Content="_Flags" IsChecked="{Binding IsRadioChecked}"/>
</Grid>
viewmodel
public class MyViewModel : INotifyPropertyChanged
{
private bool _isRadioChecked;
public bool IsRadioChecked
{
get => _isRadioChecked;
set
{
if (_isRadioChecked == value)
return;
_isRadioChecked = value;
OnPropertyChanged(nameof(IsRadioChecked));
}
}
private ICommand _checkRadioButton1Command;
public ICommand CheckRadioButton1Command => _checkRadioButton1Command ?? (_checkRadioButton1Command = new ActionCommand(CheckRadioButton1));
private void CheckRadioButton1()
{
IsRadioChecked = true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
control or windows code to set ViewModel as DataContext (you should pass your initial data to windows or control constructor)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
To simplify, Criticized for writing a novel w/no code a month ago, I made a quick wpf project (uses MVVM) with 2 buttons on the UI.
When a button is clicked, I need my ViewModel to know which one, to route the Speech Synthesizer to the correct Text to Speak. Thanks 4 any help!!
Simple UI Image
<Window x:Class="Wpf_School_Announce.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wpf_School_Announce"
xmlns:vm="clr-namespace:Wpf_School_Announce.ViewModels"
mc:Ignorable="d"
Title="Announcements" Height="236.436" Width="293.218">
<Window.Resources>
<vm:ViewModelBase x:Key="viewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Source=viewModel}">
<StackPanel Margin="0,10">
<Button x:Name="btn1stBell" Content="1st Bell" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="0,10"
Command="{Binding ParameterCommand, Source={StaticResource viewModel}}"
CommandParameter="{Binding Command, ElementName=btn1stBell}"/>
<Button x:Name="btnLunchMenu" Content="Lunch Menu" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="0,10"
Command="{Binding ParameterCommand, Source={StaticResource viewModel}}"
CommandParameter="{Binding Command, ElementName=LunchMenu}"/>
</StackPanel>
</Grid>
</Window>
namespace Wpf_School_Announce.ViewModels
{
public class ViewModelBase
{
public ParameterCommand ParameterCommand { get; set; }
public ViewModelBase()
{
ParameterCommand = new ParameterCommand(this);
}
public void ParameterMethod(string <Not sure what needs to go here>)
{
Debug.WriteLine("Parameter Comand:{0}", AnnoucementModel);
//Todo: Need to find out which UI button was clicked to direct The Speech Synthesozer to the correct Speech Text.
}
}
}
namespace Wpf_School_Announce.ViewModels.Commands
{
public class ParameterCommand : ICommand
{
public ViewModelBase ViewModel { get; set; }
public ParameterCommand(ViewModelBase viewModel)
{
ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ViewModel.ParameterMethod(parameter as String);
}
}
}
It is a bad solution to just have one command on your viewmodel and to bind every button to it. If you have different things to be executed, define different commands. For that you either have to define a separate class with a dedicated Execute metod for each command or you can use something like RelayCommand of MvvmLight, where you can pass delegates upon creation of each command like this
public class ViewModelBase
{
public RelayCommand BellCommand...
public RelayCommand LunchCommand...
public ViewModelBase()
{
this.BellCommand = new RelayCommand(this.ExecuteBell);
this.LunchCommand = new RelayCommand(this.ExecuteLunch);
}
private void ExecuteBell(object Parameter) {...}
private void ExecuteLunch(object Parameter) {...}
}
and in your XAML
<Button Command="{Binding Path=BellCommand}"... />
<Button Command="{Binding Path=LunchCommand}" ... />
This way you have separate places for the individual logic and your viewmodel must not know anything about your ui - which is good.
Hope it helps.
XAML:
<Button CommandParameter="command_name" Command="{Binding OnClick}" Content="Click Me"></Button>
Event.cs:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
private ICommand onClick;
public ICommand OnClick
{
get
{
return onClick ?? (onClick = new RelayCommand(clickSwitch));
}
}
Class.cs:
private async void clickSwitch(System.Object obj)
{
switch (obj.ToString())
{
case "command_name":
//code
break;
}
I'l start by letting a picture do some talking.
So you see, I want to create a WPF user control that supports binding to a parent window's DataContext. The user control is simply a Button and a ListBox with a custom ItemTemplate to present things with a Label and a Remove Button.
The Add button should call an ICommand on the main view model to interact with the user in selecting a new thing (instance of IThing). The Remove buttons in the ListBoxItem in the user control should similarly call an ICommand on the main view model to request the related thing's removal. For that to work, the Remove button would have to send some identifying information to the view model about the thing requesting to be removed. So there are 2 types of Command that should be bindable to this control. Something like AddThingCommand() and RemoveThingCommand(IThing thing).
I got the functionality working using Click events, but that feels hacky, producing a bunch of code behind the XAML, and rubs against the rest of the pristine MVVM implementation. I really want to use Commands and MVVM normally.
There's enough code involved to get a basic demo working, I am holding off on posting the whole thing to reduce confusion. What is working that makes me feel like I'm so close is the DataTemplate for the ListBox binds the Label correctly, and when the parent window adds items to the collection, they show up.
<Label Content="{Binding Path=DisplayName}" />
While that displays the IThing correctly, the Remove button right next to it does nothing when I click it.
<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
This isn't terribly unexpected since the specific item isn't provided, but the Add button doesn't have to specify anything, and it also fails to call the command.
<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
So what I need is the "basic" fix for the Add button, so that it calls the parent window's command to add a thing, and the more complex fix for the Remove button, so that it also calls the parent command but also passes along its bound thing.
Many thanks for any insights,
This is trivial, and made so by treating your UserControl like what it is--a control (that just happens to be made up from other controls). What does that mean? It means you should place DependencyProperties on your UC to which your ViewModel can bind, like any other control. Buttons expose a Command property, TextBoxes expose a Text property, etc. You need to expose, on the surface of your UserControl, everything you need for it to do its job.
Let's take a trivial (thrown together in under two minutes) example. I'll leave out the ICommand implementation.
First, our Window
<Window x:Class="UCsAndICommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:UCsAndICommands"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<t:ViewModel />
</Window.DataContext>
<t:ItemsEditor Items="{Binding Items}"
AddItem="{Binding AddItem}"
RemoveItem="{Binding RemoveItem}" />
</Window>
Notice we have our Items editor, which exposes properties for everything it needs--the list of items it is editing, a command to add a new item, and a command to remove an item.
Next, the UserControl
<UserControl x:Class="UCsAndICommands.ItemsEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:t="clr-namespace:UCsAndICommands"
x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="{x:Type t:Item}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveItem, ElementName=root}"
CommandParameter="{Binding}">Remove</Button>
<TextBox Text="{Binding Name}" Width="100"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Button Command="{Binding AddItem, ElementName=root}">Add</Button>
<ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
</StackPanel>
</UserControl>
We bind our controls to the DPs defined on the surface of the UC. Please, don't do any nonsense like DataContext=this; as this anti-pattern breaks more complex UC implementations.
Here's the definitions of these properties on the UC
public partial class ItemsEditor : UserControl
{
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(IEnumerable<Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable<Item> Items
{
get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
#endregion
#region AddItem
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register(
"AddItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand AddItem
{
get { return (ICommand)GetValue(AddItemProperty); }
set { SetValue(AddItemProperty, value); }
}
#endregion
#region RemoveItem
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.Register(
"RemoveItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand RemoveItem
{
get { return (ICommand)GetValue(RemoveItemProperty); }
set { SetValue(RemoveItemProperty, value); }
}
#endregion
public ItemsEditor()
{
InitializeComponent();
}
}
Just DPs on the surface of the UC. No biggie. And our ViewModel is similarly simple
public class ViewModel
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public ICommand RemoveItem { get; private set; }
public ViewModel()
{
Items = new ObservableCollection<Item>();
AddItem = new DelegatedCommand<object>(
o => true, o => Items.Add(new Item()));
RemoveItem = new DelegatedCommand<Item>(
i => true, i => Items.Remove(i));
}
}
You are editing three different collections, so you may want to expose more ICommands to make it clear which you are adding/removing. Or you could cheap out and use the CommandParameter to figure it out.
Refer the below code.
UserControl.XAML
<Grid>
<ListBox ItemsSource="{Binding Things}" x:Name="lst">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ThingName}" Margin="3"/>
<Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Window.Xaml
<Window x:Class="MultiBind_Learning.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiBind_Learning"
Title="Window1" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/>
<local:UserControl2/>
</StackPanel>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ThingViewModel();
}
}
ThingViewModel.cs
class ThingViewModel
{
private ObservableCollection<Thing> things = new ObservableCollection<Thing>();
public ObservableCollection<Thing> Things
{
get { return things; }
set { things = value; }
}
public ICommand AddCommnd { get; set; }
public ICommand RemoveCommand { get; set; }
public ThingViewModel()
{
for (int i = 0; i < 10; i++)
{
things.Add(new Thing() { ThingName="Thing" +i});
}
AddCommnd = new BaseCommand(Add);
RemoveCommand = new BaseCommand(Remove);
}
void Add(object obj)
{
things.Add(new Thing() {ThingName="Added New" });
}
void Remove(object obj)
{
things.Remove((Thing)obj);
}
}
Thing.cs
class Thing :INotifyPropertyChanged
{
private string thingName;
public string ThingName
{
get { return thingName; }
set { thingName = value; OnPropertyChanged("ThingName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
BaseCommand.cs
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
{
_method = method;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
Instead of Base command you can try RelayCommand from MVVMLight or DelegateCommand from PRISM libraries.
By default, your user control will inherit the DataContext of its container. So the ViewModel class that your window uses can be bound to directly by the user control, using the Binding notation in XAML. There's no need to specify DependentProperties or RoutedEvents, just bind to the command properties as normal.