¿Cómo separo los elementos secundarios de un StackPanel?

187

Dado un StackPanel:

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

¿Cuál es la mejor manera de espaciar los elementos secundarios para que haya espacios de igual tamaño entre ellos, a pesar de que los elementos secundarios en sí son de diferentes tamaños? ¿Se puede hacer sin establecer propiedades en cada uno de los hijos individuales?

GraemeF
fuente
Realmente solo agregar relleno a elementos individuales parece ser la mejor opción.
zar

Respuestas:

278

Use Margen o Relleno, aplicado al alcance dentro del contenedor:

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

EDITAR: en caso de que desee reutilizar el margen entre dos contenedores, puede convertir el valor del margen en un recurso en un ámbito externo, por ejemplo

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

y luego se refieren a este valor en el ámbito interno

<StackPanel.Resources>
    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Margin" Value="{StaticResource tbMargin}"/>
    </Style>
</StackPanel.Resources>
Sergey Aldoukhov
fuente
55
El estilo con alcance es una forma increíble de hacerlo, ¡gracias por el consejo!
Ana Betts
2
¿Qué pasa si quiero usarlo para todo el proyecto?
grv_9098
10
¿Alguien puede explicar por qué esto solo funciona cuando define explícitamente el tipo (por ejemplo, TextBox)? Si intento esto usando FrameworkElement para que todos los niños estén espaciados, no tiene efecto.
Jack Ukleja
44
Esto no funciona bien si ya ha definido un estilo para Button.
Mark Ingram
1
Como nota al margen, si desea hacer esto con un Labelque tiene que usar en Paddinglugar deMargin
Anthony Nichols
84

Otro buen enfoque se puede ver aquí: http://blogs.microsoft.co.il/blogs/eladkatz/archive/2011/05/29/what-is-the-easiest-way-to-set-spacing-between- items-in-stackpanel.aspx El enlace está roto -> este es el archivo web de este enlace.

Muestra cómo crear un comportamiento adjunto, para que una sintaxis como esta funcione:

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

Esta es la forma más fácil y rápida de configurar Margen para varios elementos secundarios de un panel, incluso si no son del mismo tipo. (Es decir, botones, cuadros de texto, cuadros combinados, etc.)

Elad Katz
fuente
55
Esta es una forma bastante interesante de hacerlo. Hace muchas suposiciones sobre exactamente cómo desea espaciar las cosas, e incluso le da la oportunidad de ajustar automáticamente los márgenes en el primer / último elemento.
Armentage
2
+1 para versatilidad. Además, para mejorar la publicación del blog, agregar if (fe.ReadLocalValue(FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue)antes de configurar realmente el margen del elemento secundario permite especificar manualmente los márgenes para algunos elementos.
Xerillio
Tenga en cuenta que esto no funciona si los elementos secundarios se agregan / eliminan dinámicamente, como en un ItemsControl vinculado a una colección cambiante.
Drew Noakes
1
En caso de que no funcione: cree el proyecto, de lo contrario no mostrará la página.
Batalla el
2
¡El enlace está roto!
Dave
13

Creo que he mejorado en respuesta Elad Katz .

  • Agregue la propiedad LastItemMargin a MarginSetter para manejar especialmente el último elemento
  • Agregar propiedad adjunta espacio con propiedades verticales y horizontales que agrega espacio entre elementos en listas verticales y horizontales y elimina cualquier margen final al final de la lista

Código fuente en gist .

Ejemplo:

<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
fuente
Al igual que la respuesta de Elad, esto no funciona si los elementos secundarios se agregan / eliminan dinámicamente, como en un ItemsControlenlace a una colección cambiante. Se supone que los elementos son estáticos desde el momento en que se desencadena el Loadevento principal.
Drew Noakes
Además, su esencia da un 404.
Drew Noakes
Enlace actualizado a la esencia.
angularsen
9

Lo que realmente quieres hacer es envolver todos los elementos secundarios. En este caso, debe usar un control de elementos y no recurrir a propiedades adjuntas horribles que terminará teniendo un millón de por cada propiedad que desee diseñar.

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

ingrese la descripción de la imagen aquí

bradgonesurfing
fuente
Gran consejo, pero funciona mejor con Style TargetType como "FrameworkElement" (de lo contrario, no funciona para Image, por ejemplo)
Puffin
1
Me gusta esta idea. Solo una adición: restar la cantidad de espacio del margen del StackPanel ( Margin="0 0 -5 0") también contrarrestará el espacio después del último elemento de la lista.
label17
El problema con esto es que el estilo que establezca anulará cualquier otro estilo que ya tenga en los Elementos. Para superar esto, vea esta pregunta relacionada / respuesta aceptada aquí
waxingsatirical
6

+1 por la respuesta de Sergey. Y si desea aplicar eso a todos sus StackPanels, puede hacer esto:

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

Pero tenga cuidado: si define un estilo como este en su App.xaml (u otro diccionario que se fusiona en Application.Resources), puede anular el estilo predeterminado del control. Para controles en su mayoría sin apariencia, como el panel de pila, no es un problema, pero para cuadros de texto, etc., puede tropezar con este problema , que afortunadamente tiene algunas soluciones.

Andre Luus
fuente
<Style.Resources> ¿Estás seguro de esto porque cuando lo intenté mostraba un error?
grv_9098
Lo siento, no puedo recordar de la mano. Tendré que intentarlo yo mismo para ver. Volvere a ti.
Andre Luus
Sí funciona. Simplemente hice lo mismo para establecer TextWeight = "Bold" en todos los TextBlocks en un StackPanel. La única diferencia fue que configuré el estilo explícitamente en el StackPanel.
Andre Luus
Gracias por su preocupación, pero aún tengo dudas. Sé de eso Se llama Estilo de alcance. Supongo que será <StackPanel.Resources> y no <Style.Resources>. Será mejor si puedes pegar el código de tu ...
grv_9098
3

Siguiendo la sugerencia de Sergey, puede definir y reutilizar un estilo completo (con varios establecedores de propiedades, incluido el margen) en lugar de solo un objeto de espesor:

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

Tenga en cuenta que el truco aquí es el uso de herencia de estilo para el estilo implícito, heredando del estilo en algún diccionario de recursos externo (probablemente fusionado desde un archivo XAML externo).

Nota al margen:

Al principio, intenté ingenuamente usar el estilo implícito para establecer la propiedad Style del control en ese recurso Style externo (por ejemplo, definido con la clave "MyStyle"):

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

lo que provocó que Visual Studio 2010 se cerrara inmediatamente con un error de FALLA CATASTROFICA (HRESULT: 0x8000FFFF (E_UNEXPECTED)), como se describe en https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails -with-catastrophic-failure-when-a-style-tries-to-set-style-property #

George Birbilis
fuente
3

Grid.ColumnSpacing , Grid.RowSpacing , StackPanel.Spacing ahora están en la vista previa de UWP, todo permitirá lograr mejor lo que se solicita aquí.

Estas propiedades actualmente solo están disponibles con Windows 10 Fall Creators Update Insider SDK, ¡pero deberían llegar a los bits finales!

Pedro Lamas
fuente
2

Mi enfoque hereda 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>

Todo lo que se necesita es la siguiente clase corta:

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

a veces es necesario configurar el Relleno, no el Margen, para que el espacio entre los elementos sea más pequeño que el predeterminado

Danil
fuente