Comment espacer les éléments enfants d'un StackPanel?

187

Étant donné un StackPanel:

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

Quelle est la meilleure façon d'espacer les éléments enfants afin qu'il y ait des espaces de taille égale entre eux, même si les éléments enfants eux-mêmes sont de tailles différentes? Cela peut-il être fait sans définir de propriétés sur chacun des enfants individuels?

GraemeF
la source
Le simple fait d'ajouter du rembourrage à des éléments individuels semble être la meilleure option.
zar le

Réponses:

278

Utilisez Margin ou Padding, appliqué à la portée dans le conteneur:

<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: Dans le cas où vous voudriez réutiliser la marge entre deux conteneurs, vous pouvez convertir la valeur de la marge en une ressource dans une portée externe, fe

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

puis faites référence à cette valeur dans la portée interne

<StackPanel.Resources>
    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Margin" Value="{StaticResource tbMargin}"/>
    </Style>
</StackPanel.Resources>
Sergey Aldoukhov
la source
5
Le style scoped est un moyen génial de le faire - merci pour le conseil!
Ana Betts
2
Et si je veux l'utiliser pour l'ensemble du projet?
grv_9098
10
Quelqu'un peut-il expliquer pourquoi cela ne fonctionne que lorsque vous définissez explicitement le type (par exemple TextBox)? Si j'essaye cela en utilisant FrameworkElement pour que tous les enfants soient espacés, cela n'a aucun effet.
Jack Ukleja
4
Cela ne fonctionne pas bien si vous avez déjà défini un style pour Button.
Mark Ingram
1
En remarque, si vous voulez faire cela avec un, Labelvous devez utiliser à la Paddingplace deMargin
Anthony Nichols
84

Une autre approche intéressante peut être vue ici: http://blogs.microsoft.co.il/blogs/eladkatz/archive/2011/05/29/what-is-the-easiest-way-to-set-spacing-between- items-in-stackpanel.aspx Le lien est cassé -> ceci est l'archive web de ce lien.

Il montre comment créer un comportement attaché, afin qu'une syntaxe comme celle-ci fonctionne:

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

C'est le moyen le plus simple et le plus rapide de définir Margin sur plusieurs enfants d'un panneau, même s'ils ne sont pas du même type. (Boutons Ie, TextBoxes, ComboBoxes, etc.)

Elad Katz
la source
5
C'est une manière assez intéressante de procéder. Il fait beaucoup d'hypothèses sur la manière exacte dont vous voulez espacer les choses et vous donne même la possibilité d'ajuster automatiquement les marges sur les premier / dernier éléments.
Armentage
2
+1 pour la polyvalence. Aussi pour améliorer le billet de blog, l'ajout if (fe.ReadLocalValue(FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue)avant de définir réellement la marge de l'enfant permet de spécifier manuellement les marges pour certains éléments.
Xerillio
Notez que cela ne fonctionne pas si les éléments enfants sont ajoutés / supprimés dynamiquement, comme dans un ItemsControl lié à une collection changeante.
Drew Noakes
1
Au cas où cela ne fonctionnerait pas: construisez le projet, sinon il ne rendra pas la page.
Bataille le
2
Le lien est cassé!
Dave le
13

J'ai amélioré la réponse d'Elad Katz .

  • Ajouter la propriété LastItemMargin à MarginSetter pour gérer spécialement le dernier élément
  • Ajouter une propriété jointe d'espacement avec des propriétés verticales et horizontales qui ajoute un espacement entre les éléments dans les listes verticales et horizontales et élimine toute marge de fin à la fin de la liste

Code source dans l'essentiel .

Exemple:

<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
la source
Comme la réponse d'Elad, cela ne fonctionne pas si les éléments enfants sont ajoutés / supprimés de manière dynamique, comme dans une ItemsControlliaison à une collection changeante. Il suppose que les éléments sont statiques à partir du moment où l' Loadévénement du parent se déclenche.
Drew Noakes
En outre, votre essence donne un 404.
Drew Noakes
Lien mis à jour vers l'essentiel.
angularsen
9

La chose que vous voulez vraiment faire est d'encapsuler tous les éléments enfants. Dans ce cas, vous devez utiliser un contrôle d'objets et ne pas recourir à d'horribles propriétés attachées dont vous finirez par avoir un million pour chaque propriété que vous souhaitez styliser.

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

entrez la description de l'image ici

bradgonesurf
la source
Excellent conseil, mais cela fonctionne mieux avec le Style TargetType comme "FrameworkElement" (sinon cela ne fonctionne pas pour Image, par exemple)
Puffin
1
J'aime cette idée. Juste un ajout: soustraire la quantité d'espacement de la marge de StackPanel ( Margin="0 0 -5 0") va également contrer l'espacement après le dernier élément de la liste.
label17
Le problème avec cela est que le style que vous définissez remplacera tous les autres styles que vous avez peut-être déjà sur les éléments. Pour surmonter cela, voir cette question connexe / réponse acceptée ici
waxingsatirical
6

+1 pour la réponse de Sergey. Et si vous souhaitez appliquer cela à tous vos StackPanels, vous pouvez le faire:

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

Mais attention: si vous définissez un style comme celui-ci dans votre App.xaml (ou dans un autre dictionnaire qui est fusionné dans Application.Resources), il peut remplacer le style par défaut du contrôle. Pour la plupart des contrôles sans apparence comme le stackpanel, ce n'est pas un problème, mais pour les zones de texte, etc., vous pouvez tomber sur ce problème , qui a heureusement quelques solutions de contournement.

André Luus
la source
<Style.Resources> Êtes-vous sûr que lorsque j'ai essayé cela, il montrait une erreur.
grv_9098
Désolé, je ne m'en souviens plus. Je vais devoir l'essayer moi-même pour voir. Je reviendrai vers toi.
Andre Luus
Oui, ça marche. J'ai juste fait la même chose pour définir TextWeight = "Bold" sur tous les TextBlocks dans un StackPanel. La seule différence était que j'avais défini le style explicitement sur le StackPanel.
Andre Luus
Merci pour votre préoccupation, mais j'ai encore des doutes. Je sais que cela s'appelle Scope Style. Je suppose que ce sera <StackPanel.Resources> et non <Style.Resources>. Ce sera plus génial si vous pouvez coller le morceau de code de votre ...
grv_9098
3

Suite à la suggestion de Sergey, vous pouvez définir et réutiliser un style entier (avec divers paramètres de propriété, y compris Margin) au lieu d'un simple objet 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>

Notez que l'astuce ici est l'utilisation de l'héritage de style pour le style implicite, héritant du style dans un dictionnaire de ressources externe (probablement fusionné à partir d'un fichier XAML externe).

Sidenote:

Au début, j'ai naïvement essayé d'utiliser le style implicite pour définir la propriété Style du contrôle sur cette ressource Style externe (par exemple définie avec la clé "MyStyle"):

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

qui a provoqué l'arrêt immédiat de Visual Studio 2010 avec une erreur CATASTROPHIC FAILURE (HRESULT: 0x8000FFFF (E_UNEXPECTED)), comme décrit à https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails -avec-un-échec-catastrophique-quand-un-style-essaie-de-définir-une-propriété de style #

George Birbilis
la source
3

Grid.ColumnSpacing , Grid.RowSpacing , StackPanel.Spacing sont maintenant en aperçu UWP, tout permettra de mieux accomplir ce qui est demandé ici.

Ces propriétés ne sont actuellement disponibles qu'avec le SDK Windows 10 Fall Creators Update Insider, mais devraient atteindre les derniers éléments!

Pedro Lamas
la source
2

Mon approche hérite de StackPanel.

Usage:

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

Tout ce dont vous avez besoin est le court cours suivant:

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
la source
0

Parfois, vous devez définir Padding, pas Margin pour rendre l'espace entre les éléments plus petit que la valeur par défaut

Danil
la source