Як можна виділити дочірні елементи StackPanel?


187

Дано StackPanel:

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

Який найкращий спосіб розподілити дочірні елементи, щоб між ними були однакові зазори, навіть якщо самі дочірні елементи мають різний розмір? Чи можна це зробити без встановлення властивостей на кожного з окремих дітей?


Дійсно, просто додавання прокладки до окремих предметів, здається, найкращий варіант.
зар

Відповіді:


278

Використовуйте маржу чи прокладку, застосовані до області в контейнері:

<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: Якщо ви хочете повторно використовувати маржу між двома контейнерами, ви можете перетворити значення маржі в ресурс у зовнішній області, fe

<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
Стиль, на якому входить рамки, - це дивовижний спосіб зробити це - дякую за пораду!
Ана Беттс

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>

Це найпростіший та найшвидший спосіб встановити Margin для кількох дітей панелі, навіть якщо вони не одного типу. (Тобто кнопки, текстові скриньки, комбобокси тощо)


5
Це досить цікавий шлях для цього. Це робить багато припущень щодо того, як саме ви хочете робити космічні речі, і навіть дає вам можливість автоматично регулювати поля на перший / останній елементи.
Арментаж

2
+1 для універсальності. Також покращити публікацію в блозі, додавши if (fe.ReadLocalValue(FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue)перед фактичним встановленням поля дитини, це дозволяє вручну вказати поля для деяких елементів.
Xerillio

Зауважте, що це не працює, якщо дочірні елементи додаються / видаляються динамічно, як-от у ItemControl, пов'язаний зі зміною колекції.
Дрю Ноакс

1
У випадку, якщо він не працює: Створіть проект, інакше сторінка не відобразиться.
Битва

2
Посилання розірвано!
Дейв

13

Я покращився на відповідь Елада Каца .

  • Додайте властивість 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зв’язаній зі зміною колекції. Він передбачає, що елементи є статичними з моменту 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>

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


Чудова порада, але це краще працює з Style TargetType як "FrameworkElement" (інакше це не працює для Image, наприклад)
Puffin

1
Мені подобається ця ідея. Лише одне доповнення: відняття суми інтервалу від поля StackPanel ( Margin="0 0 -5 0") також буде протистояти проміжку після останнього пункту у списку.
мітка17

Проблема в цьому полягає в тому, що встановлений вами стиль буде заміняти будь-які інші стилі, які у вас, можливо, вже є в елементах. Щоб подолати це, дивіться цей споріднений питання / прийняту відповідь тут
waxingsatirical

6

+1 за відповідь Сергія. І якщо ви хочете застосувати це до всіх своїх StackPanel, ви можете зробити це:

<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.
Андре Луус

Дякую за турботу про ур, але я все ще сумніваюся. Я думаю, це буде <StackPanel.Resources>, а не <Style.Resources>. Буде більш чудово, якщо ви зможете вставити шматочок коду свого ...
grv_9098

3

Виконуючи пропозицію Сергія, ви можете визначити і повторно використовувати цілий Стиль (з різними наборами властивостей, включаючи Margin), а не лише об'єкт Товщина:

<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 негайно вимкнувся з помилкою CATASTROPHIC FAILURE (HRESULT: 0x8000FFFF (E_UNEXPECTED)), як описано на https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails -with-catastrophic-fail-when-a-style-try-to-set-style-property #


3

Grid.ColumnSpacing , Grid.RowSpacing , StackPanel.Spacing тепер переглядаються UWP, все дозволить краще виконати те, що тут запитується.

Наразі ці властивості доступні лише у Windows 10 Fall Creators Update Insider SDK, але вони повинні зробити його остаточними бітами!



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

Іноді вам потрібно встановити Padding, а не Margin, щоб зробити простір між елементами меншим, ніж за замовчуванням

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.