I'm trying to understand why the Click event on the MenuItem is fired only once if you add an item to the ItemsSource.
But if you add items through another control (i.e a button) then the Click event keeps firing?
<StackPanel>
<Menu HorizontalAlignment="Left" Height="100" Width="100">
<MenuItem Header="MenuItem" Click="MenuItem_Click" ItemsSource="{Binding MyItems}" Height="100" Width="100">
</MenuItem>
</Menu>
</StackPanel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
((MyViewModel)DataContext).AddNewItem(null);
}
}
public class MyViewModel
{
public ObservableCollection<string> MyItems { get; set; } = new ObservableCollection<string>();
public MyViewModel() { }
public void AddNewItem(object x)
{
MyItems.Add(Guid.NewGuid().ToString());
}
}
The Click event of the MenuItem is captured when there are child items to display.
If you click on an added MenuItem that has no children, your event handler will be invoked again.
You could handle another event like for example PreviewMouseLeftButtonDown to overcome this "issue".
Related
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();
}
}
I have a listview's itemsource binded to a Observable collection of Animal class.
When the window loads up, listview displays all the items correctly.
But I have a button which deletes an item from the observablecollection which did not update the listview.
Expected Behaviour: Button click should delete first item in observable collection and update the UI
Observed Behaviour: Button click should deletes first item in observable collection but did not update the UI
public class Animal
{
public int Num { get; set; }
public string Name { get; set; }
}
public class ViewModel:INotifyPropertyChanged
{
private ObservableCollection<Animal> animals;
public ObservableCollection<Animal> Animals
{
get { return animals; }
set { animals = value; OnPropertyChanged("Animals"); }
}
public ViewModel()
{
Animals = new ObservableCollection<Animal>()
{
new Animal(){ Name="ASD", Num = 1},
new Animal(){ Name="XYZ", Num = 2},
};
}
public void Run()
{
Animals.RemoveAt(0);
}
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
<Grid DataContext="{Binding Source={StaticResource ViewModelDataSource}}">
<Button Content="Button" HorizontalAlignment="Left" Height="20" Margin="70,285,0,0" VerticalAlignment="Top" Width="100" Click="Button_Click"/>
<ListView x:Name="mylistview" HorizontalAlignment="Left" Height="212" Margin="42,47,0,0" VerticalAlignment="Top" Width="446" ItemsSource="{Binding Animals}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Num}"/>
<Label Content="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
public partial class MainWindow : Window
{
private ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel();
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
vm.Run();
}
}
ListView uses DataContext="{Binding Source={StaticResource ViewModelDataSource}}.
In a Window you create another instance of a view model (vm = new ViewModel();). After that you have 2 different instances and collections. vm.Run(); removes item from collection which is not connected to view.
You need to work with one instance, so try to find the same resource, which is used in the view:
public MainWindow()
{
InitializeComponent();
vm = (ViewModel)this.FindResource("ViewModelDataSource");
}
Also DataContext setter can be simplified:
`DataContext="{StaticResource ViewModelDataSource}"`
it is preferable to follow MVVM aproach and get rid of code behind:
1] declare command property in a viewmodel
public ICommand RunCmd { get; private set; }
2] use some ready-made ICommand implementation, e.g. RelayCommand or DelegateCommand and initialize RunCmd property from viewmodel constructor:
RunCmd = new RelayCommand(Run);
3] bind Button to that command:
<Button Content="Button"
HorizontalAlignment="Left"
Height="20" Width="100" Margin="70,285,0,0"
VerticalAlignment="Top"
Command="{Binding RunCmd}"/>
note, that Click handler is removed
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.
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
}
}