WPF Input KeyBinding for a ListBox Item - c#

I'm having a WPF Application, programatically I'm setting the focus to the ListBox Item, after that using UP / Down arrow I'm navigating from one Item to another Item. I need to Implement ENTER KeyEvent for that appropriate Item, it should trigger the ICommand SelectItemCommand in the ViewModel.
Consider the ViewModel Code:
public class MobileViewModel
{
public ObservableCollection<Mobile> MobileCollection { get; set; }
public MobileViewModel()
{
MobileCollection = new ObservableCollection<Mobile>()
{
new Mobile() { ID = 1, Name = "iPhone 6S", IsSelected = false },
new Mobile() { ID = 2, Name = "Galaxy S7", IsSelected = false }
}
}
public ICommand SelectItemCommand
{
get
{
return new DelegatingCommand((obj) =>
{
// Enter Key Event Operation
});
}
}
}
public class Mobile
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
The XAML Code is
<ListBox ItemsSource="{Binding MobileCollection}" x:Name="KeyListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Command="{Binding SelectItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MobileViewModel}}}" CommandParameter="{Binding }">
<Button.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SelectItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MobileViewModel}}}" CommandParameter="{Binding }" />
</Button.InputBindings>
<Button.Content>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My requirement is to trigger the ICommand while on Keyboard ENTER Key hit. I tried the KeyBinding inside the Button, but its not happening. Kindly assist me.

The ListBox Key Binding is
<ListBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding DataContext.SelectItemCommand, ElementName=KeyListBox}"
CommandParameter="{Binding SelectedItem,
RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"/>
</ListBox.InputBindings>
You should specify the Element Name and bind using DataContext. Then It should be work
The Complete XAML Source Code is
<ListBox Name="KeyListBox" ItemsSource="{Binding MobileCollection}" HorizontalAlignment="Left" Height="Auto" VerticalAlignment="Top" Width="300" HorizontalContentAlignment="Stretch">
<ListBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding DataContext.SelectItemCommand, ElementName=lstBox}" CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"/>
</ListBox.InputBindings>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Button Command="{Binding DataContext.SelectItemCommand, ElementName=lstBox}" CommandParameter="{Binding }" Foreground="Black" Padding="12 10" HorizontalContentAlignment="Left">
<Button.Content>
<StackPanel>
<CheckBox IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Foreground="#404040">
<CheckBox.Content>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name, IsAsync=True}" TextWrapping="Wrap" MaxWidth="270" />
</StackPanel>
</CheckBox.Content>
</CheckBox>
</StackPanel>
</Button.Content>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

You could put the InputBinding on the ListBox itself, passing through the selected item as the command parameter.
<ListBox ItemsSource="{Binding MobileCollection}" x:Name="KeyListBox">
<ListBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SelectItemCommand}" CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"/>
</ListBox.InputBindings>
</ListBox>

Related

Binding Tag property of checkbox to ancestor datacontext

I know this is a frequently question but after viewing lots of question in this context i still did not find working solution.
I have this MainWindow
public partial class MainWindow : Window
{
public ObservableCollection<Camera> Cameras { get; set; } = new ObservableCollection<Camera>();
public ObservableCollection<Group> Groups { get; set; } = new ObservableCollection<Group>();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
And this class
public class Group : INotifyPropertyChanged
{
private int _number;
[XmlAttribute("Number")]
public int Number
{
get { return _number; }
set
{
_number = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This is part of the MainWindow.xaml (the relevant part)
<StackPanel>
<Button Click="Button_Click_1" Margin="55,0,0,0" Padding="4">Add Group</Button>
<ListView Grid.ColumnSpan="3" Grid.Row="1" ItemsSource="{Binding Groups,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Margin="3" Width="30" Grid.Column="0" Text="{Binding Number}"></TextBlock>
<ComboBox Margin="3" Width="50" Grid.Column="5" ItemsSource="{Binding DataContext.Cameras, RelativeSource={RelativeSource AncestorType=ListView}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.Number}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<!--<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding Path=DataContext.Number, RelativeSource={RelativeSource AncestorType=ListView}}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />-->
<TextBlock Text="{Binding Path=Name, Mode=TwoWay}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
The list of checkbox inside the comboxbox will be filled according to the list of the cameras.
I want to bind the property "Tag" of the inner checkbox to the member Groups.Number like i did with the textblock above it.
The reason behind this (maybe you have another solution) is that the list of groups is a dynamic group, and i want to identify from which group the checkbox was checked.
I've tried everything with the ancestor issue but nothing seems to work.
other things i've tried are:
<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.Number}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.Groups.Number}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=Number}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=Groups.Number}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
What do i miss here?
Ty!
ListView has the wron DataContext. It is outside the DataTemplate and is set to MainWindow. The DataTemplate that targets Group has the proper DataContext, of course the current Group item. You must chose an element of this DataTemplate as binding source. You couls bind to the ComboBox.DataContext:
<CheckBox Tag="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.Number}" />

WPF Window to edit the user.settings. Why values are not changing?

I'm working on a wpf window to edit the user settings.
This is what I did so far:
<ListView Grid.Row="1"
ItemsSource="{Binding Source={x:Static properties:Settings.Default}, Path=PropertyValues}"
HorizontalContentAlignment="Stretch" Background="LightGray"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel HorizontalAlignment="Stretch"
IsEnabled="{Binding DataContext.Enabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<Label Width="200" Content="{Binding Name}"/>
<Label Width="200" Content="{Binding Path=Property.PropertyType}" Foreground="Gray" FontStyle="Italic"/>
<ContentControl VerticalContentAlignment="Center" Content="{Binding Path=PropertyValue}">
<ContentControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Int32}">
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ResourceDictionary>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The values are showing but they're not updated in Properties.Settings.Default when I change and save them with Properties.Settings.Default.Save();.
Is the two way binding correct?
Thanks
As suggested in the comments and in this answer, I need to encapsulate the System.Configuration.SettingsPropertyValue into a ViewModel that implements INotifyPropertyChanged. Otherwise the binding won't work.
ViewModel:
public class SettingsPropertyValueProxy : INotifyPropertyChanged
{
public string Name { get; }
public Type PropertyType => PropertyValue.GetType();
public object PropertyValue
{
get
{
return Properties.Settings.Default[Name];
}
set
{
try
{
Properties.Settings.Default[Name] = Convert.ChangeType(value, PropertyType);
Properties.Settings.Default.Save();
}
catch
{ }
}
}
public SettingsPropertyValueProxy(string name)
{
Name = name;
Properties.Settings.Default.PropertyChanged += (sender, e) => _OnPropertyChanged(e.PropertyName);
}
private void _OnPropertyChanged(string propertyName)
{
if (propertyName == Name) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PropertyValue)));
}
public event PropertyChangedEventHandler PropertyChanged;
}
New Property to bind:
public IEnumerable<SettingsPropertyValueProxy> Values { get; }
= Properties.Settings.Default.Properties
.Cast<SettingsProperty>()
.Select(p => new SettingsPropertyValueProxy(p.Name))
.OrderBy(p => p.Name)
.ToArray();
Correct View and Correct DataTemplates:
<ListView Grid.Row="1"
ItemsSource="{Binding Path=Values}"
HorizontalContentAlignment="Stretch" Background="LightGray"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel HorizontalAlignment="Stretch"
IsEnabled="{Binding DataContext.Enabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<Label Width="200" Content="{Binding Path=Name}"/>
<Label Width="200" Content="{Binding Path=PropertyType}" Foreground="Gray" FontStyle="Italic"/>
<!--<TextBox Text="{Binding Path=PropertyValue, Mode=TwoWay}"/>-->
<ContentControl VerticalContentAlignment="Center" Content="{Binding Path=PropertyValue}">
<ContentControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=PropertyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}, Path=DataContext}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding Path=PropertyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}, Path=DataContext}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Int32}">
<TextBox Text="{Binding Path=PropertyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}, Path=DataContext}"/>
</DataTemplate>
</ResourceDictionary>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

bind command to nested listbox

I'm trying to bind a Command to a nested ListBox inside an itemsControl but somehow I can't find the right DataContext to execute the command.
XAML
<UserControl DataContext="{Binding VM1, Source={StaticResource Locator}}"
<ItemsControl ItemsSource="{Binding SceneList}"
<ListBox ItemsSource="{Binding PartialScenes}"
SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.SelectedPartialScene}" >
<ListBox.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DeleteSelectedPartialCommand}"/>
</ListBox.InputBindings>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Run Text="{Binding Path=Label}"/>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ItemsControl
</UserControl
VM
public class VM1
{
public()
{
DeleteSelectedPartialCommand= new RelayCommand(Delete);
}
public RelayCommand DeleteSelectedPartialCommand{ get; private set; }
private void Delete()
{
Collection.Remove(SelectedItem);
}
}
I've tried
Command="{Binding Path=DataContext.DeleteSelectedPartialCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}" />
and
Command="{Binding Path=DataContext.DeleteSelectedPartialCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}" />
Both options are not working.
You need to access the root DataContext which is in UserControl.
Add a name to your UserControl
x:Name="MyUserControl"
And then for your command:
Command"{Binding ElementName=MyUserControl, Path=DataContext.DeleteSelectedPartialCommand}"

Adding ContextMenu to TabItem through DataTemplate using MVVM

I have a TabControl with a DataTemplate.. I've tried having a ContextMenu on the TabControl, which kinda worked, but only on the selected item - not the tab header I was actually clicking on. I tried adding the ContextMenu to the TabItem header instead, the menu is showing, but the functionality is no longer working.
View:
<TabControl VerticalAlignment="Center" HorizontalAlignment="Left" Margin="50,0,0,0" ItemsSource="{Binding Playlists}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PlaylistID}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" FontWeight="ExtraLight" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding MenuRenameCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
<MenuItem Header="Delete" FontWeight="ExtraLight" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding MenuDeleteCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate></DataTemplate>
</TabControl.ContentTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding SelectionChangedCommand}"
CommandParameter="{Binding Path=SelectedItem,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabControl}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TabControl>
ViewModel:
public ObservableCollection<PlaylistModel> Playlists
{
get { return _playlists; }
set
{
if (value.Equals(_playlists)) return;
_playlists = value;
RaisePropertyChanged("Playlists");
}
}
public RelayCommand<PlaylistModel> MenuRenameCommand { get; set; }
public RelayCommand<PlaylistModel> MenuDeleteCommand { get; set; }
...
public void Initialize()
{
MenuRenameCommand = new RelayCommand<PlaylistModel>((e) =>
{
if (e == null) return;
string newName = DialogInputWindow.Prompt("Enter a new name for the playlist", "Rename playlist");
if (String.IsNullOrEmpty(newName) || String.IsNullOrWhiteSpace(newName) || !Mapp.Move(e.PlaylistID, newName))
{
DialogInputWindow.Message("Could not rename playlist. Does a playlist with that name already exist?", "Rename failed");
return;
}
Playlists[Playlists.IndexOf(e)].PlaylistID = newName;
});
}
I can't get this to work, so I hope you guys can help.
Thanks in advance.

How can I bind a command from my IntemsControl list to my main ViewModel?

How can I bind a command from my IntemsControl list to my main ViewModel?
<Window x:Class="MemoryGame.MainWindow"
...
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<me:ColorConverter x:Key="ColorConverter"/>
</Window.Resources>
<Grid x:Name="LayoutRoot" Background="#FF44494D">
<ItemsControl ItemsSource="{Binding GameBoard.CardList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle x:Name="Card01" Fill="{Binding Converter={StaticResource ColorConverter}}" Height="100" Width="100" Margin="10,10,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Stroke="Black">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding SelectCardCommand, ???????????}" CommandParameter="{Binding Name, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Rectangle}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
...
SelectCardCommand is defined in my main window ViewModel. I already tryed to add RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window} but it doesn't work.
---- EDIT More information ----
In my DataContext ViewModel I have this :
public class MainViewModel : ViewModelBase
{
private RelayCommand<string> _selectCardCommand;
public RelayCommand<string> SelectCardCommand
{
get
{
return _selectCardCommand;
}
}
public MainViewModel()
{
_selectCardCommand = new RelayCommand<string>((s) => DoSelectCardCommand(s));
// GameBoard = new Board();
// this.StartGame();
}
private void DoSelectCardCommand(string card)
{
// Code here
}
}
you can use two methods
using RelativeSource
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding DataContext.SelectCardCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Rectangle}}"/>
</i:EventTrigger>
or using ElementName
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding DataContext.SelectCardCommand, ElementName=LayoutRoot}"
CommandParameter="{Binding Name, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Rectangle}}"/>
</i:EventTrigger>
EDIT
after executing the code I realized the reason, as rectangle do not have any event named Click so it does not bind, however you can bind to UIElement.MouseDown
alternatively I came up with a different approach to solve your issue.
I modify the template a bit to use Button and templated the button as needed and also changed the ItemsPanelTemplate so it look more like a memory game
<ItemsControl ItemsSource="{Binding GameBoard.CardList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding DataContext.SelectCardCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}}"
CommandParameter="{Binding}">
<Button.Template>
<ControlTemplate>
<Rectangle Fill="{Binding Converter={StaticResource ColorConverter}}"
Height="100"
Width="100"
Margin="10,10,0,0"
Stroke="Black">
</Rectangle>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
the command
#region SelectCardCommand
public RelayCommand<Card> SelectCardCommand { get; private set; }
#endregion
public MainViewModel()
{
SelectCardCommand = new RelayCommand<Card>((s) => DoSelectCardCommand(s));
GameBoard = new Board();
this.StartGame();
}
private void DoSelectCardCommand(Card card)
{
if (card != null)
card.Upside = true;
}
this will help you remove int.Parse(card.Substring(4,2)); and other string manipulations too

Categories