Wie platziere ich die untergeordneten Elemente eines StackPanels?

187

Gegeben ein StackPanel:

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

Was ist der beste Weg, um die untergeordneten Elemente so zu platzieren, dass zwischen ihnen gleich große Lücken bestehen, obwohl die untergeordneten Elemente selbst unterschiedlich groß sind? Kann dies durchgeführt werden, ohne die Eigenschaften für jedes einzelne Kind festzulegen?

GraemeF
quelle
Es scheint die beste Option zu sein, einzelne Elemente nur mit Polstern zu versehen.
Zar

Antworten:

278

Verwenden Sie Rand oder Polsterung, die auf den Bereich innerhalb des Containers angewendet werden:

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

BEARBEITEN: Wenn Sie den Rand zwischen zwei Containern wiederverwenden möchten, können Sie den Randwert in eine Ressource in einem äußeren Bereich konvertieren, z

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

und beziehen Sie sich dann auf diesen Wert im inneren Bereich

<StackPanel.Resources>
    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Margin" Value="{StaticResource tbMargin}"/>
    </Style>
</StackPanel.Resources>
Sergey Aldoukhov
quelle
5
Der Scoped Style ist eine großartige Möglichkeit, dies zu tun - danke für den Tipp!
Ana Betts
2
Was ist, wenn ich es für das gesamte Projekt verwenden möchte?
grv_9098
10
Kann jemand erklären, warum dies nur funktioniert, wenn Sie den Typ explizit definieren (z. B. TextBox)? Wenn ich dies mit FrameworkElement versuche, sodass alle untergeordneten Elemente voneinander getrennt sind, hat dies keine Auswirkungen.
Jack Ukleja
4
Dies funktioniert nicht gut, wenn Sie bereits einen Stil für definiert haben Button.
Mark Ingram
1
Als Randnotiz, wenn Sie dies mit einem tun möchten, müssen LabelSie Paddinganstelle vonMargin
Anthony Nichols
84

Ein weiterer netter Ansatz ist hier zu sehen: http://blogs.microsoft.co.il/blogs/eladkatz/archive/2011/05/29/what-is-the-easiest-way-to-set-spacing-between- items-in-stackpanel.aspx Link ist defekt -> dies ist das Webarchiv dieses Links.

Es wird gezeigt, wie ein angehängtes Verhalten erstellt wird, damit die folgende Syntax funktioniert:

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

Dies ist der einfachste und schnellste Weg, um Margin auf mehrere untergeordnete Elemente eines Panels festzulegen, auch wenn diese nicht vom gleichen Typ sind. (Dh Schaltflächen, Textfelder, Kombinationsfelder usw.)

Elad Katz
quelle
5
Dies ist ein ziemlich interessanter Weg, um dies zu erreichen. Es werden viele Annahmen darüber getroffen, wie Sie die Dinge genau platzieren möchten, und Sie haben sogar die Möglichkeit, die Ränder der ersten / letzten Elemente automatisch anzupassen.
Armentage
2
+1 für Vielseitigkeit. Um den Blog-Beitrag zu verbessern, können Sie if (fe.ReadLocalValue(FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue)vor dem eigentlichen Festlegen des Randes des untergeordneten Elements manuell Ränder für einige Elemente festlegen.
Xerillio
Beachten Sie, dass dies nicht funktioniert, wenn die untergeordneten Elemente dynamisch hinzugefügt / entfernt werden, z. B. in einem ItemsControl, das an eine sich ändernde Sammlung gebunden ist.
Drew Noakes
1
Falls es nicht funktioniert: Erstellen Sie das Projekt, sonst wird die Seite nicht gerendert.
Schlacht
2
Link ist kaputt!
Dave
13

Ich habe die Antwort von Elad Katz verbessert .

  • Fügen Sie MarginSetter die LastItemMargin-Eigenschaft hinzu, um das letzte Element speziell zu behandeln
  • Angehängte Eigenschaft "Abstand hinzufügen" mit vertikalen und horizontalen Eigenschaften, die den Abstand zwischen Elementen in vertikalen und horizontalen Listen hinzufügen und nachfolgende Ränder am Ende der Liste entfernen

Quellcode im Kern .

Beispiel:

<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>
eckig
quelle
Wie Elads Antwort funktioniert dies nicht, wenn die untergeordneten Elemente dynamisch hinzugefügt / entfernt werden, z. B. in einer ItemsControlBindung an eine sich ändernde Sammlung. Es wird davon ausgegangen, dass die Elemente ab dem Moment, in dem das LoadEreignis des Elternteils ausgelöst wird, statisch sind .
Drew Noakes
Außerdem gibt Ihr Kern eine 404.
Drew Noakes
Link zum Kern aktualisiert.
Angularsen
9

Das, was Sie wirklich tun möchten, ist, alle untergeordneten Elemente zu verpacken. In diesem Fall sollten Sie ein Elementsteuerelement verwenden und nicht auf schreckliche angehängte Eigenschaften zurückgreifen, von denen Sie am Ende eine Million für jede Eigenschaft haben, die Sie stylen möchten.

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

Geben Sie hier die Bildbeschreibung ein

Bradgonesurfing
quelle
Toller Tipp, aber das funktioniert besser mit dem Style TargetType als "FrameworkElement" (sonst funktioniert es zum Beispiel nicht für Image)
Puffin
1
Ich mag diese Idee. Nur eine Ergänzung: Wenn Sie den Abstand vom Rand des StackPanel ( Margin="0 0 -5 0") abziehen, wird auch der Abstand nach dem letzten Element in der Liste ausgeglichen .
label17
Das Problem dabei ist, dass der von Ihnen festgelegte Stil alle anderen Stile überschreibt, die Sie möglicherweise bereits für die Elemente haben. Um dies zu überwinden, sehen Sie diese verwandte Frage / akzeptierte Antwort hier
Waxingsatirical
6

+1 für Sergeys Antwort. Und wenn Sie das auf alle Ihre StackPanels anwenden möchten, können Sie dies tun:

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

Aber Achtung: Wenn Sie einen Stil , wie dies in Ihrem App.xaml (oder ein anderes Wörterbuch , das in die Application.Resources verschmolzen ist) definieren es kann den Standardstil der Steuerung außer Kraft setzen. Für meist unscheinbare Steuerelemente wie das Stackpanel ist dies kein Problem, aber für Textfelder usw. können Sie auf dieses Problem stoßen , das zum Glück einige Problemumgehungen aufweist.

Andre Luus
quelle
<Style.Resources> Sind Sie sich sicher, denn als ich das versuchte, zeigte es einen Fehler.
grv_9098
Entschuldigung, ich kann mich nicht ohne weiteres erinnern. Ich muss es selbst versuchen, um zu sehen. Ich melde mich bei dir.
Andre Luus
Ja, es funktioniert. Genauso haben Sie TextWeight = "Bold" für alle TextBlocks in einem StackPanel festgelegt. Der einzige Unterschied war, dass ich den Stil explizit auf dem StackPanel festgelegt habe.
Andre Luus
Vielen Dank für Ihre Besorgnis, aber ich habe immer noch Zweifel. Ich weiß, dass es Scope Style heißt. Ich denke, es wird <StackPanel.Resources> und nicht <Style.Resources> sein. Es wird besser sein, wenn Sie den Code Ihrer ...
grv_9098
3

Gemäß dem Vorschlag von Sergey können Sie einen ganzen Stil (mit verschiedenen Eigenschaftssetzern, einschließlich Rand) definieren und wiederverwenden, anstatt nur ein Objekt für die Dicke:

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

Beachten Sie, dass der Trick hier die Verwendung der Stilvererbung für den impliziten Stil ist, der vom Stil in einem externen (wahrscheinlich aus einer externen XAML-Datei zusammengeführten) Ressourcenwörterbuch erbt.

Randnotiz:

Zuerst habe ich naiv versucht, den impliziten Stil zu verwenden, um die Style-Eigenschaft des Steuerelements auf diese äußere Style-Ressource festzulegen (z. B. mit dem Schlüssel "MyStyle" definiert):

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

Dies führte dazu, dass Visual Studio 2010 sofort mit dem Fehler CATASTROPHIC FAILURE (HRESULT: 0x8000FFFF (E_UNEXPECTED)) heruntergefahren wurde, wie unter https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails beschrieben -mit-katastrophalem-Fehler-wenn-ein-Stil-versucht-zu-setzen-Stil-Eigenschaft #

George Birbilis
quelle
3

Grid.ColumnSpacing , Grid.RowSpacing , StackPanel.Spacing sind jetzt in der UWP-Vorschau verfügbar. Alle ermöglichen eine bessere Erfüllung der hier angeforderten Anforderungen.

Diese Eigenschaften sind derzeit nur mit dem Windows 10 Fall Creators Update Insider SDK verfügbar, sollten es aber bis zum Ende schaffen!

Pedro Lamas
quelle
2

Mein Ansatz erbt StackPanel.

Verwendung:

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

Alles was benötigt wird ist die folgende kurze Klasse:

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.
quelle
0

Manchmal müssen Sie Padding und nicht Margin festlegen, um den Abstand zwischen Elementen kleiner als die Standardeinstellung zu machen

Danil
quelle