Dynamically binding and statically adding MenuItems - using view Models/MVVM - c#

I'm trying to have a dynamic menu item using MVVM from an observable collection. Everything worked, but then I needed to add a "add new" button to the end. I found a solution using a CompositeCollection, like here:
How do I dynamically bind and statically add MenuItems?
So have the following code, where TimeSpans is a collection of ViewModels:
<MenuItem Header="Time Ranges">
<MenuItem.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding TimeSpans}" />
<Separator />
<MenuItem Header="Add New" cal:Message.Attach="NewTimeSpan()" />
</CompositeCollection>
</MenuItem.ItemsSource>
<MenuItem.ItemTemplate>
<ItemContainerTemplate>
<MenuItem Header="{Binding Name}" cal:Message.Attach="ConfigureTimeSpan()" />
</ItemContainerTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
However, the view models are not populated like it was just using ItemsSource="{Binding TimeSpans}", it's not showing anything:
I suspect this is because I'm in the StackOverflow answer above the binding is actually a collection of MenuItems, so that composite collection makes sense. Whereas mine's mixing ViewModels & MenuItems.
Is there any way to construct the collection of menu-items created from ViewModels in the XAML so I can bind it?

For anyone else who comes across this, as Szabolcs Dezsi said, I needed to use a resource for the CollectionViewSource (bad reading comprehension on my part, as that was in the answer linked in my question).
Working code below:
<MenuItem Header="Time Ranges" x:Name="TimeRangesMenuItem">
<MenuItem.Resources>
<CollectionViewSource Source="{Binding ElementName=TimeRangesMenuItem, Path=TimeSpans}" x:Key="TimeSpanMenuItems" />
</MenuItem.Resources>
<MenuItem.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource TimeSpanMenuItems}}" />
<Separator />
<MenuItem Header="Add New" cal:Message.Attach="NewTimeSpan()" />
</CompositeCollection>
</MenuItem.ItemsSource>
<MenuItem.ItemTemplate>
<ItemContainerTemplate>
<MenuItem Header="{Binding Name}" cal:Message.Attach="ConfigureTimeSpan()" />
</ItemContainerTemplate>
</MenuItem.ItemTemplate>
</MenuItem>

Related

ContextMenu and Hierachicaldatatemplate Binding

In the MainWindow I have an ObservableCollection of "parent" items.
Every parent, has an ObservableCollection of "device" items.
The first level of my ContextMenu is properly bind to the items of the first Collection. The problem is, for every MenuItem I am getting always the same results (meaning that the datacontext for each MenuItem is always the same "parent"?)
Here is a screenshot, to better understand:
ContextMenu
And here is the XAML code:
<StackPanel.Resources>
<CollectionViewSource Source="{Binding Parents}" x:Key="Parents"/>
<HierarchicalDataTemplate DataType="{x:Type local:parent}">
<HierarchicalDataTemplate.ItemsSource>
<Binding>
<Binding.Source>
<CompositeCollection>
<MenuItem Header="ParentStartAll"/>
<MenuItem Header="ParentStopAll"/>
<MenuItem Header="ParentRestartAll"/>
<Separator/>
<CollectionContainer Collection="{Binding Childs, Source={StaticResource Parents}}"/>
</CompositeCollection>
</Binding.Source>
</Binding>
</HierarchicalDataTemplate.ItemsSource>
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:device}">
<HierarchicalDataTemplate.ItemsSource>
<Binding>
<Binding.Source>
<CompositeCollection>
<MenuItem Header="StartAll"/>
<MenuItem Header="StopAll"/>
<MenuItem Header="RestartAll"/>
<Separator/>
<CollectionContainer Collection="{Binding Processes, Source={StaticResource Parents}}"/>
</CompositeCollection>
</Binding.Source>
</Binding>
</HierarchicalDataTemplate.ItemsSource>
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
[...]
</StackPanel.Resources>
<StackPanel.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" ItemsSource="{Binding Parents}">
</ContextMenu>
</StackPanel.ContextMenu>
What am I doing wrong? Also, when I try to open the ContextMenu for the second time, it only shows the first level (only the "parents").
Any hints?
Pretty newbie here, so please don't angry if the question is too easy or if I made some ugly mistakes. I have to say it was not easy to manage the binding in the firstplace and I am struggling to get this contextmenu working.
Thanks a lot.

WPF - Create Context Menu mixing static and dynamic items

I try to create Context Menu mixing static and dynamic items.
this is the code:
<CollectionViewSource x:Key="DynamicMenuBridge" Source="{Binding Path=MyMenuCollection}" />
<ContextMenu x:Key="_Menu" >
<ContextMenu.ItemsSource>
<CompositeCollection>
<MenuItem Header="A" Click="..."/>
<Separator/>
<CollectionContainer Collection="{Binding Source={StaticResource DynamicMenuBridge}}" >
</CollectionContainer>
<Separator/>
<MenuItem Header="b" Click ="..."/>
<MenuItem Header="c" Click="..."/>
</CompositeCollection>
</ContextMenu.ItemsSource>
</ContextMenu>
But it does not work, why ?
(in debuging by snoop. it looks like that the CollectionContainer does not exist)

WPF, TreeViewItem, how to not show contextMenu according to dependency property?

I have a treeview using the following style:
<HierarchicalDataTemplate x:Key="itemTemplate" DataType="{x:Type AttCatalog:AttachmentCatalogModel}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" Tag="{Binding Guid}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="New Item"/>
<MenuItem Header="Move to..." />
<MenuItem Header="Delete" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
<TreeView x:Name="tree" HorizontalAlignment="Left" Width="216" BorderThickness="0,0,1,0" Background="#FFFBFBFB" IsEnabled="{Binding IsEnabled}" ItemsSource="{Binding Catalogs}" ItemTemplate="{StaticResource itemTemplate}" TreeViewItem.Expanded="OnExpandItemHandler" Margin="0,0,0,241" SelectedItemChanged="tree_SelectedItemChanged">
you can see TextBlock Tag binding a Guid property, my question is, how to do that when this Guid is empty (00000-00000000-00000), not show the contextMenu?
In your context menu, bind the visibility to the Guid, and treat it with a converter =>
<ContextMenu Visibility="{Binding Element=Guid,
Converter={StaticResource GuidToVisibilityConverter}}">
<MenuItem Header="New Item"/>
<MenuItem Header="Move to..." />
<MenuItem Header="Delete" />
</ContextMenu>
In your converter, you can treat your Guid value the way you want, to either return Visibility.Visible or Visibility.Hidden, depending on the value.
You can find more information on converters here.
Hope that helped !

How to Interact with 2 or More ViewModels

I have written a tool in which a ListBox is bound to a ObserservableCollection<object> with varying datatypes I've define. I use a PropertyDataTemplateSelector to present the data in the ListBox. The PropertyDataTemplateSelector references several DataTemplates that are set as UserControls. There is a background class that provides logic to the PropertyDataTemplateSelector by checking the object type and then applying the correct DataTemplate.
Here's an abbreviated example of the XAML for the UserControls and the MainWindow.
UserControl1
<TextBlock Text="{Binding Path=Val1}"
Style="{StaticResourse Yes}" />
<Button Content="I'm Button 1"
Command="{Binding Path=PathtoCommand1}"
CommandParameter="{Binding Parameter1}"
IsEnabled="{Binding IsEnabled1}" />
<Button Content="I'm Button 2"
Command="{Binding Path=PathtoCommand2}"
CommandParameter="{Binding Parameter2}"
IsEnabled="{Binding IsEnabled2}"
Tag="{Binding Path="DataContext.TagItem2}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem IsCheckable="True"
IsChecked="{Binding Path=Tag}"
DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</UserControl>
UserControlN
<UserControl x:Class="AwesomerControl">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=FancyName2}"
Style="{StaticResourse Yes}" />
<Button Content="Clicker 1"
Command="{Binding Path=DoSomethingGreat1}"
CommandParameter="{Binding Greatness1}"
IsEnabled="{Binding IsTurnedOn1}" />
<Button Content="Clicker 2"
Command="{Binding Path=DoSomethingGreat2}"
CommandParameter="{Binding Greaterness2}"
IsEnabled="{Binding IsTurnedOn2}"
Tag="{Binding Path="DataContext.TagItem2}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem IsCheckable="True"
IsChecked="{Binding Path=Tag}"
DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</UserControl>
Here I set the UserControls to a specified DataTemplate. The UserControls were moved out to make the XAML easier to read/navigate. In actuality the UserControls are a few hundred lines each.
<Window.Resources>
<DataTemplate x:Key"Template1">
<customControls:AwesomeControl/>
</DataTemplate>
...
<DataTemplate x:Key"TemplateN">
<customControls:AwesomerControl/>
</DataTemplate>
<dts:PropertyDataTemplateSelector x:Key="templateselector"
Template1="{StaticResource Template1"}
...
TemplateN="{StaticResource TemplateN"}
</Window.Resources>
The ListBox is defined as this.
<ListBox ItemSource="{Binding Path=CollectionofMyObjects}"
ItemTemplateSelector="{StaticResource templateselector}" />
I am using a single ViewModel to drive the MainWindow and the UserControls.
So that's where I'm at, essentially. I have this currently working as I'd like, but in an ongoing effort to learn (this is my first MVVM/WPF/C# project) I'd like to keep exploring how to make my code "better" (however that's defined). I'm not looking to solve an error here. So to avoid a general/broad question, I'll ask what I think I want to know. Someone can correct me and I'll update the "question(s)" appropriately
Question: How can I go about producing a ViewModel for each of the UserControls? Some of the ViewModels, for the UserControls, will occasionally require two-way communication to the MainWindow_ViewModel. The main crux of my problem is figuring out how the multiple VMs will communicate.
You're close, but it's not quite MVVM yet. ;)
First, break out all the functionality that is relevant to each UserControl into their own classes. These are your view-model classes.
Your controls should now become "view" classes, and they deserve their own mark-up file. Rather than use a template selector, you can use the DataTemplate.DataType to automatically connect the view-model class type to its view.
There are a lot of options for communication between view-models. To further your education, I'd consider looking at a light-weight MVVM framework that has built-in solutions for communication. My personal favorite is Caliburn.Micro, which includes an EventAggregator, a service that provides the ability to publish an object from one view-model to another in a loosely-coupled fashion.
Keep learning, you're on the right track!

Partially filling a MenuItem through binding

I want to use a list of custom objects to generate a list of MenuItems in a menu, but at the bottom of that menu I want a couple of static MenuItems to always appear. Logically in my head, this can be done by programatically creating another list to bind to that always has those MenuItems at the bottom, but this strikes me as a bit of a naive way of approaching this. I'm sure there's a more elegant way to do it with the list I already have along with some XAML wizardry, possibly with some kind of DataTemplate. Any pointers?
Like McGarnagle said, you can use a CompositeCollection. However, you don't need to create a resource for the fixed menu items. You can place them directly in the CompositeCollection like this:
<Menu>
<Menu.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Path=MyItems}" />
<Separator/>
<MenuItem Header="Fixed item 1" />
<MenuItem Header="Fixed item 2" />
</CompositeCollection>
</Menu.ItemsSource>
</Menu>
Use a CompositeCollection, with two child collections (the generated menu items, and the static ones).
Edit Should look something like this:
<Button Content="Test">
<Button.Resources>
<viewModel:MenuItemCollection x:Key="FixedMenuItems">
<MenuItem Header="Fixed Item" />
</viewModel:MenuItemCollection>
</Button.Resources>
<Button.ContextMenu>
<ContextMenu>
<ContextMenu.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{StaticResource FixedMenuItems}" />
<CollectionContainer Collection="{Binding MyMenuItems}" />
</CompositeCollection>
</ContextMenu.ItemsSource>
</ContextMenu>
</Button.ContextMenu>
</Button>
Where viewModel:MenuItemCollection is just a list of MenuItem:
public class MenuItemCollection : ObservableCollection<MenuItem>
{
}
Second Edit
One fix is needed for this. To bind to "MyMenuItems" in the view model, it's necessary to use a proxy as outlined in this answer. So instead of <CollectionContainer Collection="{Binding MyMenuItems}" />, you would end up using:
<CollectionContainer Collection="{Binding Path=DataContext.MyMenuItems,Source={StaticResource ProxyElement}}" />
And adding the proxy to the top of the view:
<UserControl.Resources>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
<UserControl.Resources>
<ContentControl Visibility="Collapsed" Content="{StaticResource ProxyElement}"/>

Categories