使用模板在WPF中进行递归绑定

时间:2015-03-29 14:39:34

标签: c# wpf binding

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

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中的元素创建模板时递归绑定子元素?

1 个答案:

答案 0 :(得分:1)

这里是一个使用你的ApplicationMenuItem类的递归XAML模板的一个例子,与你定义它完全一样(除了我把它放在名为Wobbles的名称空间中)。这还没有完成,可释放的代码。但它演示了一个递归DataTemplate,以及一些显示弹出窗口的奖励。您可以在菜单项类中添加IsEnabled属性,并在XAML中使用设置颜色的附加触发器以及驱动SubmenuPopup.IsOpen的多重触发器中的附加条件实现该属性。如果要支持水平分隔符,可以添加属性bool ApplicationMenuItem.IsSeparator并为模板提供触发器,当该属性为True时,该触发器将水平线替换为下面的网格内容。

RecursiveTemplate.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和简短的Homily

使用XAML,我建议您尝试使用ObservableCollection<T>代替List<T>。如果在构建UI后集合中的项目发生更改,ObservableCollection<T>将导致UI正确更新。出于同样的原因,您希望ApplicationMenuItem实施INotifyPropertyChanged。我还希望支持ICommand Command属性以及Click事件,并且我还要根据标准进一步命名Click事件Click XAML练习。

&#34; XAML会做什么?&#34;如果您尽最大努力编写可能被误认为与您正在使用的环境一起提供的标准库的代码,那么您几乎不会出错。