I am trying to dynamically bind context menus from my base view model. I've managed to functionally do what i'd like to do, but I'm stuck with how my dynamic context menu looks compared to my static one. There seems to be an extra level of menu items. My View Model looks like this.
public ObservableCollection<ContextMenuItem> ContextMenuItems { get; protected set; }
protected Constructor
{
ContextMenuItems = new ObservableCollection<ContextMenuItem>()
{
new ContextMenuItem() {Caption = "Add/Replace Supporting Data", Command = AddReplaceSupportingCommand},
new ContextMenuItem() {Caption = "Display SupportingData", Command = DisplaySupportingDataCommand}
};
}
the xaml for the 1st context menu us generated like this.
<StackPanel Orientation="Vertical" Margin="0,20">
<StackPanel.ContextMenu >
<ContextMenu ItemsSource="{Binding ContextMenuItems}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<MenuItem Command="{Binding Command}" Header="{Binding Caption}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</StackPanel.ContextMenu>
...
</StackPanel>
The xaml for the second context menu looks like this
<StackPanel >
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding AddReplaceSupportingCommand}" Header="Add or Replace Supporting Data"/>
<MenuItem Command="{Binding DisplaySupportingDataCommand}" Header="Display Supporting Data"/>
</ContextMenu>
</StackPanel.ContextMenu>
...
</StackPanel>
(for completeness, here's the ContextMenuItem class)
public class ContextMenuItem : ReactiveObject
{
private string _Caption;
public string Caption
{
get { return _Caption; }
set { this.RaiseAndSetIfChanged(ref _Caption, value); }
}
public ReactiveCommand Command { get; set; }
}
It looks different because ContextMenu, MenuBase to be specific, uses MenuItem as ItemContainerStyle so you're effectively wrapping MenuItem within MenuItem. Try this instead:
<StackPanel Orientation="Vertical" Margin="0,20">
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextMenuItems}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Caption}"/>
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</StackPanel.ContextMenu>
<!-- ... -->
</StackPanel>
you can use ItemTemplate if you want but use something like TextBox instead
<StackPanel Orientation="Vertical" Margin="0,20">
<StackPanel.ContextMenu >
<ContextMenu ItemsSource="{Binding ContextMenuItems}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</ContextMenu.ItemContainerStyle>
<ContextMenu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Caption}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</StackPanel.ContextMenu>
<!-- ... -->
</StackPanel>
Related
This question already has an answer here:
WPF ListView with group of RadioButtons and select default value
(1 answer)
Closed 4 months ago.
Have two properties in my viewmodel, INotifyPropertyChanged is implemented but not shown here. Both Properties are initiated in the constructor and values are set.
public ObservableCollection<Foo> Foos { get; init;}
public Foo SelectedFoo { get => _selectedFoo; set => _selectedFoo = value;}
the foo class:
public class Foo
{
public string NameOfFoo { get; set; }
public bool IsActive { get; set; }
}
in the corresponding View...
<ListView
ItemsSource="{Binding Foos}"
SelectedItem="{Binding SelectedFoo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Background="#FF063852"
BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding NameOfFoo}"
GroupName="GroupSelector"
BorderThickness="0"
Style="{StaticResource RadioButtonStyle}"
IsChecked="{Binding IsActive}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding Path=DataContext.SelectGroupCommand,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</RadioButton>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Margin="5" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
ItemsSource of ListView is working fine.
The radioButton is not checked as expected after creating new instance from viewmodel.
The command for the event "checked" is ok.
The SETTER from the SelectedFoo propery is never called. Why??
What have I to do, to have the propper radiobutton checked in my view after creation?
Found a solution here: https://stackoverflow.com/a/24179089/14800168
Multibinding for IsChecked was the trick!
Following on from a previous question here, I'm struggling to style a MenuItem's Icon with a control I have that inserts icon images based upon a string dependency property.
Initially I started with:
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
...
<Setter Property="Icon">
<local:StringToIcon IconName="{Binding IconName}" />
</Setter>
</Style>
</ContextMenu.Resources>
</ContextMenu>
This had the predictable effect of only displaying one of the icons in the menu, usually the last one, as the instance was shared around.
I then tried the non-shared resource approach:
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<Style TargetType="MenuItem">
...
<Setter Property="Icon" Value="{StaticResource MenuIcon} />
</Style>
</ContextMenu.Resources>
</ContextMenu>
This had no effect. It didn't offer me x:Shared in Intellisense, so I wonder if that's an invalid property here.
Out of desperation, I threw the thing into a template:
<Setter Property="Icon">
<Setter.Value>
<ContentControl>
<ContentControl.Template>
<ControlTemplate>
<local:StringToIcon IconName="{Binding IconName}" />
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
</Setter.Value>
</Setter>
Again, no effect. My StringToIcon looks like this at the moment, hard-coded with a single image to check the problem doesn't lie there. (Or does it?)
<UserControl x:Class="RAP.Admin3.Components.StringToIcon"
...
>
<Image DataContext="{Binding ElementName=StringIconControl}" Source="pack://application:,,,/Resources/Icons/lorry.png"/>
</UserControl>
How do I get this darn thing to template and allow multiple uses? It's probably something basic I'm overlooking.
I've looked at various similar questions, and most seem to have success with the non-shared resource method.
Edit: Let me add substantially more code as requested. I've come up with a minimal replication of the problem:
The context menu is part of a TreeView resource.
<UserControl x:Class="MyApp.ItemHierarchy"
...
Name="ItemHierarchyControl">
<Grid>
<TreeView ItemsSource="{Binding ElementName=ItemHierarchyControl, Path=Items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:HierarchyItem}" ItemsSource="{Binding Subitems}">
<StackPanel Orientation="Horizontal" Margin="0,1,4,1">
<TextBlock Text="My text" VerticalAlignment="Center" />
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}" />
</Style>
</ContextMenu.Resources>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</UserControl>
This is backed by a dependency property for the items.
public ObservableCollection<HierarchyItem> Items
{
get { return (ObservableCollection<HierarchyItem>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<HierarchyItem>), typeof(ItemHierarchy), new PropertyMetadata(new ObservableCollection<HierarchyItem>()));
StringToIcon is also backed by a string dependency property for the icon name, which is summarily ignored because of the hard-coded image at the moment.
HierarchyItems are simple for the example:
public ObservableCollection<HierarchyItem> Subitems { get; set; }
public ObservableCollection<BindableMenuItem> MenuItems { get; set; }
Just to get this proof working, I attached the ItemHierarchy to some properties of the main window:
public ObservableCollection<BindableMenuItem> MenuItems { get; set; }
public ObservableCollection<HierarchyItem> IHItems { get; set; }
public MainWindow()
{
MenuItems = new ObservableCollection<BindableMenuItem>();
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
MenuItems.Add(new BindableMenuItem("Item", null));
IHItems = new ObservableCollection<HierarchyItem>();
IHItems.Add(new HierarchyItem() { MenuItems = this.MenuItems });
InitializeComponent();
}
Edit 2: Here's BindableMenuItem also:
public class BindableMenuItem
{
public BindableMenuItem(string name, ICommand command)
{
this.Name = name;
this.Command = command;
}
public string Name { get; set; }
public ICommand Command { get; set; }
public string IconName { get; set; }
public ObservableCollection<BindableMenuItem> Children { get; set; }
}
Try to move the StringToIcon to <TreeView.Resources>:
<TreeView ItemsSource="{Binding ElementName=ItemHierarchyControl, Path=Items}">
<TreeView.Resources>
<local:StringToIcon x:Key="MenuIcon" x:Shared="False" IconName="{Binding IconName}" />
<HierarchicalDataTemplate DataType="{x:Type local:HierarchyItem}" ItemsSource="{Binding Subitems}">
<StackPanel Orientation="Horizontal" Margin="0,1,4,1">
<TextBlock Text="My text" VerticalAlignment="Center" />
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="Icon" Value="{StaticResource MenuIcon}" />
</Style>
</ContextMenu.Resources>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I use a DataGrid in my WPF application. It does feature RowDetails for selected Rows. Therefor I set the RowDetailsTemplate. Inside this DataTemplate i want to access my Window's DataContext. For Example I have a lable inside my RowDetailsTemplate and I want to bind it's content-property to a property of my viewModel which is in the DataContext of the window. How do I achieve this.
Thank you for your help!
Look at this usage of RelativeSource based binding, like {Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}. Here is example:
Xaml:
<DataGrid ItemsSource="{Binding Strings}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="String" Width="SizeToCells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type soDataGridHeplAttempt:ClicableItemsModel}">
<ListBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding ClickableItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Width="70" Content="{Binding }" Style="{StaticResource ButtonInCellStyle}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.Command}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Content}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
View model:
private ICommand _command;
public ObservableCollection<ClicableItemsModel> Strings { get; set; }
public ICommand Command
{
get { return _command ?? (_command = new RelayCommand<object>(MethodOnCommmand)); }
}
private void MethodOnCommmand(object obj)
{
}
Model pu this inside the the model class:
public ObservableCollection<String> ClickableItems { get; set; }
regards,
I have a datagrid, in one of the columns I want to display a string - in each cell in the column a different string(that I get from the user) that looks like this: "data1, data2, data3, ..." and make each datai(data1,data2,...) clickable.
how can I achieve that?
Here is edited version:
Xaml (put it inside the <DataGridTemplateColumn.CellTemplate):
<DataTemplate DataType="{x:Type soDataGridHeplAttempt:ClicableItemsModel}">
<ListBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding ClickableItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Width="70" Content="{Binding }" Style="{StaticResource ButtonInCellStyle}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.Command}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Content}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</DataTemplate>
Xaml resources use this if you want to edit the button content:
<Style x:Key="ButtonInCellStyle" TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Background="{x:Null}" MaxWidth="40" BorderBrush="{x:Null}" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type Button}}, Path=Content, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
HorizontalAlignment="Center" VerticalAlignment="Center" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}">
</TextBox>
</DataTemplate>
</Setter.Value>
</Setter>
Viewmodel:
private ICommand _command;
public ObservableCollection<ClicableItemsModel> Strings { get; set; }
public ICommand Command
{
get { return _command ?? (_command = new RelayCommand<object>(MethodOnCommmand)); }
}
private void MethodOnCommmand(object obj)
{
}
Clickable Items model code (create the ClicableItemsModel class an put it there):
public ObservableCollection<String> ClickableItems { get; set; }
when running:
regards,
for some reason unbeknownst to me I have been having some trouble getting this click event firing from the context menu of a datasourced treeviewitem.
The context menu appears as expected but is not handling its click events ( or at least not in the form I can see/retrieve ).
<UserControl x:Class="Pipeline_General.Custom_Controls.ProjectTree"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:pm="clr-namespace:Pipeline_General"
mc:Ignorable="d"
DataContext = "{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TreeView Name="StructureTree" Background="{x:Static pm:myBrushes.defaultBG}" ItemsSource="{Binding ProjectList}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Background="{x:Static pm:myBrushes.defaultBG}" Foreground="{x:Static pm:myBrushes.gray}">
<MenuItem Header="Add Episode.." Click="AddEp"/>
<MenuItem Header="Add Sequence.." Click="AddSeq"/>
<MenuItem Header="Add Scene.." Click="AddScene"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type pm:ProjectRoot}" ItemsSource="{Binding Episodes}">
<TextBlock Text="{Binding Path=Title}" Foreground="{x:Static pm:myBrushes.pink}"
FontFamily="Calibri" FontSize="18"/>
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Background="{x:Static pm:myBrushes.defaultBG}" Foreground="{x:Static pm:myBrushes.gray}">
<MenuItem Header="Add Sequence.." Click="AddSeq"/>
<MenuItem Header="Add Scene.." Click="AddScene"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type pm:Episode}" ItemsSource="{Binding Sequences}">
<TextBlock Text="{Binding Path=Key}" Foreground="{x:Static pm:myBrushes.orange}"
FontFamily="Calibri" FontSize="16"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type pm:Sequence}" ItemsSource="{Binding Scenes}">
<TextBlock Text="{Binding Path=Key}" Foreground="{x:Static pm:myBrushes.yellow}"
FontFamily="Calibri" FontSize="14"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type pm:Scene}">
<TextBlock Text="{Binding Path=Key}" Foreground="{x:Static pm:myBrushes.yellow}"
FontFamily="Calibri" FontSize="14"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
and in code behind...
public void AddSeq(object sender, RoutedEventArgs e)
{
var item = (TreeViewItem)StructureTree.SelectedItem;
Console.WriteLine(item.Header.ToString());
}
public void AddEp(object sender, RoutedEventArgs e)
{
Console.WriteLine(e.OriginalSource.ToString());
Console.WriteLine(e.Source.ToString());
Console.WriteLine("EP");
}
public void AddScene(object sender, RoutedEventArgs e)
{
Console.WriteLine(e.OriginalSource.ToString());
Console.WriteLine(e.Source.ToString());
Console.WriteLine("Scene");
}
From what I can tell, the problem is that you cannot attach a Click event like you did in a DataTemplate. You can refer to Event handler in DataTemplate to see how to do this, but what would be even better is to use a Command. This way your menu items will look like this:
<MenuItem Header="Add Episode.." Command="{x:Static throwAwayApp:MyCommands.AddEppCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}"/>
and you would add a command like this:
public static class MyCommands
{
static MyCommands()
{
AddEppCommand = new SimpleDelegateCommand(p =>
{
var menuItem = p as MenuItem;
//Console.WriteLine(e.OriginalSource.ToString());
//Console.WriteLine(e.Source.ToString());
Console.WriteLine("EP");
});
}
public static ICommand AddEppCommand { get; set; }
}
public class SimpleDelegateCommand : ICommand
{
public SimpleDelegateCommand(Action<object> executeAction)
{
_executeAction = executeAction;
}
private Action<object> _executeAction;
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_executeAction(parameter);
}
}