水平手风琴控制?

Horizontal accordion control?

我想要一个行为如下的控件:

它必须使用这些确切的控件 (Grid/Expander),而不是一些自定义控件,因此我的应用程序的样式可以自动应用到它们。

我似乎找不到已经制作好的东西,似乎不存在 built-in 解决方案(如果只有 "filling" StackPanel...),这是我能想出的唯一解决方案with 是制作我自己的 Grid 实现,这似乎...令人生畏。

是否有找到或实施此类控件的解决方案?

这是我目前拥有的。它不处理 "single-expanded" 也不处理填充。我真的不知道这应该归咎于 StackPanel 还是 Expander。

<ItemsControl>
    <ItemsControl.Resources>
        <DataTemplate x:Key="verticalHeader">
            <ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Expander}}, Path=Header}" />
        </DataTemplate>
        <Style TargetType="{x:Type Expander}"
               BasedOn="{StaticResource {x:Type Expander}}">
            <Setter Property="HeaderTemplate"
                    Value="{StaticResource verticalHeader}" />
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="HorizontalAlignment" Value="Stretch"/>
            <Setter Property="ExpandDirection"
                    Value="Right" />
        </Style>
    </ItemsControl.Resources>
    <ItemsControl.Template>
        <ControlTemplate>
            <!-- Damn you, StackPanel! -->
            <StackPanel Orientation="Horizontal" IsItemsHost="True"/>
        </ControlTemplate>
    </ItemsControl.Template>
    <Expander Header="Exp1">
        <TextBlock Text="111111111" Background="Red"/>
    </Expander>
    <Expander Header="Exp2">
        <TextBlock Text="222222222" Background="Blue"/>
    </Expander>
    <Expander Header="Exp3">
        <TextBlock Text="333333333" Background="Green"/>
    </Expander>
</ItemsControl>

我的第一个想法是用行为来执行这种动作。您可以将此功能添加到现有的 XAML 控件中,从而为您提供一些额外的自定义设置。

我只是在寻找不使用 ItemsSource 的东西,因为我使用的是带列的网格等。但是在普通网格中,您可以添加一个行为来监听它的子级展开和折叠事件,例如这个:

public class ExpanderBehavior : Behavior<Grid>
{
    private List<Expander> childExpanders = new List<Expander>();

    protected override void OnAttached()
    {
        //since we are accessing it's children, we have to wait until initialise is complete for it's children to be added
        AssociatedObject.Initialized += (gridOvject, e) =>
        {
            foreach (Expander expander in AssociatedObject.Children)
            {
                //store this so we can quickly contract other expanders (though we could just access Children again)
                childExpanders.Add(expander);

                //track expanded events
                expander.Expanded += (expanderObject, e2) =>
                {
                    //contract all other expanders
                    foreach (Expander otherExpander in childExpanders)
                    {
                        if (expander != otherExpander && otherExpander.IsExpanded)
                        {
                            otherExpander.IsExpanded = false;
                        }
                    }

                    //set width to star for the correct column
                    int index = Grid.GetColum(expanderObject as Expander);

                    AssociatedObject.ColumnDefinitions[index].Width = new GridLength(1, GridUnitType.Star);
                };

                //track Collapsed events
                expander.Collapsed += (o2, e2) =>
                {
                    //reset all to auto
                    foreach (ColumnDefinition colDef in AssociatedObject.ColumnDefinitions)
                    {
                        colDef.Width = GridLength.Auto;
                    }
                };
            }
        };
    }
}

像这样使用它,注意你必须添加System.Windows.Interactivity作为对你项目的引用:

<Window ...
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
        xmlns:local="...">
    <Window.Resources>
        <DataTemplate x:Key="verticalHeader">
            <ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Expander}}, Path=Header}" />
        </DataTemplate>
        <Style TargetType="{x:Type Expander}"
               BasedOn="{StaticResource {x:Type Expander}}">
            <Setter Property="HeaderTemplate"
                    Value="{StaticResource verticalHeader}" />
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="HorizontalAlignment" Value="Stretch"/>
            <Setter Property="ExpandDirection"
                    Value="Right" />
        </Style>

        <local:ExpanderBehavior x:Key="ExpanderBehavor"/>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <i:Interaction.Behaviors>
            <local:ExpanderBehavior/>
        </i:Interaction.Behaviors>

        <Expander Header="Exp1">
            <TextBlock Text="111111111" Background="Red"/>
        </Expander>
        <Expander Header="Exp2" Grid.Column="1">
            <TextBlock Text="222222222" Background="Blue"/>
        </Expander>
        <Expander Header="Exp3" Grid.Column="2">
            <TextBlock Text="333333333" Background="Green"/>
        </Expander>
    </Grid>
</Window>

最终结果:


编辑:使用 ItemsControl - 将其添加到承载项目的网格中,并添加一点来管理列映射

public class ItemsSourceExpanderBehavior : Behavior<Grid>
{
    private List<Expander> childExpanders = new List<Expander>();

    protected override void OnAttached()
    {
        AssociatedObject.Initialized += (gridOvject, e) =>
        {
            //since we are accessing it's children, we have to wait until initialise is complete for it's children to be added
            for (int i = 0; i < AssociatedObject.Children.Count; i++)
            {
                Expander expander = AssociatedObject.Children[i] as Expander;

                //sort out the grid columns
                AssociatedObject.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
                Grid.SetColumn(expander, i);

                childExpanders.Add(expander);

                //track expanded events
                expander.Expanded += (expanderObject, e2) =>
                {
                    foreach (Expander otherExpander in childExpanders)
                    {
                        if (expander != otherExpander && otherExpander.IsExpanded)
                        {
                            otherExpander.IsExpanded = false;
                        }
                    }

                    //set width to auto
                    int index = AssociatedObject.Children.IndexOf(expanderObject as Expander);

                    AssociatedObject.ColumnDefinitions[index].Width = new GridLength(1, GridUnitType.Star);
                };

                //track Collapsed events
                expander.Collapsed += (o2, e2) =>
                {
                    foreach (ColumnDefinition colDef in AssociatedObject.ColumnDefinitions)
                    {
                        colDef.Width = GridLength.Auto;
                    }
                };
            }
        };
    }
}

已用:

<ItemsControl>
    <ItemsControl.Template>
        <ControlTemplate>
            <Grid IsItemsHost="True">
                <i:Interaction.Behaviors>
                    <local:ItemsSourceExpanderBehavior/>
                </i:Interaction.Behaviors>
            </Grid>
        </ControlTemplate>
    </ItemsControl.Template>
    <Expander Header="Exp1">
        <TextBlock Text="111111111" Background="Red"/>
    </Expander>
    <Expander Header="Exp2">
        <TextBlock Text="222222222" Background="Blue"/>
    </Expander>
    <Expander Header="Exp3">
        <TextBlock Text="333333333" Background="Green"/>
    </Expander>
</ItemsControl>

请注意,如果您对 ItemsSource 有任何更改,则必须添加一些逻辑来管理 new/removed 个子项!