Как разнести дочерние элементы StackPanel?

187

Учитывая StackPanel:

<StackPanel>
  <TextBox Height="30">Apple</TextBox>
  <TextBox Height="80">Banana</TextBox>
  <TextBox Height="120">Cherry</TextBox>
</StackPanel>

Каков наилучший способ разнести дочерние элементы так, чтобы между ними были зазоры одинакового размера, даже если сами дочерние элементы имеют разные размеры? Можно ли это сделать, не устанавливая свойства для каждого из дочерних элементов?

GraemeF
источник
Действительно, просто добавление отступов к отдельным элементам кажется наилучшим вариантом.
Зар

Ответы:

278

Используйте Margin или Padding, примененные к области внутри контейнера:

<StackPanel>
    <StackPanel.Resources>
        <Style TargetType="{x:Type TextBox}">
            <Setter Property="Margin" Value="0,10,0,0"/>
        </Style>
    </StackPanel.Resources> 
    <TextBox Text="Apple"/>
    <TextBox Text="Banana"/>
    <TextBox Text="Cherry"/>
</StackPanel>

РЕДАКТИРОВАТЬ: Если вы хотите повторно использовать поле между двумя контейнерами, вы можете преобразовать значение поля в ресурс во внешней области, например

<Window.Resources>
    <Thickness x:Key="tbMargin">0,10,0,0</Thickness>
</Window.Resources>

а затем сослаться на это значение во внутренней области видимости

<StackPanel.Resources>
    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Margin" Value="{StaticResource tbMargin}"/>
    </Style>
</StackPanel.Resources>
Сергей Алдоухов
источник
5
Scoped Style - отличный способ сделать это - спасибо за подсказку!
Ана Беттс
2
Что делать, если я хочу использовать его для всего проекта?
grv_9098
10
Может кто-нибудь объяснить, почему это работает, только когда вы явно определяете тип (например, TextBox)? Если я попробую это с помощью FrameworkElement, чтобы все дочерние объекты были разнесены, это не окажет никакого влияния.
Джек Уклея
4
Это не работает, если вы уже определили стиль для Button.
Марк Ингрэм
1
В качестве дополнительного примечания: если вы хотите сделать это с, Labelвы должны использовать PaddingвместоMargin
Энтони Николс
84

Другой хороший подход можно увидеть здесь: http://blogs.microsoft.co.il/blogs/eladkatz/archive/2011/05/29/what-is-the-easiest-way-to-set-spacing-between- items-in-stackpanel.aspx Ссылка не работает -> это веб- архив этой ссылки.

Он показывает, как создать прикрепленное поведение, чтобы такой синтаксис работал:

<StackPanel local:MarginSetter.Margin="5">
   <TextBox Text="hello" />
   <Button Content="hello" />
   <Button Content="hello" />
</StackPanel>

Это самый простой и быстрый способ установить маржу для нескольких дочерних элементов панели, даже если они не одного типа. (Т.е. кнопки, текстовые поля, комбинированные списки и т. Д.)

Элад Кац
источник
5
Это довольно интересный способ сделать это. Он делает много предположений о том, как именно вы хотите разместить вещи, и даже дает вам возможность автоматически корректировать поля для первых / последних элементов.
Арментаж
2
+1 за универсальность. Также для улучшения поста в блоге добавление if (fe.ReadLocalValue(FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue)перед фактической установкой поля дочернего элемента позволяет вручную указывать поля для некоторых элементов.
Ксериллио
Обратите внимание, что это не работает, если дочерние элементы добавляются / удаляются динамически, например, в ItemsControl, связанном с изменяющейся коллекцией.
Дрю Ноакс
1
Если это не сработает: создайте проект, иначе он не будет отображать страницу.
Битва
2
Ссылка не работает!
Дэйв
13

Я улучшил ответ Элада Каца .

  • Добавьте свойство LastItemMargin в MarginSetter для специальной обработки последнего элемента.
  • Добавить вложенное свойство «Интервал» со свойствами «Вертикальный» и «Горизонтальный», которое добавляет интервалы между элементами в вертикальных и горизонтальных списках и устраняет любые конечные поля в конце списка

Исходный код в гисте .

Пример:

<StackPanel Orientation="Horizontal" foo:Spacing.Horizontal="5">
  <Button>Button 1</Button>
  <Button>Button 2</Button>
</StackPanel>

<StackPanel Orientation="Vertical" foo:Spacing.Vertical="5">
  <Button>Button 1</Button>
  <Button>Button 2</Button>
</StackPanel>

<!-- Same as vertical example above -->
<StackPanel Orientation="Vertical" foo:MarginSetter.Margin="0 0 0 5" foo:MarginSetter.LastItemMargin="0">
  <Button>Button 1</Button>
  <Button>Button 2</Button>
</StackPanel>
angularsen
источник
Как и ответ Elad, это не работает, если дочерние элементы добавляются / удаляются динамически, например, в ItemsControlпривязке к изменяющейся коллекции. Предполагается, что элементы статичны с момента возникновения Loadсобытия родителя .
Дрю Ноакс
Кроме того, ваша суть дает 404.
Дрю Ноакс
Обновлена ​​ссылка на суть.
angularsen
9

То, что вы действительно хотите сделать, это обернуть все дочерние элементы. В этом случае вы должны использовать элемент управления элементами и не прибегать к ужасным прикрепленным свойствам, которые в конечном итоге будут иметь миллион на каждое свойство, которое вы хотите стилизовать.

<ItemsControl>

    <!-- target the wrapper parent of the child with a style -->
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="Control">
            <Setter Property="Margin" Value="0 0 5 0"></Setter>
        </Style>
    </ItemsControl.ItemContainerStyle>

    <!-- use a stack panel as the main container -->
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <!-- put in your children -->
    <ItemsControl.Items>
        <Label>Auto Zoom Reset?</Label>
        <CheckBox x:Name="AutoResetZoom"/>
        <Button x:Name="ProceedButton" Click="ProceedButton_OnClick">Next</Button>
        <ComboBox SelectedItem="{Binding LogLevel }" ItemsSource="{Binding LogLevels}" />
    </ItemsControl.Items>
</ItemsControl>

введите описание изображения здесь

bradgonesurfing
источник
Отличный совет, но он лучше работает с Style TargetType как «FrameworkElement» (в противном случае, например, он не работает для Image)
Puffin
1
Мне нравится эта идея. Только одно добавление: вычитание количества интервалов из поля StackPanel ( Margin="0 0 -5 0") также будет противодействовать интервалу после последнего элемента в списке.
label17
Проблема в том, что установленный вами стиль будет переопределять любые другие стили, которые вы, возможно, уже использовали для элементов. Чтобы преодолеть это, смотрите этот связанный вопрос / принятый ответ здесь
waxingsatirical
6

+1 за ответ Сергея. И если вы хотите применить это ко всем вашим StackPanels, вы можете сделать это:

<Style TargetType="{x:Type StackPanel}">
    <Style.Resources>
        <Style TargetType="{x:Type TextBox}">
            <Setter Property="Margin" Value="{StaticResource tbMargin}"/>
        </Style>
    </Style.Resources>
</Style>

Но будьте осторожны: если вы определите такой стиль в своем App.xaml (или другом словаре, который объединен с Application.Resources), он может переопределить стиль элемента управления по умолчанию. Для большинства элементов управления без внешнего вида, таких как панель стека, это не проблема, но для текстовых полей и т. Д. Вы можете столкнуться с этой проблемой , которая, к счастью, имеет некоторые обходные пути.

Андре Луус
источник
<Style.Resources> Вы уверены в этом, потому что, когда я попробовал это, он показывал ошибку.
grv_9098
Извините, я не могу вспомнить оф-руки. Я должен попробовать это сам, чтобы увидеть. Я вернусь к вам.
Андре Луус
Да, это работает. Просто сделал то же самое, чтобы установить TextWeight = "Bold" для всех TextBlocks в StackPanel. Единственная разница заключалась в том, что я установил стиль явно на StackPanel.
Андре Луус
Спасибо за беспокойство, но у меня все еще есть сомнения. Я знаю об этом. Он называется Scope Style. Я предполагаю, что это будет <StackPanel.Resources>, а не <Style.Resources>. Будет лучше, если вы сможете вставить часть кода вашего ...
grv_9098
3

Следуя совету Сергея, вы можете определить и повторно использовать весь Стиль (с различными установщиками свойств, включая Маржу) вместо просто объекта Толщина:

<Style x:Key="MyStyle" TargetType="SomeItemType">
  <Setter Property="Margin" Value="0,5,0,5" />
  ...
</Style>

...

  <StackPanel>
    <StackPanel.Resources>
      <Style TargetType="SomeItemType" BasedOn="{StaticResource MyStyle}" />
    </StackPanel.Resources>
  ...
  </StackPanel>

Обратите внимание, что хитрость здесь заключается в использовании наследования стилей для неявного стиля, унаследованного от стиля в некотором внешнем (возможно, объединенном из внешнего файла XAML) словаре ресурсов.

Примечание:

Сначала я наивно пытался использовать неявный стиль, чтобы установить свойство Style элемента управления для этого внешнего ресурса Style (скажем, определенного с помощью ключа «MyStyle»):

<StackPanel>
  <StackPanel.Resources>
    <Style TargetType="SomeItemType">
      <Setter Property="Style" Value={StaticResource MyStyle}" />
    </Style>
  </StackPanel.Resources>
</StackPanel>

что привело к немедленному завершению работы Visual Studio 2010 с ошибкой CATASTROPHIC FAILURE (HRESULT: 0x8000FFFF (E_UNEXPECTED)), как описано на странице https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails -с-катастрофический безотказный-когда-а-стиль-нах к набору стиль-свойство #

Джордж Бирбилис
источник
3

Grid.ColumnSpacing , Grid.RowSpacing , StackPanel.Spacing теперь находятся на предварительном просмотре UWP, все позволит лучше соответствовать тому, что запрашивается здесь.

Эти свойства в настоящее время доступны только в пакете обновления 10 для Windows 10 Fall Creators Update Insider, но они должны быть в полной мере!

Педро Ламас
источник
2

Мой подход наследует StackPanel.

Использование:

<Controls:ItemSpacer Grid.Row="2" Orientation="Horizontal" Height="30" CellPadding="15,0">
    <Label>Test 1</Label>
    <Label>Test 2</Label>
    <Label>Test 3</Label>
</Controls:ItemSpacer>

Все, что нужно, это следующий короткий класс:

using System.Windows;
using System.Windows.Controls;
using System;

namespace Controls
{
    public class ItemSpacer : StackPanel
    {
        public static DependencyProperty CellPaddingProperty = DependencyProperty.Register("CellPadding", typeof(Thickness), typeof(ItemSpacer), new FrameworkPropertyMetadata(default(Thickness), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnCellPaddingChanged));
        public Thickness CellPadding
        {
            get
            {
                return (Thickness)GetValue(CellPaddingProperty);
            }
            set
            {
                SetValue(CellPaddingProperty, value);
            }
        }
        private static void OnCellPaddingChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
        {
            ((ItemSpacer)Object).SetPadding();
        }

        private void SetPadding()
        {
            foreach (UIElement Element in Children)
            {
                (Element as FrameworkElement).Margin = this.CellPadding;
            }
        }

        public ItemSpacer()
        {
            this.LayoutUpdated += PART_Host_LayoutUpdated;
        }

        private void PART_Host_LayoutUpdated(object sender, System.EventArgs e)
        {
            this.SetPadding();
        }
    }
}
Джеймс М
источник
0

иногда вам нужно установить отступы, а не поля, чтобы пространство между элементами было меньше, чем по умолчанию

Данил
источник