When setting contextmenu via style setter, PlacementTarget property is null - wpf

In my application, I have a view (ListView) and a view model. Inside view model, I have 2 properties: first is a list of items, and the second is a command. I want to display items (from first property) inside ListView. In addition, I want to have for each one a context menu, where clicking on it will activate a command (from second property).
Here is a code of my view model:
public class ViewModel
{
public IEnumerable Items
{
get
{
return ...; //returns a collection of items
}
}
public ICommand MyCommand //this is a command, I want to be able execute from context menu of each item
{
get
{
return new DelegateCommand(new Action<object>(delegate(object parameter)
{
//here code of the execution
}
), new Predicate<object>(delegate(object parameter)
{
//here code of "can execute"
}));
}
}
Now the XAML part:
<ListView ItemsSource="{Binding Items}">
<ListView.Resources>
<commanding:CommandReference x:Key="myCommand" Command="{Binding MyCommand}"/>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Name}"
/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem
Header="Remove from workspace"
Command="{StaticResource myCommand}"
CommandParameter="HERE I WANT TO PASS THE DATA CONTEXT OF THE ListViewItem"
/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
The problem: until I actually opening context menu, the PlacementTarget of the context menu is null. I need somehow to receive data context of the clicked ListViewItem into "CanExecute" of the command, BEFORE the command being called - and I truly wish to make everything in the XAML, without handling any callbacks in code behind.
Thank you in advance.

If you are looking for ListViewItem's DataContext you can do this:
CommandParameter="{Binding}"
Edit - Here is what I tried:
public partial class MainWindow : Window
{
private ObservableCollection<Person> list = new ObservableCollection<Person>();
public MainWindow()
{
InitializeComponent();
list.Add(new Person() { Name = "Test 1"});
list.Add(new Person() { Name = "Test 2"});
list.Add(new Person() { Name = "Test £"});
list.Add(new Person() { Name = "Test 4"});
this.DataContext = this;
}
public static ICommand MyCommand //this is a command, I want to be able execute from context menu of each item
{
get
{
return new DelegateCommand<Person>(
a => Console.WriteLine(a.Name),
a => true);
}
}
public ObservableCollection<Person> Items
{
get
{
return this.list;
}
}
}
public class Person
{
public string Name { get; set; }
}
And the xaml:
<Window x:Class="ListView1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ListView1="clr-namespace:ListView1" Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Remove from workspace" Command="{x:Static ListView1:MainWindow.MyCommand}" CommandParameter="{Binding}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
</Window>

Related

Binding a Command to HierarchicalDataTemplate MenuItem

I am trying to bind a VM method as a command in MenuItem. Though menu is displays correctly the function never get called.I expecting the MenuCommand Method to be get called from the command binding.
Xaml
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubMenu}">
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding MenuCommand}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
ViewModel
public class MenuViewModel : ViewModelBase
{
public ObservableCollection<Menu> Menu { get; set; }
public RelayCommand MenuCommand { get; set; }
public void Load()
{
Menu = new ObservableCollection<Menu> {
new Menu
{
Name = "File",
SubMenu = new List<Menu>
{
new Menu { Name = "New" },
new Menu { Name = "Open" },
new Menu { Name = "Save" }
}
}};
MenuCommand = new RelayCommand(MenuExecution);
}
public void MenuExecution(object item)
{
MessageBox.Show("Hello");
}
}
Thanks #GK & #Mark I am able to bind the command successfully by below Xaml
<Menu ItemsSource="{Binding Menu}" >
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" >
<Setter Property="Command" Value="{Binding ElementName=level1Lister, Path=DataContext.MenuCommand}" />
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=SubMenu}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>

BarButtonItems and BarSubItems on Bound RibbonControl

I am developing a WPF application using DevExpress controls, such as the Ribbon control. I want to be able to place buttons on the ribbon dynamically. I would like to be able to support both regular buttons and drop-down buttons.
I was thinking something similar to below.
WPF View:
<UserControl.Resources>
<DataTemplate x:Key="RibbonCommandTemplate">
<ContentControl>
<dxb:BarButtonItem RibbonStyle="All" Content="{Binding Caption}"
Command="{Binding (dxr:RibbonControl.Ribbon).DataContext.MenuExecuteCommand,
RelativeSource={RelativeSource Self}}"
CommandParameter="{Binding}" />
</ContentControl>
</DataTemplate>
</UserControl.Resources>
<Grid>
<DockPanel>
<dxr:RibbonControl DockPanel.Dock="Top" RibbonStyle="Office2010">
<dxr:RibbonDefaultPageCategory>
<dxr:RibbonPage Caption="Home">
<dxr:RibbonPageGroup Caption="Dynamic Commands"
ItemLinksSource="{Binding DynamicCommands}"
ItemTemplate="{StaticResource RibbonCommandTemplate}" />
</dxr:RibbonPage>
</dxr:RibbonDefaultPageCategory>
</dxr:RibbonControl>
<Grid/>
</DockPanel>
</Grid>
View Model:
public class RibbonCommand
{
public string Caption { get; set; }
public int CommandCode { get; set; }
public ObservableCollection<RibbonCommand> SubItems { get; set; }
public bool HasSubItems
{
get
{
if (SubItems != null)
return (SubItems.Count > 0);
else
return false;
}
}
}
[POCOViewModel]
public class MainViewModel
{
public ObservableCollection<RibbonCommand> DynamicCommands { get; set; }
public MainViewModel()
{
DynamicCommands = new ObservableCollection<RibbonCommand>();
// Regular buttons.
DynamicCommands.Add(new RibbonCommand() { Caption = "Button 1", CommandCode = 1 });
DynamicCommands.Add(new RibbonCommand() { Caption = "Button 2", CommandCode = 2 });
// Drop-down button.
RibbonCommand dropDownCommand = new RibbonCommand() { Caption = "Drop-Down", CommandCode = 3 };
dropDownCommand.SubItems = new ObservableCollection<RibbonCommand>();
dropDownCommand.SubItems.Add(new RibbonCommand() { Caption = "Sub-Item 1", CommandCode = 31 });
dropDownCommand.SubItems.Add(new RibbonCommand() { Caption = "Sub-Item 2", CommandCode = 32 });
dropDownCommand.SubItems.Add(new RibbonCommand() { Caption = "Sub-Item 3", CommandCode = 33 });
DynamicCommands.Add(dropDownCommand);
}
public void MenuExecute(RibbonCommand command)
{
MessageBox.Show(string.Format("You clicked command with ID: {0} (\"{1}\").",
command.CommandCode, command.Caption), "Bound Ribbon Control");
}
}
This code does successfully populate the ribbon with items I added in my DynamicCommands collection, but I would like to support drop-down buttons for items with anything in the SubItems collection (the third button on my example above).
Is there a way to conditionally change the type of control displayed in a DataTemplate. If the object's HasSubItems is true, I would like a BarSubItem placed on the ribbon. If it is false, I will keep the BarButtonItem.
If this is regular WPF rather than UWP, and if the DataContexts of your subitems are of different types, you can define multiple DataTemplates with DataType attributes in the RibbonPageGroup's resources (where they won't be in scope for anything that doesn't need them), and get rid of that ItemTemplate attribute:
<dxr:RibbonPageGroup
Caption="Dynamic Commands"
ItemLinksSource="{Binding DynamicCommands}">
<dxr:RibbonPageGroup.Resources>
<DataTemplate DataType="{x:Type local:RibbonCommand}">
<!-- XAML stuff -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:SpecialRibbonCommand}">
<!-- Totally different XAML stuff -->
</DataTemplate>
</dxr:RibbonPageGroup.Resources>
<!-- etc -->
For another option, you should be able to write a DataTemplateSelector and give it to the RibbonControl's ToolbarItemTemplateSelector property or the RibbonPageGroup's ItemTemplateSelector property.
Lastly, write one complicated DataTemplate with multiple child controls superimposed in a Grid, and a series of triggers that show only the appropriate one based on properties of the DataContext. If you've only got two different options to handle, this may be the quickest and easiest route.
<DataTemplate x:Key="RibbonCommandTemplate">
<Grid>
<Label x:Name="OneThing" />
<Label x:Name="AnotherThing" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding HasSubItems}" Value="True">
<Setter TargetName="OneThing" Property="Visibility" Value="Collapsed" />
<Setter TargetName="AnotherThing" Property="Visibility" Value="Visible" />
</DataTrigger>
<!-- Other triggers for HasSubItems == False, whatever -->
</DataTemplate.Triggers>
</DataTemplate>
This seems pretty crude, but I've done it so much in WPF that I'm getting desensitized to it.
I figured out a way to do this using a DataTemplateSelector class:
using System.Windows;
using System.Windows.Controls;
using RibbonDynamicButtons.ViewModels;
namespace RibbonDynamicButtons.Selectors
{
public class RibbonCommandSelector : DataTemplateSelector
{
public DataTemplate CommandTemplate { get; set; }
public DataTemplate SubCommandTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if(item is RibbonCommand)
{
RibbonCommand command = (RibbonCommand)item;
if (command.HasSubItems)
return SubCommandTemplate;
else
return CommandTemplate;
}
return base.SelectTemplate(item, container);
}
}
}
I added my selector to the xaml as follows:
<UserControl
...
xmlns:Selectors="clr-namespace:RibbonDynamicButtons.Selectors">
<UserControlResources>
<DataTemplate x:Key="RibbonSubItemTemplate">
<ContentControl>
<dxb:BarButtonItem RibbonStyle="SmallWithText" Content="{Binding Caption}"
Command="{Binding (dxr:RibbonControl.Ribbon).DataContext.MenuExecuteCommand,
RelativeSource={RelativeSource Self}}" CommandParameter="{Binding}" />
</ContentControl>
</DataTemplate>
<Selectors:RibbonCommandSelector x:Key="RibbonCommandSelector">
<Selectors:RibbonCommandSelector.CommandTemplate>
<DataTemplate>
<ContentControl>
<dxb:BarButtonItem RibbonStyle="All"
Content="{Binding Caption}"
Command="{Binding (dxr:RibbonControl.Ribbon).DataContext.MenuExecuteCommand,
RelativeSource={RelativeSource Self}}"
CommandParameter="{Binding}" />
</ContentControl>
</DataTemplate>
</Selectors:RibbonCommandSelector.CommandTemplate>
<Selectors:RibbonCommandSelector.SubCommandTemplate>
<DataTemplate>
<ContentControl>
<dxb:BarSubItem RibbonStyle="All" Content="{Binding Caption}"
ItemLinksSource="{Binding SubItems}"
ItemTemplate="{StaticResource RibbonSubItemTemplate}" />
</ContentControl>
</DataTemplate>
</Selectors:RibbonCommandSelector.SubCommandTemplate>
</Selectors:RibbonCommandSelector>
</UserControlResources>
I bind the ItemTemplateSelector to my selector on the RibbonPageGroup:
<dxr:RibbonPageGroup Caption="Dynamic Commands" ItemLinksSource="{Binding DynamicCommands}"
ItemTemplateSelector="{StaticResource RibbonCommandSelector}" />
I did not need to make any changes to the View Model I included on my original question.

ContextMenu on TreeView-Element in MVVM-style

I have the "simple" task to have a ContextMenu on a TreeView(Element) that is done in MVVM-way.
When searching the web I found some solutions that I could bring to work with buttons etc. but not with the TreeView. I think the problem is with setting the ItemsSource-Property of TreeView that gives every single item an own DataContext.
Here's my little Test-App where you can see the principle working for button but not for the TreeView-Elements:
MainWindow.xaml:
<Window x:Class="ContextMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid >
<StackPanel>
<TextBlock Text="{Binding MyText}" />
<Button Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=Self}}" Content="Click me">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding PlacementTarget.Tag.MyText,
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
<TreeView ItemsSource="{Binding MyList}" Tag="{Binding DataContext, RelativeSource={RelativeSource Mode=Self}}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag.MyText,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Grid>
</Window>
Codebehind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowVM();
}
}
MainWindowVM.cs:
public class MainWindowVM
{
public string MyText { get; set; }
public ObservableCollection<TreeElement> MyList { get; set; }
public MainWindowVM()
{
MyText = "This is my Text!";
MyList = new ObservableCollection<TreeElement>();
MyList.Add(new TreeElement("String 1"));
MyList.Add(new TreeElement("String 2"));
}
}
public class TreeElement
{
public string Name { get; set; }
public TreeElement(string Name)
{
this.Name = Name;
}
}
Thanks for your help!!
Joerg
You are close.
What you are doing:
you set the Tag of TreeView to its own DataContext. This is
unnecessary.
you try to get Tag.MyText from your ContextMenu.PlacementTarget - which is TextBlock. The TextBlock has no Tag set.
What you should do:
set the Tag of the TextBlock to DataContext of the Window
(Window is TextBlock ancestor and you should look it up via
RelativeSource Mode=FindAncestor).
the second part is OK - you have TextBlock.Tag set in the first step.

WPF: Preventing the ContextMenu from appearing on child TreeViewItems

I am trying to make design a WPF TreeView where the ContextMenu activates on particular nodes.
In my example, despite my best efforts, I cannot keep the ContextMenu of a BarNode from appearing when it's children 'FooNode's are clicked.
C#:
public abstract class NodeBase
{
public NodeBase[] ChildNodes { get; set; }
}
public class FooNode : NodeBase
{
}
public class BarNode : NodeBase
{
}
public class ExampleModel : BaseModel
{
private NodeBase[] _nodes;
public NodeBase[] Nodes
{
get
{
_nodes = new NodeBase[]
{
new FooNode(),
new BarNode()
{
ChildNodes = new NodeBase[]
{
new FooNode(),
new FooNode()
}
}
};
return _nodes;
}
}
public ExampleModel()
{
}
}
public class TreeViewStyleSelector : StyleSelector
{
public Style FooNodeStyle { get; set; }
public Style BarNodeStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
var fooNode = item as FooNode;
if (fooNode != null)
{
return FooNodeStyle;
}
var barNode = item as BarNode;
if (barNode != null)
{
return BarNodeStyle;
}
return base.SelectStyle(item, container);
}
}
XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Nodes="clr-namespace:UnderstandingWPFTreeView.Nodes"
xmlns:Models="clr-namespace:UnderstandingWPFTreeView.Models"
xmlns:Common="clr-namespace:UnderstandingWPFTreeView.Common"
x:Class="UnderstandingWPFTreeView.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<Models:ExampleModel/>
</Window.DataContext>
<Window.Resources>
<ContextMenu x:Key="testContextMenu">
<MenuItem Header="Test Context Item"></MenuItem>
<MenuItem Header="Test Context Item"></MenuItem>
</ContextMenu>
<Style TargetType="{x:Type TreeViewItem}" x:Key="FooNodeStyle">
</Style>
<Style TargetType="{x:Type TreeViewItem}" x:Key="BarNodeStyle">
<Setter Property="ContextMenu" Value="{StaticResource testContextMenu}" />
</Style>
<Common:TreeViewStyleSelector
x:Key="treeViewStyleSelector"
FooNodeStyle="{StaticResource ResourceKey=FooNodeStyle}"
BarNodeStyle="{StaticResource ResourceKey=BarNodeStyle}" />
</Window.Resources>
<StackPanel HorizontalAlignment="Left" Height="320" VerticalAlignment="Top" Width="517">
<TreeView Height="100">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type Nodes:BarNode}" ItemsSource="{Binding Path=ChildNodes}">
<TextBlock Text="Bar" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type Nodes:FooNode}" ItemsSource="{Binding Path=ChildNodes}">
<TextBlock Text="Foo" />
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeViewItem Header="Testing" ItemsSource="{Binding Nodes}" ItemContainerStyleSelector="{StaticResource ResourceKey=treeViewStyleSelector}"/>
</TreeView>
</StackPanel>
</Window>
I asked the same question of the MSDN Forums and got an answer that I can confirm as working.
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/7dd183bc-d616-4ec4-8b2a-0b438c9a115c
Placing the ContextMenu objects on the TextBlock gives the same visual appearance, minus the effect of passing the ContextMenu down the chain of TreeNodes.
<HierarchicalDataTemplate DataType="{x:Type local:BarNode}" ItemsSource="{Binding Path=ChildNodes}">
<TextBlock Text="Bar" ContextMenu="{StaticResource testContextMenu}" />
</HierarchicalDataTemplate>

multiple userControl instances in tabControl

I have a tabControl that is bound to an observable collection.
In the headerTemplate, I would like to bind to a string property, and in the contentTemplate I have placed a user-control.
Here's the code for the MainWindow.xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="contentTemplate">
<local:UserControl1 />
</DataTemplate>
<DataTemplate x:Key="itemTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</Grid.Resources>
<TabControl IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Pages}"
ItemTemplate="{StaticResource itemTemplate}"
ContentTemplate="{StaticResource contentTemplate}"/>
</Grid>
And its code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
public class MainWindowViewModel
{
public ObservableCollection<PageViewModel> Pages { get; set; }
public MainWindowViewModel()
{
this.Pages = new ObservableCollection<PageViewModel>();
this.Pages.Add(new PageViewModel("first"));
this.Pages.Add(new PageViewModel("second"));
}
}
public class PageViewModel
{
public string Name { get; set; }
public PageViewModel(string name)
{
this.Name = name;
}
}
So the problem in this scenario (having specified an itemTemplate as well as a controlTemplate) is that I only get one instance for the user-control, where I want to have an instance for each item that is bound to.
Try this:
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Pages}">
<TabControl.Resources>
<DataTemplate x:Key="contentTemplate" x:Shared="False">
<local:UserControl1/>
</DataTemplate>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="ContentTemplate" Value="{StaticResource contentTemplate}"/>
</Style>
</TabControl.Resources>
</TabControl>
Try setting
x:Shared="False"
When set to false, modifies Windows Presentation Foundation (WPF) resource retrieval behavior such that requests for a resource will create a new instance for each request, rather than sharing the same instance for all requests.
You need to override the Equals() Method of your PageViewModel class.
public override bool Equals(object obj)
{
if (!(obj is PageViewModel)) return false;
return (obj as PageViewModel).Name == this.Name;
}
Something like this should work.
Now it is looking for the same property of the value Name. Otherwise you could also add a ID Property which is unique.

Resources