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

Учитывая StackPanel:

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

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


person GraemeF    schedule 31.05.2009    source источник
comment
На самом деле, просто добавление отступов к отдельным элементам кажется лучшим вариантом.   -  person zar    schedule 25.01.2020


Ответы (11)


Используйте 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>
person Sergey Aldoukhov    schedule 31.05.2009
comment
Стиль с областью видимости - это отличный способ сделать это - спасибо за совет! - person Ana Betts; 31.05.2009
comment
Что, если я захочу использовать его для всего проекта? - person grv_9098; 28.11.2012
comment
Может кто-нибудь объяснить, почему это работает только тогда, когда вы явно определяете тип (например, TextBox)? Если я попробую это с помощью FrameworkElement, чтобы все дочерние элементы были разнесены, это не повлияет. - person Jack Ukleja; 28.01.2014
comment
Это не сработает, если вы уже определили стиль для Button. - person Mark Ingram; 04.02.2015
comment
В качестве примечания: если вы хотите сделать это с Label, вы должны использовать Padding вместо Margin - person Anthony Nichols; 30.09.2015
comment
Я заполнил свою панель стека сетками, а затем установил целевой тип сетки. Моя панель стека имеет Grid.IsSharedSizeScope="True", что также позволяет мне иметь группы общих размеров сетки в моих подсетках. - person xtreampb; 21.05.2018
comment
@Mark Ingram Я полагаю, это из-за специфики? - person Paul; 03.05.2019
comment
Для @Mark Ingram и других людей используйте свойство Style.BaseOn. BaseOn="{StaticResource {x:Type Button}}" может сделать так, чтобы он унаследовал родительский стиль. для получения дополнительной информации: stackoverflow.com/questions/44065383 - person Mr. Squirrel.Downy; 03.09.2019
comment
Что делать, если мне нужно использовать поле для нескольких элементов, таких как Textbox, Button, ..? - person ; 17.01.2020

Еще один хороший подход можно увидеть здесь: 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>

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

person Elad Katz    schedule 29.05.2011
comment
Это довольно интересный способ сделать это. Он делает множество предположений о том, как именно вы хотите разместить элементы, и даже дает вам возможность автоматически настраивать поля для первого / последнего элементов. - person Armentage; 23.06.2011
comment
+1 за универсальность. Также для улучшения сообщения в блоге добавление if (fe.ReadLocalValue(FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue) перед фактической установкой поля дочернего элемента позволяет вручную указывать поля для некоторых элементов. - person Xerillio; 12.08.2016
comment
Обратите внимание, что это не работает, если дочерние элементы добавляются / удаляются динамически, например, в ItemsControl, привязанном к изменяющейся коллекции. - person Drew Noakes; 09.10.2017
comment
Работает, когда панель настроена как ItemsPanelTemplate для элемента управления элементами - person Jonathan H; 31.10.2018
comment
Это выглядит очень элегантно. Есть ли у этого существенные недостатки? - person Paul; 03.05.2019
comment
В случае, если это не сработает: создайте проект, иначе он не отобразит страницу. - person Battle; 03.09.2019
comment
Ссылка не работает! - person Dave; 21.01.2020

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

  • Добавьте свойство LastItemMargin в MarginSetter, чтобы специально обрабатывать последний элемент
  • Присоединенное свойство Add Spacing со свойствами Vertical и Horizontal, которое добавляет интервал между элементами в вертикальном и горизонтальном списках и устраняет любые конечные поля в конце списка

Исходный код в сущности.

Пример:

<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>
person angularsen    schedule 17.01.2015
comment
Как и ответ Элада, это не работает, если дочерние элементы добавляются / удаляются динамически, например, в ItemsControl, привязанном к изменяющейся коллекции. Предполагается, что элементы статичны с момента запуска родительского события Load. - person Drew Noakes; 09.10.2017
comment
Кроме того, ваша суть дает 404. - person Drew Noakes; 09.10.2017
comment
Обновлена ​​ссылка на суть. - person angularsen; 09.10.2017

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

<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>

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

person bradgonesurfing    schedule 16.06.2015
comment
Отличный совет, но это лучше работает со Style TargetType как FrameworkElement (в противном случае он не работает, например, для Image) - person Puffin; 04.02.2016
comment
Мне нравится эта идея. Только одно дополнение: вычитание количества интервала из поля StackPanel (Margin="0 0 -5 0") также приведет к противодействию интервалу после последнего элемента в списке. - person label17; 18.04.2016
comment
Проблема в том, что установленный вами стиль переопределит любые другие стили, которые у вас уже могут быть для Items. Чтобы решить эту проблему, см. Соответствующий вопрос / принятый ответ здесь - person waxingsatirical; 03.06.2016

+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), он может переопределить стиль по умолчанию для контроль. Для почти не выглядящих элементов управления, таких как панель стека, это не проблема, но для текстовых полей и т. Д. Вы можете наткнуться на эта проблема, у которой, к счастью, есть обходные пути.

person Andre Luus    schedule 23.06.2010
comment
‹Style.Resources› Вы уверены в этом, потому что когда я попробовал это, он показывал ошибку. - person grv_9098; 29.11.2012
comment
Извините, не припомню насквозь. Придется попробовать самому, чтобы увидеть. Я вернусь к вам. - person Andre Luus; 29.11.2012
comment
Да, работает. Сделайте то же самое, чтобы установить TextWeight = Bold для всех текстовых блоков в StackPanel. Единственная разница в том, что я явно установил стиль в StackPanel. - person Andre Luus; 30.11.2012
comment
Спасибо за беспокойство, но я все еще сомневаюсь. Я знаю, что это называется Scope Style. Думаю, это будет ‹StackPanel.Resources›, а не ‹Style.Resources›. Будет лучше, если вы сможете вставить фрагмент кода своего ... - person grv_9098; 30.11.2012

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

<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-редактор-окно-сбой-с-катастрофической-неудачей-когда-стиль-пытается-установить-свойство-стиля#

person George Birbilis    schedule 11.07.2012

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

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

person Pedro Lamas    schedule 06.07.2017

UniformGrid может быть недоступен в Silverlight, но кто-то перенес его из WPF. http://www.jeff.wilcox.name/2009/01/uniform-grid/

person Jean Azzopardi    schedule 31.05.2009

Мой подход наследует 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();
        }
    }
}
person Community    schedule 16.04.2016

Обычно я использую Grid вместо StackPanel вот так:

горизонтальный корпус

<Grid>
 <Grid.ColumnDefinitions>
    <ColumnDefinition Width="auto"/>
    <ColumnDefinition Width="*"/>
    <ColumnDefinition  Width="auto"/>
    <ColumnDefinition Width="*"/>
    <ColumnDefinition  Width="auto"/>
 </Grid.ColumnDefinitions>
 <TextBox Height="30" Grid.Column="0">Apple</TextBox>
 <TextBox Height="80" Grid.Column="2">Banana</TextBox>
 <TextBox Height="120" Grid.Column="4">Cherry</TextBox>
</Grid>

вертикальный корпус

<Grid>
     <Grid.ColumnDefinitions>
        <RowDefinition Width="auto"/>
        <RowDefinition Width="*"/>
        <RowDefinition  Width="auto"/>
        <RowDefinition Width="*"/>
        <RowDefinition  Width="auto"/>
     </Grid.ColumnDefinitions>
     <TextBox Height="30" Grid.Row="0">Apple</TextBox>
     <TextBox Height="80" Grid.Row="2">Banana</TextBox>
     <TextBox Height="120" Grid.Row="4">Cherry</TextBox>
</Grid>
person farouk osama    schedule 30.08.2020

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

person Danil    schedule 25.11.2013