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

учитывая StackPanel:

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

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

10 ответов


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

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

EDIT: если вы хотите повторно использовать поле между двумя контейнерами, вы можете преобразовать значение поля в ресурс во внешней области, f.e.

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

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

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


Я ответ Элада Каца.

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

исходный код в gist.

пример:

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

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

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

enter image description here


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

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

но будьте осторожны: если вы определяете такой стиль в своем приложении.xaml (или другой словарь, объединенный с приложением.Ресурсы) это можете переопределить стиль элемента управления по умолчанию. Для в основном lookless элементов управления, таких как stackpanel, это не проблема, но для текстовых полей и т. д. Вы можете наткнуться на проблема, которым к счастью, есть некоторые обходные пути.


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

<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) словаре ресурсов.

Sidenote:

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

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

что вызвало немедленное завершение работы Visual Studio 2010 с катастрофической ошибкой сбоя (HRESULT: 0x8000FFFF (E_UNEXPECTED)), как описано в https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails-with-catastrophic-failure-when-a-style-tries-to-set-style-property#


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


решетки.Свойства columnspacing, решетки.RowSpacing, StackPanel.Интервал теперь на предварительном просмотре UWP, все позволит лучше выполнить то, что запрашивается здесь.

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


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

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