Come distacco gli elementi figlio di uno StackPanel?


187

Dato uno StackPanel:

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

Qual è il modo migliore per distanziare gli elementi figlio in modo che vi siano spazi uguali tra loro, anche se gli elementi figlio stessi sono di dimensioni diverse? Può essere fatto senza impostare le proprietà su ciascuno dei singoli bambini?


L'aggiunta di imbottiture ai singoli articoli sembra essere l'opzione migliore.
Zar

Risposte:


278

Usa margine o riempimento, applicato all'ambito all'interno del contenitore:

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

MODIFICA: Nel caso in cui si desideri riutilizzare il margine tra due contenitori, è possibile convertire il valore del margine in una risorsa in un ambito esterno, fe

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

e quindi fare riferimento a questo valore nell'ambito interno

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

5
Lo stile con ambito è un modo fantastico per farlo - grazie per il suggerimento!
Ana Betts,

2
Cosa succede se voglio usarlo per l'intero progetto?
grv_9098,

10
Qualcuno può spiegare perché questo funziona solo quando si definisce esplicitamente il tipo (ad esempio TextBox)? Se provo questo usando FrameworkElement in modo che tutti i bambini siano distanziati, non ha alcun effetto.
Jack Ukleja,

4
Questo non funziona bene se hai già definito uno stile per Button.
Mark Ingram,

1
Come nota a margine, se vuoi farlo con un Labeldevi usare Paddinginvece diMargin
Anthony Nichols

84

Un altro bell'approccio può essere visto qui: http://blogs.microsoft.co.il/blogs/eladkatz/archive/2011/05/29/what-is-the-easiest-way-to-set-spacing-b Between- items-in-stackpanel.aspx Il link è interrotto -> questo è il webarchive di questo link.

Mostra come creare un comportamento associato, in modo che la sintassi come questa funzioni:

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

Questo è il modo più semplice e veloce per impostare il margine su diversi figli di un pannello, anche se non sono dello stesso tipo. (Vale a dire pulsanti, caselle di testo, caselle combinate, ecc.)


5
Questo è un modo piuttosto interessante di farlo. Presenta molte ipotesi su come si desidera spaziare le cose e ti dà anche l'opportunità di regolare automaticamente i margini sul primo / ultimo oggetto.
Armentage,

2
+1 per versatilità. Inoltre, per migliorare il post sul blog, aggiungendo if (fe.ReadLocalValue(FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue)prima di impostare effettivamente il margine del figlio, è possibile specificare manualmente i margini per alcuni elementi.
Xerillio,

Si noti che ciò non funziona se gli elementi figlio vengono aggiunti / rimossi in modo dinamico, ad esempio in un ItemsControl associato a una raccolta che cambia.
Drew Noakes,

1
Nel caso in cui non funzioni: crea il progetto, altrimenti non visualizzerà la pagina.
Battaglia

2
Il collegamento è interrotto!
Dave,

13

Ho migliorato la risposta di Elad Katz .

  • Aggiungi la proprietà LastItemMargin a MarginSetter per gestire in modo speciale l'ultimo elemento
  • Aggiungi la proprietà Spaziatura collegata con le proprietà Verticale e Orizzontale che aggiunge la spaziatura tra gli elementi negli elenchi verticale e orizzontale ed elimina qualsiasi margine finale alla fine dell'elenco

Codice sorgente in sintesi .

Esempio:

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

Come la risposta di Elad, questo non funziona se gli elementi figlio vengono aggiunti / rimossi in modo dinamico, ad esempio in un ItemsControlvincolo a una raccolta che cambia. Presuppone che gli elementi siano statici dal momento in cui l' Loadevento del genitore viene generato.
Drew Noakes,

Inoltre, la tua idea dà un 404.
Drew Noakes

Link aggiornato a gist.
angularsen,

9

La cosa che vuoi veramente fare è avvolgere tutti gli elementi figlio. In questo caso dovresti usare un controllo degli oggetti e non ricorrere a orribili proprietà annesse che finirai per avere un milione di per ogni proprietà che desideri stile.

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

inserisci qui la descrizione dell'immagine


Ottimo consiglio, ma funziona meglio con Style TargetType come "FrameworkElement" (altrimenti non funziona per Image, ad esempio)
Puffin

1
Mi piace questa idea. Solo un'aggiunta: sottraendo la quantità di spaziatura dal margine di StackPanel ( Margin="0 0 -5 0") si contrapporrà anche la spaziatura dopo l'ultimo elemento dell'elenco.
etichetta17,

Il problema è che lo stile impostato sovrascriverà qualsiasi altro stile che potresti già avere sugli Articoli. Per ovviare a questo, vedere questa domanda correlata / risposta accettata qui
waxingsatirical,

6

+1 per la risposta di Sergey. E se vuoi applicarlo a tutti i tuoi StackPanel puoi farlo:

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

Ma attenzione: se si definisce uno stile come questo nel proprio App.xaml (o in un altro dizionario che viene unito in Application.Resources), può sovrascrivere lo stile predefinito del controllo. Per i controlli per lo più privi di aspetto come lo stackpanel non è un problema, ma per le caselle di testo ecc. Potresti imbatterti in questo problema , che fortunatamente ha alcune soluzioni alternative.


<Style.Resources> Ne sei sicuro perché quando ho provato questo stava mostrando errore.
grv_9098,

Mi dispiace, non riesco a ricordare fuori mano. Dovrò provare io stesso per vedere. Tornerò da te.
Andre Luus,

Sì, funziona. Ho fatto lo stesso per impostare TextWeight = "Bold" su tutti i TextBlock in StackPanel. L'unica differenza era che ho impostato esplicitamente lo stile su StackPanel.
Andre Luus,

Grazie per la tua preoccupazione, ma ho ancora dei dubbi. So che si chiama Scope Style. Immagino che sarà <StackPanel.Resources> e non <Style.Resources>. Sarà più bello se riesci a incollare il pezzo di codice del tuo ...
grv_9098

3

Seguendo il suggerimento di Sergey, puoi definire e riutilizzare un intero stile (con vari setter di proprietà, incluso il margine) invece di un solo oggetto 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>

Si noti che il trucco qui è l'uso di Style Inheritance per lo stile implicito, che eredita dallo stile in un dizionario delle risorse esterno (probabilmente unito da un file XAML esterno).

Nota a margine:

Inizialmente, ho cercato ingenuamente di usare lo stile implicito per impostare la proprietà Style del controllo su quella risorsa Style esterna (diciamo definita con la chiave "MyStyle"):

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

che ha causato la chiusura immediata di Visual Studio 2010 con errore CATASTROPHIC FAILURE (HRESULT: 0x8000FFFF (E_UNEXPECTED)), come descritto in https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails -con-catastrofica-fallimento-quando-a-style-cerca-to-set-style-proprietà #




2

Il mio approccio eredita 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>

Tutto ciò che serve è la seguente breve classe:

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

a volte è necessario impostare Riempimento, non Margine per rendere lo spazio tra gli elementi più piccolo del valore predefinito

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.