Create Hierarchical ContextMenu dynamically MVVM - c#

I would like to create dynamically Hierarchical ContextMenu from data in ViewModel.
In ViewMode, I defined ContextMenuAction:
public class ContextMenuAction : ViewModelBase
{
public string Header { get; set; }
public ICommand Action { get; set; }
public Brush Icon { get; set; }
public ObservableCollection<ContextMenuAction> SubActions { get; set; } = new ObservableCollection<ContextMenuAction>();
}
In View:
<ContextMenu ItemsSource="{Binding Path=PlacementTarget.Tag.Actions, RelativeSource={RelativeSource Self}}">
<ContextMenu.ItemTemplate >
<DataTemplate DataType="MenuItem">
<MenuItem/>
</DataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="ItemsSource" Value="{Binding SubActions}"/>
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Command" Value="{Binding Action}"/>
</Style>
</ContextMenu.ItemContainerStyle>
This is result, there no text in context menu.
I already check output window to check binding, all bindings work, there is no exception.
Please help me to find out the reason, thank in advance!

You should define a HierarchicalDataTemplate:
<ContextMenu ItemsSource="{Binding Path=PlacementTarget.Tag.Actions, RelativeSource={RelativeSource Self}}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ContextMenuAction}" ItemsSource="{Binding SubActions}">
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Action}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>

Related

XAML: Show button on ComboBox item highlight

I have a ComboBox with an ItemTemplateSelector, using 2 different DataTemplates, one for when its drop down is visible and another when it is not. For the drop down template, each ComboBox item is represented by a TextBlock and a Button that should only be visible whenever that item is focused/highlighted/mouse over. This is what I've tried:
<ComboBox x:Name="Windows" ItemsSource="{Binding Windows}" SelectedItem="{Binding Window}" Focusable="False" MaxDropDownHeight="238">
<ComboBox.ItemTemplateSelector>
<s:ComboBoxItemTemplateSelector>
<s:ComboBoxItemTemplateSelector.SelectedTemplate>
<DataTemplate>
<TextBlock Text="{Binding TitleShort}" ToolTip="{Binding Title}" />
</DataTemplate>
</s:ComboBoxItemTemplateSelector.SelectedTemplate>
<s:ComboBoxItemTemplateSelector.DropDownTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding TitleShort}" />
<Button Content="X">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsFocused, ElementName=Windows}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</DataTemplate>
</s:ComboBoxItemTemplateSelector.DropDownTemplate>
</s:ComboBoxItemTemplateSelector>
</ComboBox.ItemTemplateSelector>
<ComboBox.ItemContainerStyle>
<Style BasedOn="{StaticResource MaterialDesignComboBoxItemStyle}" TargetType="ComboBoxItem">
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock Text="{Binding Title}" />
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
public DataTemplate SelectedTemplate { get; set; }
public DataTemplate DropDownTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ComboBoxItem comboBoxItem = GetVisualParent<ComboBoxItem>(container);
if (comboBoxItem == null)
{
return SelectedTemplate;
}
return DropDownTemplate;
}
private static T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
}
ComboBox generates ComboBoxItem as a container for every item in its itemssource. You can bind to its properties with RelativeSource binding.
This should get you the expected behavior:
<Button Content="X">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

How to access the right datacontext

I'm building a treeview with HierarchicalDataTemplates and would like to have some nodes expaned. The Nodes have a property "IsNodeExpanded" that I'd like to bind the property IsExpanded to.
Where I run into troubles is binding to this property. E.g. this
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding DataContext.IsNodeExpanded, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
</Setter>
</Style>
will bind to a property IsNodeExpanded defined on my MainViewModel while this
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsNodeExpanded}">
</Setter>
</Style>
will have no effect at all because, I guess, the binding has a datacontext problem.
How can I refer to the right datacontext?
For completeness, here is my TreeView
<TreeView ItemsSource="{Binding _questions}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Question}"
ItemsSource="{Binding Converter={StaticResource QuestionConverter}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MainOption}"
ItemsSource="{Binding MainOptions}">
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Path=Name}"
IsChecked="{Binding Path=IsSelected}"
Command="{Binding DataContext.ToggleSelectedMetaItem, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding Path=MetaItemId}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding DataContext.IsNodeExpanded, RelativeSource={RelativeSource FindAncestor, AncestorType=TreeView}}">
</Setter>
</Style>
</TreeView.ItemContainerStyle>
EDIT:
public class MainOption
{
public string Name { get; set; }
public int MetaItemId { get; set; }
public bool IsSelected { get; set; }
public MainOption()
{
this.IsSelected = true;
}
}
public class Question
{
public string Name { get; set; }
public List<MainOption> MainOptions { get; set; }
public Question()
{
MainOptions = new List<MainOption>();
}
}
In ItemContainerStyle you should not define relative source when binding to IsNodeExpanded property. if itemssource _questions is list of Question, then {Binding IsNodeExpanded} binds to IsNodeExpanded property of Question class. I quess you are missing the property.
here is xaml:
<TreeView ItemsSource="{Binding Questions}">
<TreeView.Resources>
<DataTemplate x:Key="OptionTemplate" DataType="l:MainOption">
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}" />
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="l:Question" ItemsSource="{Binding MainOptions}"
ItemTemplate="{StaticResource OptionTemplate}" ItemContainerStyle="{x:Null}">
<!-- DataContext of type Question -->
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
and C# code:
public class MainWindowViewModel : BindableBase
{
public MainWindowViewModel()
{
Questions = Enumerable.Range(1, 10)
.Select(i => new Question
{
Name = "Question " + i,
MainOptions = Enumerable.Range(1, 5)
.Select(j => new MainOption {Name = "Option " + i})
.ToList()
})
.ToList();
}
public List<Question> Questions { get; private set; }
}
public class Question
{
public string Name { get; set; }
public List<MainOption> MainOptions { get; set; }
public bool IsNodeExpanded { get; set; }
public Question()
{
IsNodeExpanded = true;
MainOptions = new List<MainOption>();
}
}
the point is, that you set Style and ItemTemplate of root items in threeview. In HierarchicalItemTemplate ItemSource is used to generate subitems, ItemContaierStyle affects style of subitems and ItemTemplate affects datatempalte of subitems

Change ContentPresenter's DataTemplate

I have a popup, where I want to display different things depending on various buttons which are clicked. To do this I've added a ContentPresenter nad in this ContentPresenter I've got an TemplateSelector. My problem is that as far as I can see it only checks which template to use the first time my popUp is run and uses this template from then on. Is there a way to get the code to change the template to use?
The code I've got so far is (xaml):
<Popup IsOpen="{Binding IsOpen}" Height="{Binding Height}" Width="{Binding Width}">
<Grid>
<ContentPresenter x:Name="CP" Loaded="CP_Loaded">
<ViewModel:PopUpTemplateSelector x:Name="PUT" Content="{Binding}">
<ViewModel:PopUpTemplateSelector.View1>
<DataTemplate>
<View:View1/>
</DataTemplate>
</ViewModel:PopUpTemplateSelector.View1>
<ViewModel:PopUpTemplateSelector.View2>
<DataTemplate>
<View:View2/>
</DataTemplate>
</ViewModel:PopUpTemplateSelector.View2>
<ViewModel:PopUpTemplateSelector.View3>
<DataTemplate>
<View:View3/>
</DataTemplate>
</ViewModel:PopUpTemplateSelector.View3>
<ViewModel:PopUpTemplateSelector.View4>
<DataTemplate>
<View:View4/>
</DataTemplate>
</ViewModel:PopUpTemplateSelector.View4>
<ViewModel:PopUpTemplateSelector.View5>
<DataTemplate>
<Design:View5/>
</DataTemplate>
</ViewModel:PopUpTemplateSelector.View5>
</ViewModel:PopUpTemplateSelector>
</ContentPresenter>
</Grid>
</Popup>
and my popUpTemplateSelector(C#) is
public class PopUpTemplateSelector : DataTemplateSelector
{
public DataTemplate View1{ get; set; }
public DataTemplate View2 { get; set; }
public DataTemplate View3 { get; set; }
public DataTemplate View4 { get; set; }
public DataTemplate View5 { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
PopUpViewModel Pop = item as PopUpViewModel;
if(Pop.TemplateToUse == "View1")
{
return View1;
}
else if(Pop.TemplateToUse == "View2")
{
return View2;
}
else if(Pop.TemplateToUse.Equals("View3"))
{
return View3;
}
else if (Pop.TemplateToUse.Equals("View4"))
{
return View4;
}
else if(Pop.TemplateToUse.Equals("View5"))
{
return View5;
}
return null;
}
}
I would suggest you use DataTriggers bound to TemplateToUse property on the ViewModel to update ContentTemplate. And also use ContentControl instead of ContentPresenter
<Popup IsOpen="{Binding IsOpen}" Height="{Binding Height}" Width="{Binding Width}">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="View1Template">
<View:View1/>
</DataTemplate>
<DataTemplate x:Key="View2Template">
<View:View2/>
</DataTemplate>
<DataTemplate x:Key="View3Template">
<View:View3/>
</DataTemplate>
<DataTemplate x:Key="View4Template">
<View:View4/>
</DataTemplate>
<DataTemplate x:Key="View5Template">
<Design:View5/>
</DataTemplate>
<Style x:Key="ContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TemplateToUse}" Value="View1">
<Setter Property="ContentTemplate" Value="{StaticResource View1Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=TemplateToUse}" Value="View2">
<Setter Property="ContentTemplate" Value="{StaticResource View2Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=TemplateToUse}" Value="View3">
<Setter Property="ContentTemplate" Value="{StaticResource View3Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=TemplateToUse}" Value="View4">
<Setter Property="ContentTemplate" Value="{StaticResource View4Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=TemplateToUse}" Value="View5">
<Setter Property="ContentTemplate" Value="{StaticResource View5Template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ContentControl x:Name="CP" Loaded="CP_Loaded" Style="{StaticResource ContentStyle}" Content="{Binding}" />
</Grid>
</Popup>

Binding on background color with a custom style on a button and checkbox

I am currently working on a WPF application and have redefined the buttons and checkbox to use a custom style in App.xaml.
App.xaml
<Style x:Key="{x:Type CheckBox}" TargetType="CheckBox">
<Setter Property="Margin" Value="0,0,10,5" />
<Setter Property="Width" Value="85" />
<Setter Property="Height" Value="50" />
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="FocusVisualStyle"
Value="{DynamicResource CheckBoxFocusVisual}" />
<Setter Property="Template" />
<Setter.Value />
<!-- ... etc. -->
</Style>
The code is from here with some modifications.
Now I want to change the background color through coding. For example, if the title of the checkbox is "a" then the background color would be red and so on. I succeeded in binding the titles but not the background color. My checkboxes are used inside a control item.
MainWindow.xaml
<Grid>
<ItemsControl Name="ControllerDisplay">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding controltitle}"
Style="{StaticResource MainControltitle}"/>
<CheckBox>
// want to change checkbox background
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}"
BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="Background" Value="Aqua" />
</Style>
</CheckBox.Style>
<StackPanel >
<TextBlock Text="{Binding controlno}" HorizontalAlignment="Center"
FontSize="16" />
<TextBlock Text="{Binding status}" HorizontalAlignment="Center"
FontSize="10"/>
</StackPanel>
</CheckBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
on the coding side:
public void printcheckbox()
{
List<Controller> listItem = new List<Controller>();
listItem.Add(new Controller() { controltitle = "title1", controlno = "12345",
status = "On" , controlbg = Colors.Red});
ControllerDisplay.ItemsSource = listItem;
}
public class Controller
{
public string controltitle { get; set; }
public string controlno { get; set; }
public string status { get; set; }
public Color controlbg { get; set; }
}

CommandBinding in ContextMenu

I have a TreeView and have created a basic TreeItem type. Each TreeItem has a header, a TreeItem Collection for children and a collection for a possible context menu. The TreeItem class has those objects:
public delegate void dExecute(TreeItem item);
public dExecute ExecuteTarget { get; set; }
public object Tag { get; set; }
public string Header { get; set; }
public List<TreeItem> Children { get; set; }
public List<TreeItem> ContextMenu { get; set; }
The context menu uses again a HierarchicalDataTemplate to display TreeItem objects (I use the TreeItem class for the items in the treeview AND in the context menu). The context menu looks like this:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" Visibility="{Binding ShowContextMenu}" ItemsSource="{Binding ContextMenu}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Execute}"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
The context menu is rendered as I want it to be. I have created a context menu that I just attach to some of my items in the tree view. This is its content.
public List<TreeItem> ContextMenu
{
get
{
List<TreeItem> list = new List<TreeItem>();
TreeItem ti = new TreeItem("Some Action") { ExecuteTarget = targetMethod};
list.Add(ti);
ti = new TreeItem("test");
ti.Children.Add(new TreeItem("foo") { ExecuteTarget = targetMethod});
ti.Children.Add(new TreeItem("bar") { ExecuteTarget = targetMethod});
ti.Children.Add(new TreeItem("foo") { ExecuteTarget = targetMethod});
TreeItem ti2 = new TreeItem("inner"){ ExecuteTarget = targetMethod};
ti.Children.Add(ti2);
ti2.Children.Add(new TreeItem("foo") { ExecuteTarget = targetMethod});
list.Add(ti);
return list;
}
}
The context menu looks like this.
It looks as it should be. The commands work as they should. EXCEPT for the command on the highest level of the context menu. When I click on "Some Action" nothing happens. I assume that I have to add something to XAML, but I have no idea where.
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" Visibility="{Binding ShowContextMenu}" ItemsSource="{Binding ContextMenu}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Execute}"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
<!-- this is what you're missing -->
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Execute}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>

Categories

Resources