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...
Related
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}" />
I'm using Prism RegionManager, to register different views with a TabControl region inside the MainView.
MainView.xaml
<TabControl regions:RegionManager.RegionName="MainViewTabRegion">
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel Width="Auto">
<Button Command="{Binding DataContext.DataContext.CloseTabCommand, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontWeight="Bold"
Margin="4,0,0,0"
FontSize="10"
VerticalContentAlignment="Center"
Width="15" Height="15" />
<ContentPresenter Content="{Binding DataContext.DataContext.HeaderText, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}" />
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
In MainViewViewModel I'm Adding different views with the same base class.
MainViewViewModel.cs:
private void AddProjectView() {
var view = _container.Resolve<ProjectSettingsView>();
var dataContext = _container.Resolve<ProjectSettingsViewModel>();
dataContext.HeaderText = "test header txt";
view.DataContext = dataContext;
_regionManager.RegisterViewWithRegion("MainViewTabRegion", () => view);
}
I can add new tab item with the view.
How can I close the tab item, the <TabControl.ItemTemplate> in the XAML code above, adds a close button with CloseCommand in the ProjectSettingsViewModel, with the TabItem bonded to it.
ProjectSettingsViewModel.cs
private void OnExecuteCloseCommand(object tabItem) {
//Close this TabItem
}
Bind the CommandParameter property of the Button to the DataContext of the TabItem:
<Button Command="{Binding DataContext.DataContext.CloseTabCommand, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}"
CommandParameter="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontWeight="Bold"
Margin="4,0,0,0"
FontSize="10"
VerticalContentAlignment="Center"
Width="15" Height="15" />
You could then remove the view like this in the view model:
public class ProjectSettingsViewModel
{
private readonly IRegionManager _regionManager;
public ProjectSettingsViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
CloseTabCommand = new DelegateCommand<object>(OnExecuteCloseCommand);
}
private void OnExecuteCloseCommand(object tabItem)
{
_regionManager.Regions["MainViewTabRegion"].Remove(tabItem);
}
public DelegateCommand<object> CloseTabCommand { get; }
}
You just need to get the reference to your IRegionManager. Then you get the Region that your view belongs to, and call Remove on the region and pass the tabItem reference to remove it.
Ex:
private void OnExecuteCloseCommand(object tabItem) {
regionManager.Regions["MainViewTabRegion"].Remove(tabItem);
}
You can actually just place this in your MainViewViewModel and bind to it in the DataTemplate, then you don't have to rewrite the close command for each tab item's view model.
I cover this in my Pluralsight course "Prism Problems & Solutions: Mastering the Tab Control". You can see the solution here: https://app.pluralsight.com/library/courses/prism-mastering-tabcontrol/table-of-contents
Essentially, you just need to create a TriggerAction that does all the work for you. Simple. Nothing is needed in the VM.
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}"
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>
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}}">