Jak rozmieścić elementy potomne StackPanel?

187

Biorąc pod uwagę StackPanel:

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

Jaki jest najlepszy sposób na rozmieszczenie elementów potomnych, aby między nimi były równej wielkości przerwy, mimo że same elementy potomne mają różne rozmiary? Czy można to zrobić bez ustawiania właściwości dla poszczególnych dzieci?

GraemeF
źródło
Naprawdę po prostu dodanie dopełnienia do poszczególnych elementów wydaje się najlepszą opcją.
zar

Odpowiedzi:

278

Użyj marginesu lub wypełnienia, zastosowanych do zakresu w kontenerze:

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

EDYCJA: Jeśli chcesz ponownie użyć marginesu między dwoma kontenerami, możesz przekonwertować wartość marginesu na zasób w zakresie zewnętrznym, np.

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

a następnie odnoszą się do tej wartości w wewnętrznym zakresie

<StackPanel.Resources>
    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Margin" Value="{StaticResource tbMargin}"/>
    </Style>
</StackPanel.Resources>
Siergiej Aldoukhov
źródło
5
Styl z lunetą to niesamowity sposób na zrobienie tego - dzięki za wskazówkę!
Ana Betts
2
Co jeśli chcę go używać do całego projektu?
grv_9098
10
Czy ktoś może wyjaśnić, dlaczego to działa tylko wtedy, gdy wyraźnie zdefiniujesz typ (np. TextBox)? Jeśli spróbuję tego za pomocą FrameworkElement, aby wszystkie dzieci były rozmieszczone w odstępach, nie będzie to miało wpływu.
Jack Ukleja
4
To nie działa dobrze, jeśli już zdefiniowałeś styl Button.
Mark Ingram
1
Na marginesie, jeśli chcesz to zrobić Label, musisz użyć PaddingzamiastMargin
Anthony Nichols
84

Inne fajne podejście można zobaczyć tutaj: 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 jest zepsuty -> to jest webarchive tego linku.

Pokazuje, jak utworzyć załączone zachowanie, aby działała taka składnia:

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

Jest to najłatwiejszy i najszybszy sposób ustawienia marginesu na kilka elementów podrzędnych panelu, nawet jeśli nie są tego samego typu. (Tj. Przyciski, TextBoxes, ComboBoxes itp.)

Elad Katz
źródło
5
Jest to dość interesujący sposób na obejście tego. Dokonuje wielu założeń na temat tego, jak dokładnie chcesz rozmieszczać rzeczy, a nawet daje ci możliwość automatycznej regulacji marginesów na pierwszych / ostatnich elementach.
Armentage
2
+1 za wszechstronność. Również w celu ulepszenia posta na blogu, dodanie if (fe.ReadLocalValue(FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue)przed faktycznym ustawieniem marginesu dziecka pozwala ręcznie określić marginesy dla niektórych elementów.
Xerillio,
Zauważ, że to nie działa, jeśli elementy potomne są dodawane / usuwane dynamicznie, na przykład w ItemsControl powiązanym ze zmieniającą się kolekcją.
Drew Noakes,
1
W przypadku, gdy nie działa: Zbuduj projekt, w przeciwnym razie strona nie będzie renderowana.
Bitwa
2
Link jest zepsuty!
Dave
13

Poprawiłem odpowiedź Elad Katz .

  • Dodaj właściwość LastItemMargin do MarginSetter, aby specjalnie obsłużyć ostatni element
  • Dodaj dołączoną właściwość Rozstaw z właściwościami pionowymi i poziomymi, która dodaje odstępy między elementami na listach pionowych i poziomych i eliminuje margines końcowy na końcu listy

Kod źródłowy w gist .

Przykład:

<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
źródło
Podobnie jak odpowiedź Elad, nie działa to, jeśli elementy potomne są dodawane / usuwane dynamicznie, na przykład w ItemsControlpowiązaniu ze zmieniającą się kolekcją. Zakłada, że ​​przedmioty są statyczne od momentu uruchomienia Loadzdarzenia przez rodzica .
Drew Noakes,
Poza tym twój cel daje 404.
Drew Noakes,
Zaktualizowany link do Gist.
angularsen
9

Naprawdę chcesz owinąć wszystkie elementy potomne. W takim przypadku powinieneś użyć kontroli przedmiotów i nie uciekać się do okropnie przywiązanych właściwości, które ostatecznie będą miały milion za każdą nieruchomość, którą chcesz stylizować.

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

wprowadź opis zdjęcia tutaj

bradgonesurfing
źródło
Świetna wskazówka, ale działa to lepiej ze stylem TargetType jako „FrameworkElement” (w przeciwnym razie nie działa na przykład dla obrazu)
Puffin
1
Podoba mi się ten pomysł. Tylko jeden dodatek: odejmowanie odstępu od marginesu StackPanel ( Margin="0 0 -5 0") również przeciwdziała odstępowi po ostatnim elemencie na liście.
label17
Problem polega na tym, że ustawiony styl zastąpi wszelkie inne style, które możesz już mieć na przedmiotach. Aby temu zaradzić, zobacz powiązane pytanie / zaakceptowaną odpowiedź tutaj
waxingsatirical
6

+1 za odpowiedź Siergieja. A jeśli chcesz zastosować to do wszystkich swoich StackPanels, możesz to zrobić:

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

Ale uwaga: jeśli zdefiniujesz taki styl w App.xaml (lub innym słowniku połączonym z Application.Resources), może on zastąpić domyślny styl formantu. W przypadku większości nie wyglądających elementów sterujących, takich jak panel sterujący, nie stanowi to problemu, ale w przypadku pól tekstowych itp. Można natknąć się na ten problem , który na szczęście zawiera pewne obejścia.

Andre Luus
źródło
<Style.Resources> Czy jesteś tego pewien, bo kiedy próbowałem, wyświetlał błąd.
grv_9098
Przepraszam, nie pamiętam z ręki. Będę musiał spróbować, żeby zobaczyć. Wrócę do ciebie.
Andre Luus,
Tak, to działa. Zrobiłem to samo, aby ustawić TextWeight = "Bold" na wszystkich TextBlocks w StackPanel. Jedyną różnicą było to, że ustawiłem styl wyraźnie na StackPanel.
Andre Luus,
Dzięki za troskę, ale wciąż mam wątpliwości. Wiem o tym, że nazywa się Scope Style. Myślę, że będzie to <StackPanel.Resources>, a nie <Style.Resources>. Będzie lepiej, jeśli możesz wkleić fragment kodu swojego ...
grv_9098
3

Postępując zgodnie z sugestią Siergieja, możesz zdefiniować i ponownie wykorzystać cały Styl (z różnymi ustawieniami właściwości, w tym Margines) zamiast tylko obiektu Grubość:

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

Zwróć uwagę, że sztuczka polega na użyciu dziedziczenia stylu dla stylu niejawnego, dziedziczącego po stylu w jakimś zewnętrznym (prawdopodobnie scalonym z zewnętrznego pliku XAML) słowniku zasobów.

Dygresja:

Najpierw naiwnie próbowałem użyć stylu niejawnego, aby ustawić właściwość Style kontrolki na ten zewnętrzny zasób stylu (powiedzmy zdefiniowany za pomocą klucza „MyStyle”):

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

który spowodował natychmiastowe zamknięcie programu Visual Studio 2010 z błędem CATASTROPHIC FAILURE (HRESULT: 0x8000FFFF (E_UNEXPECTED)), jak opisano na https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails -with-katastroficzna awaria-kiedy-styl-próbuje-ustawić-właściwość-styl #

George Birbilis
źródło
3

Grid.ColumnSpacing , Grid.RowSpacing , StackPanel.Spacing są teraz na podglądzie UWP, wszystko pozwoli lepiej zrealizować to, co jest wymagane.

Te właściwości są obecnie dostępne tylko z Windows 10 Fall Creators Update Insider SDK, ale powinny dotrzeć do ostatnich kawałków!

Pedro Lamas
źródło
2

Moje podejście dziedziczy StackPanel.

Stosowanie:

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

Potrzebne są tylko następujące krótkie klasy:

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.
źródło
0

czasem trzeba ustawić Wypełnienie, a nie Margines, aby zmniejszyć odstęp między przedmiotami niż domyślny

Danil
źródło