I'm trying to create a dynamic Context Menu in the WPF DataGrid. The following are the issues that I need help:
1) Root Menu Item Header are not bind with ViewModel while the submenu works fine.
2) The submenu always pop up on the left side instead of the right. How can I fix this with style?
<DataGrid.ContextMenu>
<ContextMenu ItemsSource="{Binding PackageCM.Members}" HasDropShadow="True" Placement="Right">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding CategoryName}" />
</Style>
</ContextMenu.ItemContainerStyle>
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<MenuItem Header="{Binding DisplayName}" Command="{Binding AllPackagesVM.OpenCOBAPackageCommand, Source={StaticResource Locator}}"></MenuItem>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
Root Menu Item Header are not being bind.
Basically, Context Menu is binding to the PackageCM.Members with has a list of Category object and I want to display the CategoryName on the Context Menu root. Following that, Each Category contains a list of Items which will be showed as submenu.
Thanks in advance for help.
First, your ContextMenu.ItemTemplate definition is incorrect, when you set an ItemSource for ContextMenu you do not define MenuItems yourself because actually ContextMenu will wrap this content inside another MenuItem. So you need to change your template to something like this:
<ContextMenu ItemsSource="{Binding PackageCM.Members}" ...>
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding DisplayName}"></TextBlock >
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
You need to put a TextBlock instead of MenuItem because you want to display a text in your ContextMenu menus and bind its Text property to a propety in your model. But so this to work, the model used for root menus and model used for sub-menus must have a property that is named equally, in your case it is DisplayName for sub-menus so in your root menus model must also have a property named DisplayName, This property is bound to Text property of the TextBlock.
You need to do some renaming in your models or introduce an new propety named DisplayName in Category model. So your models would have a common propety something like in this snippet:
// for root menu
public class Category
{
public string CategoryName { get; }
public string DisplayName => CategoryName;
...
}
// for submenus
public class Item
{
public string DisplayName { get; }
...
}
Hopefully this explanation helps you understand the problem for missing header values.
Related
I have a problem that is not addressed by the numerous articles on MVVM grouping that I have read.
I am writing a WPF application. Here are some excepts from classes that are relevant to my question - first the MainViewModel:
public class MainViewModel
{
public ObservableCollection<Recipe_OverViewModel> RecipeOverViews {get ; set;}
....[omitted extraneous lines]....
The class that is used as the observable collection of Recipes in the MainViewModel:
public class Recipe_OverViewModel
{
public Recipe TargetRecipe { get; set; }
public override string ToString()
{
return TargetRecipe.Parent_Name;
}
....[omitted extraneous lines]....
and The Class that is taken from the database, that is the actual Recipe
public partial class Recipe
{
[Required]
[StringLength(1000)]
public string Parent_Name { get; set; }
[Required]
[StringLength(60)]
public string Recipe_Name { get; set; }
public override string ToString()
{
return Parent_Name;
}
public int CompareTo(object obj)
{
return Parent_Name.CompareTo(((Recipe)obj).Parent_Name);
}
....[omitted extraneous lines]....
Each class has more properties and methods and so on, but these are enough to explain what I am asking.
The Recipe_OverViewModel is the view model for a control (Recipe_OverView) that displays the properties of the recipe. In the MainViewModel, I have the following xaml (extracted from the larger file):
<Window x:Class="RecipeApp.UI.MainWindow"
....[omitted extraneous lines]....
xmlns:vm="clr-namespace:RecipeApp.UI.ViewModel"
>
<Window.Resources>
<DataTemplate DataType="{x:Type vm:Recipe_OverViewModel}" x:Key="Recipe_DT" x:Name="Recipe_DT">
<control:Recipe_OverView Width="{Binding ActualWidth,ElementName=ListWidth}"/>
</DataTemplate>
<CollectionViewSource x:Key="SortedRecipeOverViews" Source="{Binding RecipeOverViews}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="TargetRecipe"/>
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="TargetRecipe"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
....[omitted extraneous lines]....
<ListView ItemsSource="{Binding Source={StaticResource SortedRecipeOverViews}}"
ItemTemplate="{StaticResource Recipe_DT}">
</ListView>
This list view correctly displays the list of recipes in the listview, with each row containing the Recipe_OverView control. However, I cannot get the grouping to work correctly. I would like to group the listview by the Parent_Name property of the Recipe associated with each Recipe_OverViewModel. My attempt looked like this, following the Microsoft HowTo:
<ListView Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Source={StaticResource SortedRecipeOverViews}}"
ItemTemplate="{StaticResource Recipe_DT}"
>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="true">
<Expander.Header>
<TextBlock Text="{Binding Path=ParentName}" />
</Expander.Header>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
However, all I get from that is as many empty (no ParentName populated) headers as there are recipes in the collection and a Binding Failure that tells me the "ParentName property not found on object of type CollectionViewGroupInternal." I get the expander buttons, but there is nothing within the expanded groups:
I thought I understand that that means that the binding is looking in the Recipe_OverViewModel for the ParentName property, but even when I added this as a property in the Recipe_OverViewModel and populated it, I still got this error, so now I am confused and have the following questions:
Where is the binding on the ListView actually looking?
How should I direct it to look at the Recipe_OverViewModel.TargetRecipe.ParentName (or is it impossible)?
I would really appreciate help on this matter, so many articles take so much simpler examples, and I cannot work out how to extend it to my case!
Where is the binding on the ListView actually looking?
It looks for a property of the CollectionViewGroupInternal class.
This class has a Name property that returns the value of the property that you group by, i.e. TargetRecipe, and an Items property that returns the collection of objects that belongs to the current group.
So, if I understand your setup correctly, you could try to bind to the Parent_Name property of the first item in the group:
<Expander.Header>
<TextBlock Text="{Binding Items[0].Parent_Name}" />
</Expander.Header>
The ItemsSource is bound to the SortedRecipeOverViews, which in turn is bound to the RecipeOverViews collection.
The item type of this collection is Recipe_OverViewModel.
And this type doesn't have a ParentName property.
There is a Parent_Name property, BUT not in the Recipe_OverViewModel type, but in the Recipe type.
And the Recipe_OverViewModel type has a property of this type.
In general, you have some kind of mess of types, their names and their properties, binding paths.
Perhaps you copied something wrong into the topic?
Based on my own guess, try applying a binding like this:
<Expander.Header>
<TextBlock Text="{Binding Path=TargetRecipe.Parent_Name}" />
</Expander.Header>
I'm currently working on a WPF .NET 4.7 application and I use Infragistics WPF controls version 18.
I need to create a custom XamComboEditor which has a XamDataTree inside. Thus a ComboBox with a Tree selection inside.
The Tree selection works fine without the XamComboEditor and looks like this:
<iWPF:XamDataTree ItemsSource="{Binding Locations}">
<iWPF:XamDataTree.GlobalNodeLayouts>
<iWPF:NodeLayout Key="Locations" TargetTypeName="LocationViewModel" DisplayMemberPath="Name"/>
<iWPF:NodeLayout Key="ChildLocations" TargetTypeName="string"/>
</iWPF:XamDataTree.GlobalNodeLayouts>
</iWPF:XamDataTree>
My XamDataTree is bound to an observable collection Locations:
public ObservableCollection<LocationViewModel> Locations { get; set; } = new ObservableCollection<LocationViewModel>();
public class LocationViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<LocationViewModel> ChildLocations { get; set; } = new List<LocationViewModel>();
}
I need to use the style setter on my XamComboEditor to put the XamDataTree inside the combobox.
My problem is now, I don't know how to achieve this, or how to pass the context from the XamComboEditor further to the XamDataTree.
I tried the following, in vain:
<iWPF:XamComboEditor ItemsSource="{Binding Locations}">
<iWPF:XamComboEditor.ComboBoxStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<iXaml:XamDataTree ItemsSource="{Binding .}">
<iXaml:XamDataTree.GlobalNodeLayouts>
<iXaml:NodeLayout Key="Locations" TargetTypeName="LocationViewModel" DisplayMemberPath="{Binding Name}"/>
<iXaml:NodeLayout Key="ChildLocations" TargetTypeName="string"/>
</iXaml:XamDataTree.GlobalNodeLayouts>
</iXaml:XamDataTree>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</iWPF:XamComboEditor.ComboBoxStyle>
</iWPF:XamComboEditor>
Do you know how to solve this issue? Do you know how to pass the data context from the parent control to, let's say, the child control? Or rather, how to put the XamDataTree inside the XamComboEditor?
If I understood this correctly, the DataContext of your XamlDataTree is no what you expect it to be (the Locations bound in you XamComboEditor).
One way to solve this problem is to specify the source of the path in your Binding markup extension.
You can use the {x:Reference ...} markup extension to reference a named controled in your control tree.
<iWPF:XamComboEditor x:Name="comboEditor" ItemsSource="{Binding Locations}">
<iWPF:XamComboEditor.ComboBoxStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<iXaml:XamDataTree ItemsSource="{Binding Source={x:Reference Name=comboEditor}, Path=DataContext.Locations}">
<iXaml:XamDataTree.GlobalNodeLayouts>
<iXaml:NodeLayout Key="Locations" TargetTypeName="LocationViewModel" DisplayMemberPath="{Binding Name}"/>
<iXaml:NodeLayout Key="ChildLocations" TargetTypeName="string"/>
</iXaml:XamDataTree.GlobalNodeLayouts>
</iXaml:XamDataTree>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</iWPF:XamComboEditor.ComboBoxStyle>
</iWPF:XamComboEditor>
You can also achieve this without naming your controls with the RelativeSource property in the Binding (something like RelativeSource={RelativeSource AncestorType=iWPF:XamComboEditor}).
I am building a dynamic context menu. To make that happen I have come up with a custom viewmodel that represents my contextmenu, defined like this:
public class ContextMenuVM {
public object ContextItem { get; set; }
public ObservableCollection<ICommand> Items { get; private set; }
}
The property Items holds the effective commands to show in the contextmenu and the property ContextItem holds the context-item that these commands are to be executed on.
Further, I have a singleton class which holds all my various commands and their implementation. So in the end there will be a method that will be called UpdateContextMenu on the viewmodel that owns the contextmenu that does something like this:
ContextMenu.Items.Clear();
ContextMenu.Items.Add(SingletonClass.Instance.CommandA);
if (condition)
ContextMenu.Items.Add(SingletonClass.Instance.CommandB);
The ContextMenu iteself in XAML is defined like this:
<ContextMenu ItemsSource="{Binding Path=ContextMenu.Items}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<ContentControl>
<MenuItem Command="{Binding}" CommandParameter="what-goes-here?" />
</ContentControl>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
What I fail to achieve is getting the CommandParameter binding to work. How am I supposed to bind to the ContextItem property of the ContextMenuVM instance?
Without a good Minimal, Complete, and Verifiable code example that clearly shows what you're doing, it's impossible to know for sure. But based on the information you've provided so far, it seems like you are looking for the RelativeSource binding source. E.g.:
<ContextMenu ItemsSource="{Binding Path=ContextMenu.Items}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<ContentControl>
<MenuItem Command="{Binding}"
CommandParameter="{Binding ContextMenu.ContextItem
RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContentControl>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
I had to make a guess as to the binding path, based on your ItemsSource binding, because your original code example isn't complete. The basic idea is to bind to a path that is relative to the specified source, so I've assumed that the ContextMenu refers to a property that returns the ContextMenuVM object you are using.
If this doesn't address your question, please improve it by providing a good MCVE.
I am building a Silverlight app which comprises a TreeView of menu options in a lefthand column and a ContentView in a righthand column. The idea is that the SelectedItemChanged event of the TreeView will change the view in the content area.
What is the 'purest MVVM' way of achieving this?
My idea is to have a TreeMenuView and TreeMenuViewModel for managing the menu events, but after that I'm a bit lost. I could use an EventAggregator to send a message from the TreeMenuViewModel to a `ContentViewModel' that would then set its current ContentView based on the message args- but surely that breaks MVVM, in the sense that a ViewModel shouldn't know about UI constructs like a View?
Am I missing something simple here?
How does a ViewModel layer drive the View selection?
I would create a ShellViewModel which had:
ObservableCollection<ViewModelBase> AvailablePages
int SelectedPageIndex
ViewModelBase CurrentPage, which returns AvailablePages[SelectedPageIndex]
Your ShellView can be anything you want. If you want to display your AvailablePages in a TreeView, then go ahead. Just remember to bind SelectedIndex to `SelectedPageIndex
In your case, I would create a DockPanel with a TreeView on the Left bound to AvailablePages, and a ContentControl on the right with ContentControl.Content bound to CurrentPage
Edit
Here's an example
<DockPanel>
<TreeView DockPanel.Dock="Right"
ItemsSource="{Binding AvailablePages}"
SelectedItem="{Binding SelectedPageIndex}">
...
</TreeView>
<ContentControl Content="{Binding CurrentPage}" />
</DockPanel>
Then use DataTemplates to define how the ContentControl containing CurrentPage will look
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomePageViewModel}" />
<local:HomePageView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:CustomerViewModel}" />
<local:CustomerView />
</DataTemplate>
</Window.Resources>
Ok I give it a shot
in TreeMenuViewModel:
public string PropSelectedItem
{
get;
set;
}
in TreeMenuView:
<TreeView Context="{Binding TreeMenuViewModel}" Content="{Binding PropSelectedItem, Mode=OneWayToSource}"/>
in ContentViewModel:
public ViewModelBase PropSelectedItem
{
get
{
switch(TreeMenuViewModelStatic.PropSelectedItem)
{
case "Booo": return typeof(View1);
case "Foo": return typeof(View2);
}
}
private set;
}
in ContentView:
<ContentControl Context="{Binding TreeMenuViewModel}" Content="{Binding PropSelectedItem, Mode=OneWay}"/>
and you need a value convertor here
I have a ListBox that its ItemsSource is given from a class based on the data binded items template. I want to find ListBox.SelectedItem position relative to the ListBox. Since I've used a class to feed ItemsSource, I'm not be able to cast ListBox.SelectedItem (which has a type of object) to the ListBoxItem. (Instead I should cast it to the source class type.)
What's the way? -Thanks
Details: (Arbitrary)
There is a ListBox which implements a Style like so:
<Style x:Key="MyListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border ...>
<StackPanel ...>
<Image Source="{Binding Path=ItemImageSource}" .../>
<TextBlock Text="{Binding Path=ItemTitle}" .../>
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The ListBox has been used as follows:
<ListBox x:Name="MyListBox"
ItemsSource="{Binding}"
Style="{StaticResource ResourceKey=MyListBoxStyle}"/>
Also there is a class that supports MyListBox data-binding info:
internal class MyListBoxItemBinding
{
public string ItemTitle { get; set; }
public ImageSource ItemImageSource { get; set; }
}
And to feed the MyListBox:
MyListBox.ItemsSource = new List<MyListBoxItemBinding> { /* some items */ };
Now, how can I find MyListBox.SelectedItem location relative to the MyListBox?
Use ItemsControl.ItemContainerGenerator to get a reference to the item container generator for your ListBox (this is the object that creates wrappers for all your databound objects).
Then, use the ItemContainerGenerator.ContainerFromItem method to get a reference to the UIElement that represents the selected ListBoxItem.
Finally, see the answer to this question to for a way of getting the coordinates of the selected item relative to the ListBox.