2009-05-31 8 views
146

donné une StackPanel:Comment espacer les éléments enfants d'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 de l'enfant afin qu'il existe des lacunes de taille égale entre eux, même si les éléments enfants eux-mêmes sont de tailles différentes? Peut-il être fait sans définir des propriétés sur chacun des enfants individuels?

Répondre

228

Utiliser la marge ou Rembourrage, appliqués à 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: Si vous voulez réutiliser la marge entre deux conteneurs, vous pouvez convertir la valeur de la marge à un ressource dans une portée externe, fe

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

puis se référer à cette valeur dans le champ intérieur

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

Le Style Scoped est une façon * géniale * de le faire - merci pour le pourboire! –

+1

Et si je veux l'utiliser pour l'ensemble du projet? –

+4

Quelqu'un peut-il expliquer pourquoi cela ne fonctionne que lorsque vous définissez explicitement le type (par exemple TextBox)? Si j'essaie ceci en utilisant FrameworkElement pour que tous les enfants soient espacés, cela n'a aucun effet. – Schneider

4

+1 pour la réponse de Sergey. Et si vous voulez l'appliquer à 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 méfiez-vous: si vous définissez un style comme celui-ci dans votre App.xaml (ou un autre dictionnaire qui est fusionné dans l'application .Resources) peut remplacer le style par défaut du contrôle. Pour des contrôles presque sans apparence comme le panneau de pile, ce n'est pas un problème, mais pour les boîtes de texte, etc., vous pouvez tomber sur this problem, qui a heureusement quelques solutions de contournement.

+0

Etes-vous sûr à ce sujet coz quand j'ai essayé cela, il montre une erreur. –

+0

Désolé, je ne m'en souviens plus. Je vais devoir l'essayer moi-même pour voir. Je reviendrai vers toi. –

+0

Ce sera reconnaissant. –

69

Une autre approche agréable peut être vu 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

Il montre comment créer un comportement attaché, de sorte que la syntaxe comme cela fonctionne:

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

C'est le & plus facile la plus rapide de Définissez la marge sur plusieurs enfants d'un panneau, même s'ils ne sont pas du même type. (boutons Ie, TextBoxes, ComboBoxes, etc.)

+5

C'est une façon assez intéressante de faire ça. Il fait beaucoup de suppositions sur exactement comment vous voulez espacer les choses, et vous donne même l'occasion d'ajuster automatiquement les marges sur le premier/dernier élément. – Armentage

+1

+1 pour la polyvalence. De plus, pour améliorer le blog, ajouter 'if (fe.ReadLocalValue (FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue)' avant de définir la marge de l'enfant, il permet de spécifier manuellement les marges de certains éléments. – Xerillio

+0

Notez que cela ne fonctionne pas si les éléments enfants sont ajoutés/supprimés dynamiquement, comme dans un ItemsControl lié à une collection en cours de modification. –

3

Faisant suite à la suggestion de Sergey, vous pouvez définir et réutiliser un ensemble de style (avec divers setters de propriété, y compris la marge) au lieu d'un objet Epaisseur:

<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 du style héritage pour le style implicite, héritant du style dans certains (probablement fusionné à partir du fichier XAML externe) externe dictionnaire de ressources.

Sidenote:

Au début, je naïvement essayé d'utiliser le style implicite pour définir la propriété Style du contrôle de cette ressource de style externe (par exemple défini avec la touche « MyStyle »):

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

qui a causé Visual studio 2010 pour arrêter immédiatement avec l'erreur CATASTROPHIQUE PANNE (HRESULT: 0x8000FFFF (E_UNEXPECTED)), comme décrit parfois à https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails-with-catastrophic-failure-when-a-style-tries-to-set-style-property#

0

vous devez définir Rembourrage, pas Marge pour faire de l'espace entre les éléments plus petits que la valeur par défaut

9

J'ai amélioré le Elad Katz' answer.

  • Ajouter une propriété LastItemMargin à MarginSetter pour traiter spécialement le dernier élément
  • Ajouter Espacement propriété attachée avec verticale et les propriétés horizontales qui ajoute l'espacement entre les éléments dans les listes verticales et horizontales et élimine toute marge de fuite à la fin de la liste

Source code in gist.

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

Comme la réponse d'Elad, cela ne fonctionne pas si les éléments enfants sont ajoutés/supprimés dynamiquement, comme dans un 'ItemsControl' lié à une collection changeante. Il suppose que les éléments sont statiques à partir du moment où l'événement 'Load' du parent se déclenche. –

+0

En outre, votre esprit donne un 404. –

+0

Lien mis à jour pour GIST. – angularsen

5

La chose que vous voulez vraiment faire est envelopper tous les éléments enfants. Dans ce cas, vous devez utiliser un contrôle des éléments et ne pas avoir recours à des propriétés attachées horribles que vous finirez par avoir un million de pour chaque propriété que vous souhaitez styler.

<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

+0

Bon conseil, mais cela fonctionne mieux avec le Style TargetType comme "FrameworkElement" (sinon cela ne fonctionne pas pour Image, par exemple) – Puffin

+0

J'aime cette idée. Juste un ajout: en soustrayant la quantité d'espacement de la marge du StackPanel ('Margin =" 0 0 -5 0 "'), vous contrerez aussi l'espacement après le dernier élément de la liste. – label17

+0

Le problème avec ceci est que le style que vous définissez remplacera tous les autres styles que vous avez déjà sur les éléments. Pour surmonter cela, voir cette question/réponse acceptée [ici] (http://stackoverflow.com/questions/4003709/wrap-something-around-each-item-in-an-itemscontrol) – waxingsatirical

0

Mon approche StackPanel hérite.

Utilisation:

<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 qui est nécessaire est court la classe suivante:

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(); 
     } 
    } 
} 
1

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

Ces propriétés sont actuellement disponibles uniquement avec le Kit de développement Windows Embedded Update Insider de Windows 10, mais devraient atteindre les derniers bits!