I need to bind Command and CommandParameter to two different classes from within a WPF ContextMenu MenuItem that is inside an ItemsControl.ItemTemplate. The problem is that ContextMenu has a separate visual tree than the rest of the window. I got it working in .NET 4.0+ but can't find a workaround that works in .NET 3.5.
Note: Even the working .NET 4.0+ workaround works at runtime but breaks both the Visual Studio 2013 Designer and the Blend Designer.
How can I bind the CommandParameter to a single CustomerViewModel and bind the Command to EditCustomersController in .NET 3.5?
View
<Window
x:Class="MvvmLight1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MVVM Light Application"
DataContext="{Binding EditCustomers, Source={StaticResource Locator}}">
<ItemsControl
x:Name="rootElement"
ItemsSource="{Binding Customers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu
PlacementTarget="{Binding ElementName=myTextBlock}">
<MenuItem
Header="Capitalize Using RelativeSource (Does not work)"
CommandParameter="{Binding}"
Command="{Binding
Path=DataContext.CapitalizeCommand,
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type Window}}}" />
<MenuItem
Header="Lowercase Using RelativeSource (Does not work)"
CommandParameter="{Binding}"
Command="{Binding
Path=DataContext.LowercaseCommand,
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type Window}}}" />
<MenuItem
Header="Capitalize Using Source (Only works in .NET 4.0+)"
CommandParameter="{Binding}"
Command="{Binding
Path=DataContext.CapitalizeCommand,
Source={x:Reference Name=rootElement}}" />
<MenuItem
Header="Lowercase Using Source (Only works in .NET 4.0+)"
CommandParameter="{Binding}"
Command="{Binding
Path=DataContext.LowercaseCommand,
Source={x:Reference Name=rootElement}}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Controller
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Collections.Generic;
using System.Windows.Input;
public class EditCustomersController : ViewModelBase
{
public EditCustomersController()
{
this.CapitalizeCommand = new RelayCommand<CustomerViewModel>(this.Capitalize);
this.LowercaseCommand = new RelayCommand<CustomerViewModel>(this.Lowercase);
var c = new List<CustomerViewModel>();
c.Add(new CustomerViewModel("Fred"));
c.Add(new CustomerViewModel("Bob"));
c.Add(new CustomerViewModel("Sue"));
c.Add(new CustomerViewModel("Sally"));
this.Customers = c;
}
public IEnumerable<CustomerViewModel> Customers { get; set; }
public ICommand CapitalizeCommand { get; private set; }
public ICommand LowercaseCommand { get; private set; }
private void Capitalize(CustomerViewModel customer)
{
customer.Name = customer.Name.ToUpper();
}
private void Lowercase(CustomerViewModel customer)
{
customer.Name = customer.Name.ToLower();
}
}
ViewModel
using GalaSoft.MvvmLight;
public class CustomerViewModel : ViewModelBase
{
private string name;
public CustomerViewModel(string name)
{
this.name = name;
}
public string Name
{
get
{
return this.name;
}
set
{
this.Set(() => this.Name, ref this.name, value);
}
}
}
Got it working in all versions of .NET by moving the ContextMenu into a resource and passing the top level DataContext through using the Tag property. Inside the ContextMenu MenuItem I can access both the current CustomerViewModel using {Binding} and the EditCustomersController using a binding with the Tag and RelativeSource.
View
<Window
x:Class="MvvmLight1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MVVM Light Application"
DataContext="{Binding EditCustomers, Source={StaticResource Locator}}">
<Window.Resources>
<ContextMenu
x:Key="ItemContextMenu"
Tag="{Binding PlacementTarget.Tag,
RelativeSource={RelativeSource Self}}">
<MenuItem
Header="Capitalize"
CommandParameter="{Binding}"
Command="{Binding Tag.CapitalizeCommand,
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type ContextMenu}}}" />
</ContextMenu>
</Window.Resources>
<ItemsControl
ItemsSource="{Binding Customers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Tag="{Binding
DataContext,
RelativeSource={RelativeSource
AncestorType={x:Type Window}}}"
Text="{Binding Name}"
ContextMenu="{StaticResource ItemContextMenu}">
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Related
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>
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}"
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?
<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
How can I get the original DataContext of the UserControl inside of a ContextMenu.
The code below, you can see that there is a Button in the DataTemplate, which binds correctly. However, when trying to bind the datasource of the contextmenu, I recieve the following error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.TreeView', AncestorLevel='1''. BindingExpression:Path=DataContext; DataItem=null; target element is 'ContextMenu' (Name=''); target property is 'DataContext' (type 'Object')
What do I need to do to allow the ContextMenu to bind to the ViewModel?
===============================================================================
The ViewModel is assigned to the datacontext of the view in the codebehind:
View:
<TreeView ItemsSource="{Binding Clients}"
cmd:TreeViewSelect.Command="{Binding SelectionChangedCommand}"
cmd:TreeViewSelect.CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=SelectedItem}">
<TreeView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu DataContext="{Binding DataContext,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeView}}}">
<MenuItem Header="{Binding TestString}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
<Button DataContext="{Binding DataContext,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeView}}}"
Content="{Binding TestString}" Command="{Binding EditSelectedClientCommand}" />
</StackPanel>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
ViewModel:
public class ClientListViewModel : ViewModelBase
{
public String TestString {
get {
return "TESTING";
}
}
private ClientList _clients = null;
private readonly IClientService _clientService = null;
private readonly IEventAggregator _eventAggregator = null;
private Client _selectedClient = null;
private ICommand _selectionChangedCommand = null;
private ICommand _editSelectedClientCommand = null;
....
}
ContextMenus do not appear in the visual tree which causes RelativeSource-bindings to fail, you can still get the DataContext one way or another though. You could try this for example:
<TextBlock Text="{Binding Name}"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TreeView}}">
<TextBlock.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding TestString}" />
<!-- ... --->
The PlacementTarget is the TextBlock, and the DataContext is tunneled through the Tag. Just one way to do this (at least i hope it works), i have also seen some libraries which bridge this gap differently but i do not recall their origin...