Como espaço os elementos filho de um StackPanel?

187

Dado um StackPanel:

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

Qual é a melhor maneira de espaçar os elementos filhos para que haja intervalos de tamanho igual entre eles, mesmo que os elementos filhos sejam de tamanhos diferentes? Isso pode ser feito sem definir propriedades em cada um dos filhos individuais?

GraemeF
fonte
Realmente apenas adicionar preenchimento a itens individuais parece ser a melhor opção.
zar

Respostas:

278

Use Margem ou Preenchimento, aplicado ao escopo no contêiner:

<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: Caso deseje reutilizar a margem entre dois contêineres, é possível converter o valor da margem em um recurso em um escopo externo, fe

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

e, em seguida, consulte esse valor no escopo interno

<StackPanel.Resources>
    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Margin" Value="{StaticResource tbMargin}"/>
    </Style>
</StackPanel.Resources>
Sergey Aldoukhov
fonte
5
O estilo com escopo é uma maneira incrível de fazer isso - obrigado pela dica!
Ana Betts
2
E se eu quiser usá-lo para todo o projeto?
grv_9098
10
Alguém pode explicar por que isso só funciona quando você define explicitamente o tipo (por exemplo, TextBox)? Se eu tentar usar o FrameworkElement para que todos os filhos sejam espaçados, isso não terá efeito.
precisa saber é o seguinte
4
Isso não funciona bem se você já definiu um estilo para Button.
Mark Ingram
1
Como uma nota lateral, se você quiser fazer isso com um Labelvocê tem que usar Paddingem vez deMargin
Anthony Nichols
84

Outra boa abordagem pode ser vista aqui: http://blogs.microsoft.co.il/blogs/eladkatz/archive/2011/05/29/what-is-the-easiest-way-to-set-spacing-between- items-in-stackpanel.aspx O link está quebrado -> é o arquivo da web deste link.

Ele mostra como criar um comportamento anexado, para que uma sintaxe como esta funcione:

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

Essa é a maneira mais fácil e rápida de definir Margem para vários filhos de um painel, mesmo que não sejam do mesmo tipo. (Ou seja, botões, caixas de texto, caixas de combinação, etc.)

Elad Katz
fonte
5
Esta é uma maneira bastante interessante de fazer isso. Faz muitas suposições sobre exatamente como você deseja espaçar as coisas e até oferece a oportunidade de ajustar automaticamente as margens nos primeiros / últimos itens.
Armentage
2
+1 para versatilidade. Também para melhorar a postagem do blog, adicionando if (fe.ReadLocalValue(FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue)antes de realmente definir a margem da criança, ela permite especificar manualmente as margens de alguns elementos.
Xerillio 11/08/16
Observe que isso não funciona se os itens filhos forem adicionados / removidos dinamicamente, como em um ItemsControl vinculado a uma coleção em mudança.
Drew Noakes #
1
Caso isso não funcione: construa o projeto, caso contrário não renderizará a página.
Batalha
2
O link está quebrado!
Dave
13

Melhorei a resposta de Elad Katz .

  • Adicione a propriedade LastItemMargin a MarginSetter para manipular especialmente o último item
  • Adicionar propriedade anexada Espaçamento com propriedades Vertical e Horizontal que adiciona espaçamento entre itens nas listas verticais e horizontais e elimina qualquer margem à direita no final da lista

Código fonte na essência .

Exemplo:

<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
fonte
Como a resposta de Elad, isso não funciona se os itens filhos forem adicionados / removidos dinamicamente, como em um ItemsControlvinculado a uma coleção em mudança. Ele assume que os itens são estáticos a partir do momento em que o Loadevento do pai é acionado.
Drew Noakes
Além disso, sua essência dá um 404.
de Drew Noakes
Link atualizado para o gist.
Angularsen 9/10/19
9

O que você realmente deseja fazer é agrupar todos os elementos filhos. Nesse caso, você deve usar um controle de itens e não recorrer a propriedades anexas horríveis, das quais você acabará tendo um milhão para todas as propriedades que deseja estilizar.

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

insira a descrição da imagem aqui

bradgonesurfing
fonte
Grande dica, mas isso funciona melhor com o TargetType estilo como "FrameworkElement" (caso contrário, ele não funciona para a imagem, por exemplo)
Puffin
1
Eu gosto desta ideia. Apenas uma adição: subtrair a quantidade de espaçamento da margem do StackPanel ( Margin="0 0 -5 0") também contraria o espaçamento após o último item da lista.
usar o seguinte código
O problema é que o estilo que você definir substituirá os outros estilos que você já pode ter nos itens. Para superar esta ver esta pergunta / resposta relacionada aceito aqui
waxingsatirical
6

+1 para a resposta de Sergey. E se você quiser aplicar isso a todos os seus StackPanels, poderá fazer o seguinte:

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

Mas cuidado: se você definir um estilo como este no seu App.xaml (ou outro dicionário que seja mesclado no Application.Resources), ele poderá substituir o estilo padrão do controle. Para controles quase sem aparência, como o stackpanel, não é um problema, mas para caixas de texto, etc., você pode se deparar com esse problema , que felizmente tem algumas soluções alternativas.

Andre Luus
fonte
<Style.Resources> Você tem certeza disso porque, quando tentei isso, estava mostrando erro.
grv_9098
Desculpe, não me lembro de imediato. Vou ter que tentar eu mesmo ver. Eu voltarei para você.
Andre Luus
Sim, funciona. Apenas fiz o mesmo para definir TextWeight = "Negrito" em todos os TextBlocks em um StackPanel. A única diferença foi que eu defini o estilo explicitamente no StackPanel.
Andre Luus 30/11/2012
Obrigado pela preocupação, mas ainda tenho dúvidas. Eu sei sobre isso, chamado Estilo de escopo. Eu acho que será <StackPanel.Resources> e não <Style.Resources>. Vai ser mais ótimo se você pode colar o pedaço de código do seu ...
grv_9098
3

Seguindo a sugestão de Sergey, você pode definir e reutilizar um estilo inteiro (com vários configuradores de propriedades, incluindo Margem) em vez de apenas um objeto 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>

Observe que o truque aqui é o uso de Herança de estilo para o estilo implícito, herdado do estilo em algum dicionário de recursos externo (provavelmente mesclado de arquivo XAML externo).

Nota:

Inicialmente, tentei ingenuamente usar o estilo implícito para definir a propriedade Style do controle para esse recurso externo de estilo (digamos, definido com a chave "MyStyle"):

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

que causou o encerramento do Visual Studio 2010 imediatamente com o erro CATASTROPHIC FAILURE (HRESULT: 0x8000FFFF (E_UNEXPECTED)), conforme descrito em https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails -com-falha-catastrófica-quando-um-estilo-tenta-definir-estilo-propriedade #

George Birbilis
fonte
3

Grid.ColumnSpacing , Grid.RowSpacing , StackPanel.Spacing agora estão na visualização UWP, todos permitirão realizar melhor o que é solicitado aqui.

Atualmente, essas propriedades estão disponíveis apenas com o SDK do Windows 10 Fall Creators Update Insider, mas devem chegar aos bits finais!

Pedro Lamas
fonte
2

Minha abordagem herda o StackPanel.

Uso:

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

Tudo o que é necessário é a seguinte turma curta:

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();
        }
    }
}
James M
fonte
0

às vezes você precisa definir Padding, não Margin, para deixar espaço entre os itens menores que o padrão

Danil
fonte