问题
Within a TreeView's resources, I have a HierarchicalDataTemplate that also defines the context menu style. The aim is to be able to have programmatically defined MenuItems specify an icon name as their Tag and then have the front-end display the right icon.
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuItems}">
<ContextMenu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Icon">
<Setter.Value>
<local:StringToIcon IconName="{Binding Tag, RelativeSource={RelativeSource AncestorType=MenuItem}}" />
</Setter.Value>
</Setter>
</Style>
</ContextMenu.Resources>
</ContextMenu>
</StackPanel.ContextMenu>
StringToIcon is another control that, for testing, looks just like this. It is backed by the dependency property IconName.
<UserControl x:Class="MyApp.Components.StringToIcon"
...
Name="StringIconControl">
<Image DataContext="{Binding ElementName=StringIconControl}">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding IconName}" Value="Refresh">
<Setter Property="Source" Value="{StaticResource IconRefresh}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</UserControl>
IconRefresh is simply a globally available resource:
<BitmapImage x:Shared="False" x:Key="IconRefresh" UriSource="pack://application:,,,/Resources/Icons/refresh.png" />
When starting the application, none of the context menu refresh icons appear. They are all blank. I get binding errors, which I'm led to believe is due to the context menus not being in the visual tree:
Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.MenuItem', AncestorLevel='1''. BindingExpression:Path=Tag; DataItem=null; target element is 'StringToIcon' (Name='StringIconControl'); target property is 'IconName' (type 'String')
However, when using Snoop to inspect the menu, all of the bindings and dependency property are correct. After inspecting in this way, the refresh icon inspected miraculously appears, as though it's forced the bindings to reevaluate. You can do this, one by one, to every instance of this icon and they'll appear.
How do I fix this lazy binding that seems to be happening here? Or is there something else at play? I saw another post that suggested attaching the menu data context to something that is in the visual tree, but I don't see what that would be in this instance.
回答1:
The eventual solution to the binding problem was to make a new class to hold the menu item details:
public class BindableMenuItem
{
public BindableMenuItem(string name, ICommand command)
{
this.Name = name;
this.Command = command;
}
public string Name { get; set; }
public ICommand Command { get; set; }
public string IconName { get; set; }
public ObservableCollection<BindableMenuItem> Children { get; set; }
}
This was then tied up to the menu item's style as follows:
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="ItemsSource" Value="{Binding Children}" />
<Setter Property="Icon">
<Setter.Value>
<local:StringToIcon IconName="{Binding IconName}" />
</Setter.Value>
</Setter>
</Style>
Presumably this works because the binding is not dependent upon there being a relative source in the visual tree, and instead it has a concrete model class to use.
There is, however, still an issue with the above regarding multiple style instances. I'll post another question for that.
来源:https://stackoverflow.com/questions/45943272/wpf-context-menu-data-bound-icon-only-appears-when-inspected