首页 > 解决方案 > 在动态创建的菜单 (WPF) 中为 PART_Popup 组件绑定“Placement”属性

问题描述

长话短说。我有一个动态创建的菜单,每个项目的视图模型如下:

  public class MenuItemViewModel
    {
        private readonly ICommand _command;
        public MenuItemViewModel(Action<object> action)
        {
            _command = new CommandViewModel(action);
        }

        public string Header { get; set; }
        public System.Windows.Controls.Primitives.PlacementMode Placement { get; set; }

        public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }

        public ICommand Command
        {
            get
            {
                return _command;
            }
        }
    }

整个窗口的数据上下文是另一个具有这些 menuViewModel 项的公共 ObservableCollection 的视图模型”

public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }

在 propgram 启动后,我添加了这样的新条目:

var currentDevice = (ControlableDeviceViewModel)sender;
            var cmds = new ObservableCollection<MenuItemViewModel>();
            foreach(var cmd in currentDevice.AvailableCommands)
            {
                cmds.Add(new MenuItemViewModel(cmd.Execute) { Header = cmd.CommandName,
                    Placement = System.Windows.Controls.Primitives.PlacementMode.Right});
            }

            MenuItems[0].MenuItems.Add(

                 new MenuItemViewModel((delegate {
                     RaiseAddNewDeviceEvent();
                 }))
                 { Header = "Add new..." });

              MenuItems[0].MenuItems.Add(

                    new MenuItemViewModel(null)
                    {
                        Header = currentDevice.Name + "->",
                        MenuItems = cmds
                     });
        }

正如您在此处看到的,我仅将位置添加到 cmds 项目,因为我希望它们向右弹出。默认行为是弹出到底部。当我在 XAML 中的模板中设置 Placement 时,它显然为所有项目设置了它。但我只想为特定项目设置它。无论我尝试以哪种方式绑定模型中的“属性”放置,它都无法正常工作。它始终具有默认值“底部”

  <Popup x:Name="PART_Popup" AllowsTransparency="true" Focusable="false" HorizontalOffset="2" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" Placement="{Binding RelativeSource={RelativeSource AncestorType={x:Type MenuItem},AncestorLevel='2'}, Path= Placement }" diag:PresentationTraceSources.TraceLevel="High" VerticalOffset="-1">


下面是负责菜单布局的 xaml 部分:

<Menu DockPanel.Dock="Top" 
                  ItemsSource="{Binding MenuItems}"
                  Grid.Column="0" 
                  Grid.ColumnSpan="5" 
                  Grid.Row="0"
                  Foreground="White"
                  Background="Black"
                  xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"               
                  >
            <Menu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="Command" Value="{Binding Command}" />
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type MenuItem}">
                                <Grid SnapsToDevicePixels="true">
                                    <DockPanel>
                                        <ContentPresenter x:Name="Icon" ContentSource="Icon" Margin="4,0,6,0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
                                        <Path x:Name="GlyphPanel" Fill="{TemplateBinding Foreground}" FlowDirection="LeftToRight" Margin="7,0,0,0" Visibility="Collapsed" VerticalAlignment="Center"/>
                                        <ContentPresenter x:Name="content" ContentSource="Header" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                    </DockPanel>
                                    <Popup x:Name="PART_Popup" AllowsTransparency="true" Focusable="false" HorizontalOffset="2" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" Placement="{Binding RelativeSource={RelativeSource AncestorType={x:Type MenuItem},AncestorLevel='1'}, Path= Placement }" diag:PresentationTraceSources.TraceLevel="High" VerticalOffset="-1">
                                        <Border BorderThickness="2" BorderBrush="Black" Background="Black">
                                            <ScrollViewer x:Name="SubMenuScrollViewer" CanContentScroll="true" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
                                                <Grid RenderOptions.ClearTypeHint="Enabled">
                                                    <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Cycle" Grid.IsSharedSizeScope="true" Margin="-1" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" KeyboardNavigation.TabNavigation="Cycle"/>
                                                </Grid>
                                            </ScrollViewer>
                                        </Border>
                                    </Popup>
                                </Grid>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="TextBlock.Foreground" Value="Gray" TargetName="content"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Menu.ItemContainerStyle>
            <Menu.ItemTemplate>
                <HierarchicalDataTemplate DataType="{x:Type viewModels:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
                    <TextBlock Text="{Binding Header}"
                               Padding="3"/>
                </HierarchicalDataTemplate>
            </Menu.ItemTemplate>
        </Menu>

非常感谢帮助我解决这个问题

标签: c#wpf

解决方案


看起来 viewModel 没有实现INotifyPropertyChanged接口,这就是为什么放置总是默认的。

尝试这个

public class MenuItemViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;  
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")  
    {  
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }  

    private readonly ICommand _command;
    public MenuItemViewModel(Action<object> action)
    {
        _command = new CommandViewModel(action);
    }

    private string header;
    public string Header 
    { 
        get 
        {
            return header;
        }
        set 
        {
            header = value;
            NotifyPropertyChanged();
        }
    }

    private System.Windows.Controls.Primitives.PlacementMode placement;
    public System.Windows.Controls.Primitives.PlacementMode Placement 
    { 
        get
        {
            return placement;
        }
        set 
        {
            placement = value;
            NotifyPropertyChanged();
        }
    }

    public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }

    public ICommand Command
    {
        get
        {
            return _command;
        }
    }
}

推荐阅读