Adding ContextMenu to TabItem through DataTemplate using MVVM - c#

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.

Related

Checked event command binding on RadioButton doesn't work when the control is dynamically created

I have 3 RadioButton controls that I add to my WPF program dynamically, with the help of a custom class I've defined and an ItemsControl. My custom class is simple; it has two properties, one an identifier and the other a bool to represent the Checked/Unchecked status.
public class RadioButtonItem
{
public string ItemName { get; set; }
public bool IsChecked { get; set; }
}
I'm using MVVMLight, and a ViewModel, and DataContext etc are all properly setup.
In the MainWindow I have the RadioButton controls added like so:
<Grid x:Name="LayoutRoot">
<ItemsControl ItemsSource="{Binding Path=RadioButtons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding ItemName}"
IsChecked="{Binding IsChecked}"
Margin="12" GroupName="ABC">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding RadioButtonCheckedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Note that i is the Interactivity namespace:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Now in my ViewModel, I create a list of RadioButtonItem instances, and at loading time it creates 3 RadioButton controls in the XAML end just fine, as I expect it to. Then using MVVMLight command RelayCommand, I hook up the Checked event to the RadioButtonCheckedCommand command, which in turn is supposed to call the RadioButtonChecked() private method.
public class MainViewModel : ViewModelBase
{
private List<RadioButtonItem> _radioButtons;
public List<RadioButtonItem> RadioButtons
{
get => _radioButtons;
set { _radioButtons = value; RaisePropertyChanged(); }
}
public RelayCommand RadioButtonCheckedCommand { get; private set; }
public MainViewModel()
{
RadioButtons = new List<RadioButtonItem>
{
new RadioButtonItem() { ItemName = "A", IsChecked = false },
new RadioButtonItem() { ItemName = "B", IsChecked = false },
new RadioButtonItem() { ItemName = "C", IsChecked = false },
};
RadioButtonCheckedCommand = new RelayCommand(RadioButtonChecked);
}
private void RadioButtonChecked()
{
}
}
But, when I run the program it doesn't call the said event. Neither does it give me any errors. How do I get an event to work in a dynamically created control like this?
To verify my command bindings etc. work, I also created three static RadioButton controls on my window, as shown below, and attached them to the same command and they work fine on Checked event.
<StackPanel Grid.Row="1" HorizontalAlignment="Center">
<RadioButton Content="X" Margin="12">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding RadioButtonCheckedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</RadioButton>
<RadioButton Content="Y" Margin="12">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding RadioButtonCheckedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</RadioButton>
<RadioButton Content="Z" Margin="12">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding RadioButtonCheckedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</RadioButton>
</StackPanel>
Previous answer is perfect. Just another solution if you don't want to use elemnt name "LayoutRoot".
<i:InvokeCommandAction Command="{Binding Path=DataContext.RadioButtonCheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"/>
inside ItemsControl, binding to RadioButtonCheckedCommand is trying to resolve it against an item - RadioButtonItem instance
but the command is defined in MainViewModel. it works in the StackPanel because there are no bindings to RadioButtonItems.
the solution is to bind to ItemsControl.DataContext (same as Grid.DataContext):
<i:InvokeCommandAction Command="{Binding Path=DataContext.RadioButtonCheckedCommand, ElementName=LayoutRoot}"/>

WPF Input KeyBinding for a ListBox Item

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>

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}"

DataGrid Context Menu Event Not Firing

This is what I have:
<window>
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:LogsViewModel}" >
<DataGrid Name="MainLogDataGrid" ItemsSource="{Binding MainLogDataGrid}">
<DataGrid.ContextMenu>
<ContextMenu Name="MainGridContextMenu" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp" >
<i:InvokeCommandAction Command="{Binding OnMainDataGridContextMenuChange}"
CommandParameter="{Binding ElementName=MainGridContextMenu, Path=PlacementTarget}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<MenuItem Header="Insert Row" Name="InsertRowMenuItem" TabIndex="0" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
<DataTemplate DataType="{x:Type viewModel:ReportsViewModel}">
<Label FontSize="50">THIS IS WHERE THE REPORTS GO</Label>
</DataTemplate>
</Window.Resources>
</Window>
<ContentControl Content="{Binding CurrentPageViewModel}" />
class LogsViewModel : ObservableObject, IViewModel
{
public RelayCommand<object> OnMainDataGridContextMenuChange { get; private set; }
public LogsViewModel()
{
OnMainDataGridContextMenuChange = new RelayCommand<object>(MainGridContextMenuItemChange);
}
private void MainGridContextMenuItemChange(object menuItem)
{
var item = menuItem as MenuItem;
}
}
The problem is that MainGridContextMenuItemChange method is never reached. Could this have something to do with the context menu not seeing LogsViewModel DataContext? How can I hook tihs up? Thanks.
Try this
<ContextMenu Name="MainGridContextMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Mode=Self}}">
ContextMenu is not a part of LogicalTree so it cannot inherit the DataContext from Parent .However ContextMenu do have Property PlacementTarget and we can use it to get the DataContext of DataGrid using binding as shown above.
Second option to set the DataContext of ContextMenu is
<ContextMenu Name="MainGridContextMenu" DataContext="{Binding DataContext, Source={x:Reference MainLogDataGrid}}">

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