关于WPF ContextMenu的Xaml Namescope和模板问题

时间:2010-06-06 19:14:38

标签: wpf xaml binding contextmenu

除了ContextMenu上的绑定之外,下面代码中的所有内容都有效。这显然是由于ContextMenu位于Style内部,它将其置于与xaml其余部分不同的名称范围内。我正在寻找一个解决方案,我不必在代码隐藏中实例化ContextMenu,因为我必须应用解决方案的应用程序包含一个非常大的ContextMenu,其中包含大量绑定。必须有一种方法可以在xaml中实现这一点,否则它看起来像是一个严重的疏忽。另请注意,我已经尝试使用VisualTreeHelper和LogicalTreeHelper遍历元素树,但是我无法从Window的根元素中找到ContextMenu(这些类明显地跳过了有趣的元素)。无论如何,所有代码都在下面。这可以粘贴到Visual Studio中的新WPF应用程序中,并且不会遗漏任何内容。

这是App.xaml.cs的代码(xaml保持不变):

using System.Windows;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            WindowV windowV = new WindowV();
            WindowVM windowVM = new WindowVM();

            windowV.DataContext = windowVM;

            windowV.Show();
        }
    }
}

这是原来Window1的xaml:

<Window x:Class="WpfApplication1.WindowV"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:WpfApplication1"
        Name="MainWindow"
        Title="WindowV" Height="300" Width="300">
    <Window.Resources>
        <Style TargetType="{x:Type ItemsControl}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsLocked}" Value="true">
                    <Setter Property="ItemsSource" Value="{Binding LockedList}" />
                    <Setter Property="ItemTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <TextBlock Text="{Binding}" />
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
                <DataTrigger Binding="{Binding IsLocked}" Value="false">
                    <Setter Property="ItemsSource" Value="{Binding RegularList}" />
                    <Setter Property="ItemTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <TextBlock Text="{Binding}">
                                    <TextBlock.ContextMenu>
                                        <ContextMenu>
                                            <MenuItem Header="{Binding MenuItem1, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
                                            <MenuItem Header="{Binding MenuItem2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
                                            <MenuItem Header="{Binding MenuItem3, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
                                        </ContextMenu>
                                    </TextBlock.ContextMenu>
                                </TextBlock>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="4*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <ItemsControl Grid.Row="0" />
        <Button Name="ToggleButton"
                Grid.Row="1"
                Content="Toggle Lock"
                Click="OnToggleLock" />
    </Grid>
</Window>

这是最初Window1:

的代码隐藏
using System.Windows;
using System.Windows.Markup;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class WindowV : Window
    {
        public WindowV()
        {
            InitializeComponent();
        }

        private void OnToggleLock(object sender, RoutedEventArgs e)
        {
            if (((WindowVM)(DataContext)).IsLocked == true)
                ((WindowVM)(DataContext)).IsLocked = false;
            else
                ((WindowVM)(DataContext)).IsLocked = true;
        }
    }
}

一个名为WindowVM的项目中添加了一个新类。这是它的代码:

using System.Collections.Generic;
using System.ComponentModel;

namespace WpfApplication1
{
    public class WindowVM : INotifyPropertyChanged
    {
        public string MenuItem1
        {
            get
            {
                string str = "Menu item 1";
                return str;
            }
        }
        public string MenuItem2
        {
            get
            {
                string str = "Menu item 2";
                return str;
            }
        }
        public string MenuItem3
        {
            get
            {
                string str = "Menu item 3";
                return str;
            }
        }

        public List<string> LockedList
        {
            get
            {
                List<string> list = new List<string>();
                list.Add("This items control is currently locked.");
                return list;
            }
        }
        public List<string> RegularList
        {
            get
            {
                List<string> list = new List<string>();
                list.Add("Item number 1.");
                list.Add("Item number 2.");
                list.Add("Item number 3.");
                return list;
            }
        }

        private bool _isLocked;
        public bool IsLocked
        {
            get { return _isLocked; }
            set
            {
                if (_isLocked != value)
                {
                    _isLocked = value;
                    OnPropertyChanged("IsLocked");
                }
            }
        }

        public WindowVM()
        {
            IsLocked = false;
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
        }
    }
}

任何见解都将非常感激。非常感谢!

安德鲁

2 个答案:

答案 0 :(得分:0)

好的 - 我在完成你想要完成的工作时遇到了一些麻烦 - 但请尝试以下方法:

<ContextMenu>
     <MenuItem Header="{Binding DataContext.MenuItem1, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
     ...

为了使您的代码更容易,您可以尝试:

<ContextMenu DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
     <MenuItem Header="{Binding MenuItem1}"/>
     ...

问题在于您绑定了UI元素Window,该窗口没有名为MenuItem1等的属性.DataContext属性包含您感兴趣的数据。

答案 1 :(得分:0)

好的,这个解决方案有效:我更改了WindowV.xaml和WindowV.xaml.cs文件,如下所示。以下更正修复了xaml中关于名称范围的问题。

这是新的WindowV.xaml文件:

<Window x:Class="WpfApplication1.WindowV"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:WpfApplication1"
        Name="RootElement"
        Title="WindowV" Height="300" Width="300">
    <Window.Resources>
        <ContextMenu x:Key="myContextMenu" DataContext="{Binding Path=DataContext, ElementName=RootElement}">
            <MenuItem Header="{Binding MenuItem1}" />
            <MenuItem Header="{Binding MenuItem2}" />
            <MenuItem Header="{Binding MenuItem3}" />
        </ContextMenu>
        <Style TargetType="{x:Type ItemsControl}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsLocked}" Value="true">
                    <Setter Property="ItemsSource" Value="{Binding LockedList}" />
                    <Setter Property="ItemTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <TextBlock Text="{Binding}" />
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
                <DataTrigger Binding="{Binding IsLocked}" Value="false">
                    <Setter Property="ItemsSource" Value="{Binding RegularList}" />
                    <Setter Property="ItemTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <TextBlock Text="{Binding}" ContextMenu="{StaticResource myContextMenu}" />
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="4*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <ItemsControl Grid.Row="0" />
        <Button Name="ToggleButton"
                Grid.Row="1"
                Content="Toggle Lock"
                Click="OnToggleLock" />
    </Grid>
</Window>

这是相应的代码隐藏:

using System.Windows;
using System.Windows.Markup;
using System;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class WindowV : Window
    {
        public WindowV()
        {
            InitializeComponent();

            System.Windows.Controls.ContextMenu contextMenu =
                FindResource("myContextMenu") as System.Windows.Controls.ContextMenu;

            NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this as DependencyObject));
            contextMenu.RegisterName("RootElement", this);
        }

        private void OnToggleLock(object sender, RoutedEventArgs e)
        {
            if (((WindowVM)(DataContext)).IsLocked == true)
                ((WindowVM)(DataContext)).IsLocked = false;
            else
                ((WindowVM)(DataContext)).IsLocked = true;
        }
    }
}

安德鲁