【问题标题】:Recursive binding in WPF with templatesWPF中的递归绑定与模板
【发布时间】:2015-03-29 14:39:33
【问题描述】:

所以我有一个自定义类,旨在包含格式如下的菜单项和子菜单项:

public class ApplicationMenuItem
{
    public ImageSource Image { get; set; }
    public string Text { get; set; }
    public string Tooltip { get; set; }
    public ICollection<ApplicationMenuItem> Items { get; set; }
    public EventHandler Clicked {get;set;}
    public void Click(object sender, EventArgs e)
    {
        if (Clicked!=null)
            Clicked(this, e);
    }
    public ApplicationMenuItem(string Text)
    {
        this.Text = Text;
        Items = new List<ApplicationMenuItem>();
    }
    public ApplicationMenuItem()
    {
        Items = new List<ApplicationMenuItem>();
    }

}

在有人问我为什么不继承Menu 或者只是创建一个Menu 对象并绑定它之前,这是因为此类可能用于不一定使用Menu UI 对象的平台和框架,更不用说这个类会驱动导航菜单、上下文菜单、侧边栏、工具栏等......

我的问题是如您所见,我有一个自引用列表Items 包含在其中以允许子菜单;绑定一级菜单元素很简单,但是如何在 WPF 中为其元素创建模板时递归绑定子元素?

【问题讨论】:

  • 由于 XAML 本身没有,AFAIK,任何递归构造,递归行为必须在代码隐藏中实现。不幸的是,如果没有 a good, minimal, complete code example 准确显示您要完成的工作,很难知道这里的最佳答案是什么。
  • 什么意思?我把上面的代码贴出来了。
  • 请阅读我在之前评论中提供的链接,以了解一个好的、最小的、完整的代码示例的含义。
  • 我觉得你实际上并没有阅读这个问题。以上是我的整个班级。除此之外,即绑定,是我寻求帮助的内容......没有其他代码......
  • 您将为ApplicationMenuItem 定义一个DataTemplate。使用模板触发器,如果​​它没有子项,您可以显示常规菜单内容,或者如果它是其中之一,则将其显示为具有相关菜单弹出窗口的子菜单项。那是递归的。它只是带有ItemsControls 的弹出窗口,但您可以从现有的菜单模板中窃取:msdn.microsoft.com/en-us/library/ms747082(v=vs.85).aspxmsdn.microsoft.com/en-us/library/ms752296(v=vs.110).aspx

标签: c# wpf binding


【解决方案1】:

这是一个递归 XAML 模板的示例,它使用您的 ApplicationMenuItem 类,完全按照您的定义(除了我将它放在名为 Wobbles 的命名空间中)。这还没有完成,可发布的代码。但它展示了一个递归的DataTemplate,以及一些额外的好处,比如显示弹出窗口。您可以将IsEnabled 属性添加到您的菜单项类,并在XAML 中使用设置颜色的附加触发器和驱动SubmenuPopup.IsOpen 的多触发器中的附加条件来实现它。如果你想支持水平分隔符,你可以添加一个属性bool ApplicationMenuItem.IsSeparator 并给模板一个触发器,当该属性为True 时,它会用水平线替换下面的网格内容。

递归模板.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wbl="clr-namespace:Wobbles"
    >

    <DataTemplate DataType="{x:Type wbl:ApplicationMenuItem}">
        <Grid
            Name="RootGrid"
            Background="BlanchedAlmond"
            Height="Auto"
            UseLayoutRounding="True"
            SnapsToDevicePixels="True"
            >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="24" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="24" />
            </Grid.ColumnDefinitions>

            <Image
                Grid.Column="0"
                Source="{Binding Image}"
                />
            <Label
                Grid.Column="1"
                Content="{Binding Text}"
                />
            <Border
                Name="PopupGlyphBorder"
                Grid.Column="2"
                VerticalAlignment="Stretch"
                HorizontalAlignment="Stretch"
                Background="{Binding ElementName=RootGrid, Path=Background}"
                >
                <Path
                    Height="10"
                    Width="5"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center"
                    Data="M 0,0 L 5,5 L 0,10 Z"
                    Fill="Black"
                    />
            </Border>

            <Popup
                Name="SubmenuPopup"
                PlacementTarget="{Binding ElementName=PopupGlyphBorder}"
                Placement="Right"
                StaysOpen="True"
                >
                <Border
                    BorderBrush="DarkGoldenrod"
                    BorderThickness="1"
                    >
                    <ItemsControl
                        Name="SubmenuItems"
                        ItemsSource="{Binding Items}"
                        />
                </Border>
            </Popup>
        </Grid>
        <DataTemplate.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter TargetName="RootGrid" Property="Background" Value="Wheat" />
            </Trigger>

            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsMouseOver" Value="True" />
                    <Condition SourceName="SubmenuItems" Property="HasItems" Value="True" />
                </MultiTrigger.Conditions>
                <Setter TargetName="SubmenuPopup" Property="IsOpen" Value="True" />
            </MultiTrigger>

            <Trigger SourceName="SubmenuItems" Property="HasItems" Value="False">
                <Setter TargetName="PopupGlyphBorder" Property="Visibility" Value="Hidden" />
            </Trigger>
        </DataTemplate.Triggers>
    </DataTemplate>

</ResourceDictionary>

MainWindow.xaml

<Window
    x:Class="RecursiveTemplate.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wbl="clr-namespace:Wobbles"
    Title="MainWindow" 
    Height="350" 
    Width="525"
    >

    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="RecursiveTemplate.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <Window.DataContext>
        <wbl:TestViewModel />
    </Window.DataContext>

    <Grid>
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <ContentControl
                    Content="{Binding Menu}"
                    Width="100"
                    Height="24"
                    />
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

ViewModel.cs

namespace Wobbles
{
    public class TestViewModel
    {
        public TestViewModel()
        {
            Menu = CreateMenu();
        }

        public Wobbles.ApplicationMenuItem Menu { get; protected set; }

        protected Wobbles.ApplicationMenuItem CreateMenu()
        {
            var m = new Wobbles.ApplicationMenuItem("Menu");

            var msub = new Wobbles.ApplicationMenuItem("Submenu");
            msub.Items.Add(new Wobbles.ApplicationMenuItem("Sub Sub 1"));
            msub.Items.Add(new Wobbles.ApplicationMenuItem("Sub Sub 2"));
            //  LOL
            msub.Items.Add(msub);

            m.Items.Add(msub);

            m.Items.Add(new Wobbles.ApplicationMenuItem("Foo"));
            m.Items.Add(new Wobbles.ApplicationMenuItem("Bar"));
            m.Items.Add(new Wobbles.ApplicationMenuItem("Baz"));

            return m;
        }
    }
}

Nits、Cavils、Kvetches 和简短的讲道

使用 XAML,我建议练习使用 ObservableCollection&lt;T&gt; 而不是 List&lt;T&gt;。如果 UI 构建后集合中的项目发生变化,ObservableCollection&lt;T&gt; 将导致 UI 相应更新。出于同样的原因,您将希望 ApplicationMenuItem 实现 INotifyPropertyChanged。我还希望支持ICommand Command 属性以及Click 事件,并且我会根据标准XAML 实践进一步将Click 事件命名为Click

“XAML 会做什么?”如果您尽最大努力编写可能被误认为是您正在工作的环境中附带的标准库的代码,那么您几乎永远不会出错。

【讨论】:

  • 太棒了,我没有想到我可以分配一个模板,然后将它分配给子项目——同时让它递归!我的主列表在一个可观察的集合中,我没有想到子项目也应该如此。感谢您非常彻底的回答。
  • 没有问题。顺便说一句,我刚刚在模板中修复了几件事:Image.SourcePopup.PlacementTarget 的绑定都被破坏了,我忘记了 RootGrid 上的 UseLayoutRoundingSnapsToDevicePixels
猜你喜欢
  • 2010-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-25
  • 2014-08-23
相关资源
最近更新 更多