I've encountered an oddity with a very basic WPF exercise I've devised for myself, namely dynamically populating menus from a ViewModel. Given the following main window markup:
<Window x:Class="Demosne.Client.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:project="clr-namespace:Demosne.Client.WPF">
<Grid>
<Menu Height="26" Name="menu1" VerticalAlignment="Top" HorizontalAlignment="Stretch" ItemsSource="{Binding MainMenuItems}">
<Menu.ItemTemplate>
<HierarchicalDataTemplate >
<MenuItem Header="{Binding Text, Mode=OneTime}" ItemsSource="{Binding MenuItems}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
<!--<MenuItem Header="File" />
<MenuItem Header="Edit" />-->
</Menu>
</Grid>
and the ViewModel(s):
public class MainWindowViewModel
{
private IList<MenuItemViewModel> _menuItems = new List<MenuItemViewModel>()
{
new MenuItemViewModel() { Text = "File" },
new MenuItemViewModel() { Text = "Edit" }
};
public IList<MenuItemViewModel> MainMenuItems
{
get
{
return _menuItems;
}
}
}
public class MenuItemViewModel
{
public string Text { get; set; }
public IList<MenuItemViewModel> MenuItems
{
get
{
return _menuItems;
}
}
private IList<MenuItemViewModel> _menuItems = new List<MenuItemViewModel>();
}
I would expect the GUI to exactly reproduce the the result of the two commented-out lines in the markup - two MenuItems called File and Edit.
However, the bound version behaves strangely on mouseover:
Markup version:
Bound version:
Why are they different?
You are getting funny results, because you are not really using the HierarchicalDataTemplate the correct way.
When you set a itemssource on a Menu, it will create a MenuItem for each object in the collection, and if you also supply a HierarchicalDataTemplate with a itemssource set, it will create MenuItems for each of the child objects in that collection as well, down the hierarchy.
In your case, you've added a MenuItem yourself in the template, which is not needed. The framework creates those items implicitly for you. And this is causing the menu to behave oddly.
So to get a correct result you should do something like this:
<HierarchicalDataTemplate ItemsSource="{Binding MenuItems}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" />
</StackPanel>
</HierarchicalDataTemplate>
Update
By setting a DataTemplate on something, you are telling WPF that you want to control, how each of its items should be displayed.
In this case a HierarchicalDataTemplate is used, which is a template for generating headered controls. This kind of control contains a header and an items collection.
When you apply this kind of template to an object, whatever you have put in the template will be used as the header, and the items collection will be created by applying the template to each of the child objects in the collection set as the ItemsSource on the template. So it will recursively apply the template to all objects in the hierarchy.
In your example, you have a Menu. You could just create it by doing this:
<Menu ItemsSource="{Binding MainMenuItems}" />
It would work fine, but since you have not applied a template, to tell it how the items in the collection should be displayed, it will just create a MenuItem for each object in the itemssource and call ToString() on it. This value will then be used as the Header property on the MenuItem.
Since that not what you want, you have to apply a template, to tell WPF what you would like to be displayed as the content in the header of the implicitly generated MenuItem.
In my example I simply made a template containing a TextBlock, which binds to the Text property on the viewmodel.
Update 2
If you now want to set properties on the implicitly created menuitems, you have to that by setting the ItemContainerStyle property on the HierarchicalDataTemplate. The styled defined here will be applied to all the generated menuitems.
So to bind the Command property of the MenuItem to a Command property on the viewmodel you can do this:
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding Command}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
Try this HierarchicalDataTemplate:
<HierarchicalDataTemplate>
<MenuItem ItemsSource="{Binding MenuItems}">
<MenuItem.Template>
<ControlTemplate>
<TextBlock Text="{Binding Text, Mode=OneTime}" />
</ControlTemplate>
</MenuItem.Template>
</MenuItem>
</HierarchicalDataTemplate>
MenuItem ControlTemplate Example (msdn link)
Controls in Windows Presentation Foundation (WPF) have a
ControlTemplate that contains the visual tree of that control. You can
change the structure and appearance of a control by modifying the
ControlTemplate of that control. There is no way to replace only part
of the visual tree of a control; to change the visual tree of a
control you must set the Template property of the control to its new
and complete ControlTemplate.
OK let's see now on the visual tree.
If we have something like this:
<Menu Height="26" Grid.Row="1">
<MenuItem Header="File" />
<MenuItem Header="Edit" />
</Menu>
Visual tree of this is represented below:
Ok, so MenuItem has ContentPresenter with TextBlock.
What happens if we have HierarchicalDataTemplate ?
<Menu.ItemTemplate>
<HierarchicalDataTemplate >
<MenuItem Header="{Binding Text, Mode=OneTime}" ItemsSource="{Binding MenuItems}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
Let's see on the visual tree:
Wow, what it is???
So if you don't specified ControlTemplate of MenuItem, it is itself ContentPresenter of the MenuItem (you can see this on the second screen). So, you must override ControlTemplate of the MenuItem if you want use it in HierarchicalDataTemplate (first screen).
Below is the visual tree with my solution:
Related
I have a UserControl that contains a TabControl that has an ItemTemplate that in turn has a Button. I want the user be able to change the content of that button, e.g. change it from TextBlock to Image.
A solution I thought of was to set the button's content from the Resources of the UserControl and overwrite the Resource by setting it on the ResourceDictionary of the entailing Window. Of course that does not work as StaticResource always resolves to the "closest" instance it can find.
I then thought of modifying the resource in the constructor of my UserControl, depending on some property. But it seems, one cannot change a resource. Below is a close sample showing the idea with a simple ListBox in a Window in which I try to change "What" to "How".
How would you approach this?
<Window.Resources>
<TextBlock x:Key="key" Text="What: " x:Shared="false" />
</Window.Resources>
<Grid Margin="10">
<ListBox Name="lbTodoList" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StaticResource ResourceKey="key" />
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TextBlock tb = FindResource("key") as TextBlock;
tb.Text = "How: ";
List<string> items = new List<string>();
items.Add("Item 1");
items.Add("Item 2");
items.Add("Item 3");
lbTodoList.ItemsSource = items;
}
}
Instead of trying to override a resource, you should just treat it like any other XAML data-binding templating issue.
Instead of:
<StaticResource ResourceKye="key" />
Do something like this:
<TextBlock Text="{Binding LabelText}" />
In your UserControl set the default value of LabelText to "What:" and then allow the user to override the value. The data binding will take care of the rest.
If you want to have more dynamic content, then use a ContentControl instead and have properties for Content, ContentTemplate, and even ContentTemplateSelector depending on what you need to do.
<ContentControl
Content="{Binding MyContent}"
ContentTemplate="{Binding MyContentTemplate}"
ContentTemplateSelector="{Binding MyContentTemplateSelector}" />
This opens up a lot of flexibility.
I have a control that is set up as a DataTemplate:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<DataTemplate x:Key="KEYBOARD_EN">
<StackPanel>
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
</StackPanel>
</DataTemplate>
In this DataTemplate there is a control on which I wish to set the Visibility from various view models:
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource ...} > Register </Button>
I do routed events with my control, so I tried to set up something similar, but no matter what I try, the RegisterButtonVisible property does not get picked up:
public partial class MainKeyboard : UserControl
{
public static DependencyProperty RegisterButtonVisibleProperty;
public Visibility RegisterButtonVisible
{
get { return (Visibility)GetValue(RegisterButtonVisibleProperty); }
set { SetValue(RegisterButtonVisibleProperty, value); }
}
static MainKeyboard()
{
RegisterButtonVisibleProperty = DependencyProperty.Register("RegisterButtonVisible", typeof (Visibility),
typeof (MainKeyboard));
}
}
In my ViewModel I do this:
public Visibility RegisterButtonVisible // get, set, raisepropchange, etc
My DataTemplate with the button in it is wrapped in a userControl:
<UserControl x:Class="Bleh.Assets.MainKeyboard"
x:Name="TheControl"
Unloaded="UserControl_Unloaded">
<Viewbox>
<Grid>
<ContentControl Name="ctrlContent" Button.Click="Grid_Click" />
</Grid>
</Viewbox>
and is used in my views like this:
<assets:MainKeyboard
RegisterButtonVisible="Collapsed"
Loaded="MainKeyboard_Loaded">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Register">
<b:InvokeCommandAction Command="{Binding ConfirmEmailAddressCommand}"/>
</b:EventTrigger>
<b:EventTrigger EventName="Enter">
<b:InvokeCommandAction Command="{Binding EnterKeyCommand}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
</assets:MainKeyboard>
Please note this attribute:
RegisterButtonVisible="Collapsed"
This is my dependency property. It shows up in intelliesense, so the CLR has registered it correctly, but it does NOT pick up the property assignment (Collapsed is ignored).
This makes me feel like it is very close, but I do remember someone telling me I can not do this, thus the EventTriggers (this is a common issue with datatemplates and MVVM apparently).
So one option is to use something in the Interaction namespace, like I do my event triggers ( I just need to fire a "Visibility" trigger on this button somehow, at least I figure).
What is the right ANY way to do this in MVVM?
Fixing your code
In order to make your existing code work, you need to tell need to tell WPF what object RegisterButtonVisible should be read from. If it's a user control, give the UserControl a name and then reference that element via ElementName, like so:
<UserControl ... lots of stuff here
x:Name="TheControl"
>
In your button binding:
<Button Visibility="{Binding ElementName=TheControl, Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, if you can't do that because the button and the usercontrol are in different files, you can still use an ancestor binding:
<Button Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type assets:MainKeyboard}},
Path=RegisterButtonVisible}"
Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
which, for each button, will walk up to find the closest instance of assets:MainKeyboard and then bind to the RegisterButtonVisible property.
Using MVVM
If you want to achieve the same using MVVM (instead of on a control), you need to use a converter to convert a boolean to a visibility property, like so:
<Button Visibility="{Binding IsRegistrationAllowed, Converter={StaticResource BoolToVis}}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, that assumes that your DataContext is set up correctly and pointing at your ViewModel.
I want to know how you would bind a View specific data type to your View from a ViewModel.
To be more concrete I have a ContextMenu as part of a DatagridView. Now I can bind the ItemSource of my ContextMenu to either a List of MenuItems or a List of Strings (<= implementing a Converter on this).
Both options work fine, but I want to know which is the best and why. I am asking cause I have read that I should try to not use the System.Windows.* Namespace in my ViewModel, and when I Bind a List of MenuItems of course I use this Namespace but on the other Hand if I bind strings and just convert it... This feels weird.
SampleCode (using Caliburn):
<DataGrid x:Name="OverviewItems">
<DataGrid.ContextMenu>
<ContextMenu ItemsSource="{Binding AllColumns, Converter={StaticResource String_Menu_Converter}}" >
<!-- Alternative: <ContextMenu ItemsSource="{Binding AllColumns}" >-->
<ContextMenu.ItemContainerStyle>
<Style>
<Setter Property="cal:Message.Attach" Value="[Event Click]=[SetVisibilityExecute($_clickeditem)]" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
In WPF, we manipulate data object, not UI objects. So it is true that you should not have any UI objects in your view model. The correct way to get around this issue is to manipulate data objects in the view model that we can then data bind with the UI controls that we declare in DataTemplates. So in your case, you could have a collection of custom objects that have a property to bind to the MenuItem.Header property and another to bind to the MenuItem.Command property for example:
<DataTemplate DataType="{x:Type YourPrefix:YourCustomType}">
<MenuItem Header="{Binding Header}" Command="{Binding Command}" />
</DataTemplate>
...
<Menu ItemsSource="{Binding YourCustomTypeCollection}" />
In my WPF application I have a viewmodel class called CompanyViewModel.
Sometimes, an instance of this class is set as the DataContext of my main window, which is defined like this:
<window x:Class= ..... >
<Grid>
<ContentControl Content="{Binding }"></ContentControl>
</Grid>
</Window>
In this case I want a view to be used that displays all the properties of the viewmodel.
Other times, a ListView control has its itemsource set as a collection containing instances of CompanyViewModel. Here, I want a view to be used that renders only some important properties.
I have this in the resource dictionary of MainWindow.xaml:
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyView></vw:CompanyView>
</DataTemplate>
Is it possible to select a view for the viewmodel based on the context where the viewmodel is bound? For instance, to use CompanyView when displayed in the ContentControl of a window or when in a TabControl, and to use CompanyViewSmall where displayed in a ListView?
The DataTemplate to use is first looked for locally, and then looked for further up the Visual Tree hierarchy if it's not found.
Because of this, you can specify the DataTemplate to use further down the hierarchy to use something different than normal.
For example, the following will use the CompanyView anywhere the CompanyViewModel is in the visual tree, except in the specific ListView where the DataTemplate is specified as the smaller view.
<Window.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyView />
</DataTemplate>
</Window.Resources>
<ListView>
<ListView.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyViewSmall />
</DataTemplate>
</ListView.Resources>
</ListView>
You could also use an implicit style for the ListView telling it to use the smaller template in the .Resources, however this will apply the smaller view to any ListView, not just specific ones, and if you ever apply another style to a ListView you'll have to remember to inherit the default style to keep the smaller DataTemplate.
<Style TargetType="{x:Type ListView}">
<Style.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyViewSmall />
</DataTemplate>
</Style.Resources>
</Style>
Has anybody else noticed that Bindings with ElementName do not resolve correctly for MenuItem objects that are contained within ContextMenu objects? Check out this sample:
<Window x:Class="EmptyWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
x:Name="window">
<Grid x:Name="grid" Background="Wheat">
<Grid.ContextMenu>
<ContextMenu x:Name="menu">
<MenuItem x:Name="menuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/>
<MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/>
<MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/>
<MenuItem Header="Menu Item" Tag="{Binding ElementName=menuItem}" Click="MenuItem_Click"/>
</ContextMenu>
</Grid.ContextMenu>
<Button Content="Menu"
HorizontalAlignment="Center" VerticalAlignment="Center"
Click="MenuItem_Click" Tag="{Binding ElementName=menu}"/>
<Menu HorizontalAlignment="Center" VerticalAlignment="Bottom">
<MenuItem x:Name="anotherMenuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/>
<MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/>
<MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/>
<MenuItem Header="Menu Item" Tag="{Binding ElementName=anotherMenuItem}" Click="MenuItem_Click"/>
</Menu>
</Grid>
</Window>
All of the bindings work great except for the bindings contained within the ContextMenu. They print an error to the Output window during runtime.
Any one know of any work arounds? What's going on here?
I found a much simpler solution.
In the code behind for the UserControl:
NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));
As said by others, the 'ContextMenu' is not contained in the visual tree and an 'ElementName' binding won't work. Setting the context menu's 'NameScope' as suggested by the accepted answer only works if the context menu is not defined in a 'DataTemplate'. I have solved this by using the {x:Reference} Markup-Extension which is similar to the 'ElementName' binding but resolves the binding differently, bypassing the visual tree. I consider this to be far more readable than using 'PlacementTarget'. Here is an example:
<Image Source="{Binding Image}">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding Source={x:Reference Name=Root}, Path=DataContext.RemoveImage}"
CommandParameter="{Binding}" />
</ContextMenu>
</Image.ContextMenu>
</Image>
According to the MSDN-documentation
x:Reference is a construct defined in XAML 2009. In WPF, you can use
XAML 2009 features, but only for XAML that is not WPF markup-compiled.
Markup-compiled XAML and the BAML form of XAML do not currently
support the XAML 2009 language keywords and features.
whatever that means... Works for me, though.
Here's another xaml-only workaround. (This also assumes you want what's inside the DataContext, e.g., you're MVVMing it)
Option one, where the parent element of the ContextMenu is not in a DataTemplate:
Command="{Binding PlacementTarget.DataContext.MyCommand,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"
This would work for OP's question. This won't work if you are inside of a DataTemplate. In these cases, the DataContext is often one of many in a collection, and the ICommand you wish to bind to is a sibling property of the collection within the same ViewModel (the DataContext of the Window, say).
In these cases, you can take advantage of the Tag to temporarily hold the parent DataContext which contains both the collection AND your ICommand:
class ViewModel
{
public ObservableCollection<Derp> Derps { get;set;}
public ICommand DeleteDerp {get; set;}
}
and in the xaml
<!-- ItemsSource binds to Derps in the DataContext -->
<StackPanel
Tag="{Binding DataContext, ElementName=root}">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem
Header="Derp"
Command="{Binding PlacementTarget.Tag.DeleteDerp,
RelativeSource={RelativeSource
AncestorType=ContextMenu}}"
CommandParameter="{Binding PlacementTarget.DataContext,
RelativeSource={RelativeSource AncestorType=ContextMenu}}">
</MenuItem>
Context menus are tricky to bind against. They exist outside the visual tree of your control, hence they can't find your element name.
Try setting the datacontext of your context menu to its placement target. You have to use RelativeSource.
<ContextMenu
DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...
After experimenting a bit, I discovered one work around:
Make top level Window/UserControl implement INameScope and set NameScope of ContextMenu to the top level control.
public class Window1 : Window, INameScope
{
public Window1()
{
InitializeComponent();
NameScope.SetNameScope(contextMenu, this);
}
// Event handlers and etc...
// Implement INameScope similar to this:
#region INameScope Members
Dictionary<string, object> items = new Dictionary<string, object>();
object INameScope.FindName(string name)
{
return items[name];
}
void INameScope.RegisterName(string name, object scopedElement)
{
items.Add(name, scopedElement);
}
void INameScope.UnregisterName(string name)
{
items.Remove(name);
}
#endregion
}
This allows the context menu to find named items inside of the Window. Any other options?
I'm not sure why resort to magic tricks just to avoid a one line of code inside the eventhandler for the mouse click you already handle:
private void MenuItem_Click(object sender, System.Windows.RoutedEventArgs e)
{
// this would be your tag - whatever control can be put as string intot he tag
UIElement elm = Window.GetWindow(sender as MenuItem).FindName("whatever control") as UIElement;
}