Горизонтальное управление аккордеоном?

Я хочу элемент управления, поведение которого выглядит следующим образом:

  • Действовать как сетка
  • Каждый дочерний элемент управления встроен в горизонтальный Expander (заголовок которого связан со свойством Tag элемента управления)
  • Каждый из этих Expander имеет свое собственное ColumnDefinition
  • Только один из этих расширителей может быть расширен за один раз
  • ColumnDefinition нерасширенных расширителей имеет ширину, установленную на Auto
  • Один из расширенных расширителей - * (Звезда)

Он должен использовать именно эти элементы управления (Grid/Expander), а не некоторые пользовательские, поэтому стиль моего приложения может автоматически применяться к ним.

Кажется, я не могу найти что-то уже сделанное, кажется, что не существует встроенного решения (если бы только была "заполняющая" StackPanel...), и единственное решение, которое я могу придумать, - это сделать мою собственную реализацию Grid, что кажется... пугающим

Есть ли решение, чтобы найти или реализовать такой контроль?

Вот что у меня сейчас. Он не обрабатывает ни "однораспакованный", ни наполнитель. Я действительно не знаю, виноваты ли в этом 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>

1 ответ

Решение

Моя первая мысль - выполнить такое действие с поведением. Это некоторые функциональные возможности, которые вы можете добавить к существующим элементам управления XAML, что дает вам некоторые дополнительные настройки.

Я смотрел на это только для чего-то, что не использует ItemsSource, так как я использовал Grid со столбцами и т. Д. Но в простой сетке вы можете добавить поведение, которое прослушивает его события Expanded и Collapsed для детей, например:

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!

Другие вопросы по тегам