WPF Dynamic Context Menu with Caliburn.Micro - wpf

I am trying to create a dynamic context menu in my Caliburn.Micro based application. Can anyone share an example of an effective way to do that?
So far, I have a very minimal model for each context menu item:
public class ContextMenuUiModel
{
public string Name { get; set; }
}
a property in my view model that presents a list of those menu item models:
BindableCollection<ContextMenuUiModel> m_ContextMenuItems = new BindableCollection<ContextMenuUiModel>
{
new ContextMenuUiModel { Name="Item 1"},
new ContextMenuUiModel { Name="Item 2"},
new ContextMenuUiModel { Name="Item 3"}
};
public BindableCollection<ContextMenuUiModel> ContextMenuItems
{
get {return m_ContextMenuItems;}
}
and, a menu item named for the collection property (based on the menu creation in FreePIE, found via this question and answer)
<TreeView x:Name="ConfigItemTree" VerticalAlignment="Top" ItemsSource="{Binding ConfigTreeRoot}" >
<TreeView.ContextMenu>
<ContextMenu >
<MenuItem x:Name="ContextMenuItems" DisplayMemberPath="Name" />
</ContextMenu>
</TreeView.ContextMenu>
Caliburn.Micro logging reports "No actionable element for get_ContextMenuItems". Also, although Caliburn is noting other named elements for which no property was found (e.g. "Binding Convention Not Applied: Element ConfigItemTree did not match a property."), it is not making a similar statement for ContextMenuItems. So, it seems Caliburn is just not seeing the ContextMenu as an element it could or should deal with.
Maybe the issue is that Caliburn can't see the context menu because it doesn't actually exist until a right click happens (similar to this issue with collapsed elements)?
Ultimately, I would like the context menu's contents to be based on the tree view item that was right clicked, possibly including sub menus and/or disabled items. For a start, though, I'll settle for whatever items I can get.

Bind the ItemsSource property of the ContextMenu to the ContextMenuItems property:
<ContextMenu ItemsSource="{Binding PlacementTarget.DataContext.ContextMenuItems, RelativeSource={RelativeSource Self}}"
DisplayMemberPath="Name" />

Related

Combo box with submenu

How can I show the combo box like this one, in C#.net 2010
It is possible by combining two controls ComboBox and TreeView in one UserControl.
Although this control looks quite simple, the actual implementation isn’t clear and takes long time.
Here is the sequence of steps:
1) Custom TreeView and TreeViewItem. They provide the following functionality:
Allow to expand and select an item from a view model (It isn’t possible to select an item of the TreeView using other ways)
The event which is fired when a user clicks a TreeViewItem (so it will be possible to close the ComboBox)
2) Interface for an item of the ItemsSource collection
public interface ITreeViewItemModel
{
string SelectedValuePath { get; }
string DisplayValuePath { get; }
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
IEnumerable<ITreeViewItemModel> GetHierarchy();
IEnumerable<ITreeViewItemModel> GetChildren();
}
Members of this interface:
IsExpanded – allows to expand the TreeViewItem from the bound view model. Must be implemented as the INotifyPropertyChanged property.
IsSelected – allows to select the TreeViewItem from the bound view model. Must be implemented as the INotifyPropertyChanged property.
SelectedValuePath – the unique string (unique item id) that will be used to select and expand the treeview control.
DisplayValuePath – the content that will be displayed at the header when the combobox is closed
GetHierarchy – returns the item and its parents.
GetChildren – returns child items of the current item
Create the Combobox:
It is the most difficult part of the implementation. I was forced to create many methods to provide the connection between Combobox and TreeView.
But although there is many private methods, there is only two public properties:
SelectedItem – now it is possible to get or set this item and it will be selected in the treeview.
SelectedHierarchy – it wasn’t necessary to create this property, but it wasn’t difficult so I’ve decided to implement it. Use list of strings instead of the actual item.
Add it to a view and bind with a view model:
<UserControl.Resources>
<Windows:HierarchicalDataTemplate x:Key="treeViewDataTemplate"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Title}" />
</Windows:HierarchicalDataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<local:ComboBoxTreeView ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
ItemTemplate="{StaticResource treeViewDataTemplate}"
HorizontalAlignment="Center" VerticalAlignment="Top" />
</Grid>
The ItemTemplate property is obligatory property and must be of the HierarchicalDataTemplate type.
Check this out https://vortexwolf.wordpress.com/2011/04/29/silverlight-combobox-with-treeview-inside/

ItemsControl with databound ItemsSource and two-way binding of elements (replacable items)

I have an ObservableCollection of objects (e.g. Persons with First/Last Name) which I would like to display in an ItemsControl. Each Item is displayed in a custom "editor" control, which allows editing of the object's properties.
This part is working fine and fairly standard.
<ItemsControl ItemsSource="{Binding Persons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<custom:PersonEditor Person="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
However, the custom editor control also has the ability to replace the entire object is has received (rather than just editing a person's name, replace it with a new person object).
What I am looking for is a way to push this change back into the ObservableCollection. As it is now, changing the Person object within the editor does not replace the item in the list, which would be the desired outcome.
Any help would be appreciated.
If you don't have access to the custom control itself what you could try would be using a setter on a property to clear the ObservableCollection and re-add the items instead of outright replacing it.
For example:
private ObservableCollection<MyModel> _dataSource;
public ObservableCollection<MyModel> DataSource
{
get
{
return _dataSource;
}
set
{
_dataSource.Clear();
foreach(var item in value)
{
_dataSource.Add(item);
}
}
}
This would prevent the item itself from getting replaced which causes issues with ItemSources since they apparently only bind to the items property once.

Binding MenuItem's IsChecked to TabItem's IsSelected with dynamic tabs

I have a list of tab items that have views dynamically added to them. Every time a user adds a view, a new tab item is created. I'm now trying to bind a menu to a tabcontrol's items so that a user can select from a menu which view is currently the active view.
My menu is bound as such:
<Menu Background="Transparent">
<MenuItem Style="{StaticResource TabMenuButtonStyle}" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=Items}" ItemContainerStyle="{StaticResource TabMenuItem}"></MenuItem>
</Menu>
This works fine and has the desired effect (each menu item is a listing of all the open tabs).
I have the following style that binds menu items to the IsSelected property of the tab items:
<Setter Property="IsChecked" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
My problem is, this binding doesn't work. The binding error message is stating that it can't find the IsSelected property on the view object. I don't want it to use the specfic view, rather, I want it to look at the tab item that the view is currently bound to.
I've tried the following, but still get a binding error:
<Setter Property="IsChecked" Value="{Binding Path=IsSelected, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=TabItem}}}" />
Which states that it can't find an ancestor of type TabItem for each menu item (which makes sense as the menu item's ancestors are not what it is bound to.)
Is there any way I can get access to the parent of the item that is coming in as a binding so I can bind to its properties?
Update:
Per Yadyn's advice, I decided to create a value converter and return tab items.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
ItemCollection ic = (ItemCollection)value;
List<TabItem> tabItems = new List<TabItem>();
foreach (var obj in ic) {
tabItems.Add((TabItem)obj);
}
return tabItems;
}
This makes binding IsSelected to IsChecked work for static items (TabControls that have their tab items already created), but for the dynamically added views, the Convert method never gets called. It's like the TabControl is not sending out an update to binders of its items that something has changed. Here is how the MenuItem is wired up now:
<MenuItem Style="{StaticResource TabMenuButtonStyle}" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=Items, Mode=OneWay, NotifyOnSourceUpdated=True, Converter={StaticResource TabControlItemConverter}}" ItemContainerStyle="{StaticResource TabMenuItem}"></MenuItem>
TabControl.Items will get you back the views, since that is what you've bound to your TabControl to have dynamic tab views.
Unfortunately, there isn't a property you can bind to on the TabControl directly that will get you a collection of the TabItems. These are actually the ItemContainers for each item in the Items bound collection.
What you might do is create a converter or something. You can try using myTabControl.ItemContainerGenerator.ContainerFromItem and pass in the view object to get back the actual TabItem that wraps it. Then your IsSelected binding will work.
You might consider binding directly to the TabControl itself instead of the Items property. Then the converter can easily do the above call to ContainerFromItem. You'll then have to return a List<TabItems> from the converter by enumerating the Items property yourself (calling ContainerFromItem for each).
Anyway, hopefully this gets you on the right track!
Here is something simpler. Define a toplevel viewmodel that holds a collection of viewmodels representing the tabs and menuitems like so
//Not showing here the details of implementing INPC
public class MyCustomCompositeViewModel:INotifyPropertyChanged
{
public ObservableCollection<CompositeViewItem>CompositeItems{get;set;}
public CompositeViewItem SelectedItem{get;set;}
}
On the view, bind the tabitems to the CompositeItems collection and bind the selected tab item to the selectedItem. You can bind the MenuItems similarly
The compositeviewitem should provide properties like the name of the item (for display on the tab and menu), and perhaps the additional data the View needs for rendering. Hope this makes sense.

What is the best way in MVVM to build a menu that displays various pages?

I want to build a simple application with the MVVM pattern.
This application will have two main parts:
menu on top
content below
The navigation will be simple:
each menu item (e.g. "Manage Customers" or "View Reports") will fill the content area with a new page that has some particular functionality
I have done this before with code behind where the code-behind event-handler for menu items had all pages loaded and the one that should be displayed was loaded in as a child of a StackPanel. This, however, will not work in MVVM since you don't want to be manually filling a StackPanel but displaying e.g. a "PageItem" object with a DataTemplate, etc.
So those of you who have made a simple click-menu application like this with MVVM, what was your basic application structure? I'm thinking along these lines:
MainView.xaml:
<DockPanel LastChildFill="False">
<Menu
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"/>
<ContentControl
Content="{Binding SelectedPageItem}"/>
</DockPanel>
where the Menu is filled with a collection of "PageItems" and the DataTemplate displays the Title of each "PageItem object" as the Header of each MenuItem.
And the ContentControl will be filled with a View/ViewModel pair which has full functionality, but am not sure on this.
First, I think you should keep the code-behind event handler, there's no point in changing a simple 2 line event handler to a complex command driven monster for no practical reason (and don't say testebility, this is the main menu, it will be tested every time you run the app).
Now, if you do want to go the pure MVVM route, all you have to do it to make your menu fire a command, first, in some resource section add this style:
<Style x:Key="MenuItemStyle" TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding DataContext.SwitchViewCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/>
<Setter Property="CommandParameter"
Value="{Binding}"/>
</Style>
This style will make the menu item fire a the SwitchViewCommand on the attached view model with the MenuItem's DataContext as the command parameter.
The actual view is the same as your code with an additional reference to that style as the ItemContainerStyle (so it applies to the menu item and not the content of the DataTemplate):
<DockPanel LastChildFill="False">
<Menu DockPanel.Dock="Top"
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"
ItemContainerStyle="{StaticResource MenuItemStyle}"/>
<ContentControl
Content="{Binding SelectedPageItem}"/>
</DockPanel>
Now in the view model you need (I used strings because I don't have your PageItem code):
private string _selectedViewItem;
public List<string> PageItemsMainMenu { get; set; }
public string SelectedPageItem
{
get { return _selectedViewItem; }
set { _selectedViewItem = value; OnNotifyPropertyChanged("SelectedPageItem"); }
}
public ICommand SwitchViewCommand { get; set; }
And use whatever command class you use to make the command call this code:
private void DoSwitchViewCommand(object parameter)
{
SelectedPageItem = (string)parameter;
}
Now, when the user clicks a menu item the menu item will call the SwitchViewCommand with the page item as the parameter.
The command will call the DoSwitchViewCommand that will set the SelectedPageItem property
The property will raise the NotifyPropertyChanged that will make the UI update via data binding.
Or, you can write a 2 line event handler, your choice
i could imagine an ObservableCollection in the VM, that holds all the pages to be callable from the menu.
Then bind an ItemsControl And the ContentControl to it to make the ContentControl always show the CurrentItem from that List.
Of course, the menu will only bind to some Title property
whereas the ContentControl will adopt the whole item and plug in some appropriate view according to the type.
Another option is to use a ListBox instead of a menu, style the ListBox to look like a menu and then you can bind to the selected value, like this:
<DockPanel LastChildFill="False">
<ListBox
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"
IsSynchronizedWithCurrentItem="True"/>
<ContentControl
Content="{Binding PageItemsMainMenu/}"/>
</DockPanel>
Note the IsSynchronizedWithCurrentItem="True" to set the selected item and the {Binding PageItemsMainMenu/} with the trailing slash to use it.

How to bind an observable collection to Multiple user controls at runtime?

I am stucked at the part where I have to bind a collection to a dynamic usercontrol. Scenario is something like this.
I have a dynamic control, having a expander , datagrid, combobox and textbox, where combox and textbox are inside datagrid. There are already two collections with them. One is binded with combobox and another is binded with datagrid. When the item is changes in combox its respective value is set to its respective textbox, and so on. and this pair of value is then set to the collection binded with datagrid. A user can add multiple items.
Now the main problem is that all these things are happening inside a user control which is added dynamically, that is on button click event. A user can add desired numbers of user controls to the form.
problem is coming in this situtaion. Say I have added 3 controls. Now in 1st one if i add a code to the collection then it gets reflected in the next two controls too, as they are binded with same collection.
So, I want to know is there anyway to regenrate/rename the same collection so that the above condition should not arise.
It's hard to answer your question without seeing the bigger picture, however I have a feeling you are going about this the wrong way. It appears that you are adding instances of your user control directly from code. Instead of doing that, you should create some kind of ItemsControl in your XAML, and in its ItemTemplate have your user control. Bind that ItemsControl to a collection in your view model, and only manipulate that collection.
You should not be referring to visual controls in your view model or code behind. Whenever you find yourself referencing visual elements directly from code, it should raise a warning flag in your mind "Hey! There's a better way than that!"...
Example:
The view model:
public class ViewModel
{
public ObservableCollection<MyDataObject> MyDataObjects { get; set; }
public ViewModel()
{
MyDataObjects = new ObservableCollection<MyDataObject>
{
new MyDataObject { Name="Name1", Value="Value1" },
new MyDataObject { Name="Name2", Value="Value2" }
};
}
}
public class MyDataObject
{
public string Name { get; set; }
public string Value { get; set; }
}
The window XAML fragment containing the list box and the data template:
<Window.Resources>
...
<DataTemplate x:Key="MyDataTemplate">
<local:MyUserControl/>
</DataTemplate>
</Window.Resources>
...
<ListBox ItemsSource="{Binding MyDataObjects}"
ItemTemplate="{StaticResource MyDataTemplate}"
HorizontalContentAlignment="Stretch"/>
The user control:
<UniformGrid Rows="1">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Value}" HorizontalAlignment="Right"/>
</UniformGrid>

Resources