Bagaimana cara memisahkan elemen anak dari StackPanel?

187

Diberikan StackPanel:

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

Apa cara terbaik untuk memisahkan elemen-elemen anak sehingga ada celah yang berukuran sama di antara mereka, meskipun elemen-elemen anak itu sendiri memiliki ukuran yang berbeda? Bisakah itu dilakukan tanpa menetapkan properti pada masing-masing anak?

GraemeF
sumber
Benar-benar hanya menambahkan padding ke masing-masing item tampaknya menjadi pilihan terbaik.
zar

Jawaban:

278

Gunakan Margin atau Padding, diterapkan pada ruang lingkup dalam wadah:

<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: Jika Anda ingin menggunakan kembali margin antara dua kontainer, Anda dapat mengkonversi nilai margin ke sumber daya dalam lingkup luar,

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

dan kemudian merujuk pada nilai ini di lingkup dalam

<StackPanel.Resources>
    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Margin" Value="{StaticResource tbMargin}"/>
    </Style>
</StackPanel.Resources>
Sergey Aldoukhov
sumber
5
Gaya cakupan adalah cara yang luar biasa untuk melakukan itu - terima kasih atas tipnya!
Ana Betts
2
Bagaimana jika saya ingin menggunakannya untuk seluruh proyek?
grv_9098
10
Dapatkah seseorang menjelaskan mengapa ini hanya berfungsi ketika Anda secara eksplisit mendefinisikan jenisnya (mis. TextBox)? Jika saya mencoba ini menggunakan FrameworkElement sehingga semua anak diberi spasi, itu tidak berpengaruh.
Jack Ukleja
4
Ini tidak berfungsi dengan baik jika Anda sudah menentukan gaya untuk Button.
Tandai Ingram
1
Sebagai catatan, jika Anda ingin melakukan ini dengan LabelAnda harus menggunakan PaddingbukanMargin
Anthony Nichols
84

Pendekatan lain yang bagus bisa dilihat di sini: http://blogs.microsoft.co.il/blogs/eladkatz/archive/2011/05/29/what-is-the-easiest-way-to-set-spacing-between- items-in-stackpanel.aspx Tautan rusak -> ini adalah webarchive dari tautan ini.

Ini menunjukkan cara membuat perilaku terlampir, sehingga sintaksis seperti ini berfungsi:

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

Ini adalah cara termudah & tercepat untuk mengatur Margin ke beberapa anak panel, bahkan jika mereka tidak dari jenis yang sama. (Yaitu Tombol, Kotak Teks, Kotak Kombo, dll.)

Elad Katz
sumber
5
Ini adalah cara yang cukup menarik untuk dilakukan. Ini membuat banyak asumsi tentang bagaimana Anda ingin mengatur ruang, dan bahkan memberi Anda kesempatan untuk secara otomatis menyesuaikan margin pada item pertama / terakhir.
Armentage
2
+1 untuk keserbagunaan. Juga untuk meningkatkan pada posting blog, menambahkan if (fe.ReadLocalValue(FrameworkElement.MarginProperty) == DependencyProperty.UnsetValue)sebelum benar-benar mengatur margin anak itu memungkinkan untuk secara manual menentukan margin untuk beberapa elemen.
Xerillio
Perhatikan bahwa ini tidak berfungsi jika item turunan ditambahkan / dihapus secara dinamis, seperti di ItemsControl terikat ke koleksi yang berubah.
Drew Noakes
1
Jika tidak berhasil: Bangun proyek, jika tidak maka tidak akan merender halaman.
Pertempuran
2
Tautan rusak!
Dave
13

Saya memperbaiki jawaban Elad Katz .

  • Tambahkan properti LastItemMargin ke MarginSetter untuk menangani item terakhir secara khusus
  • Tambahkan Spacing properti terlampir dengan properti Vertikal dan Horizontal yang menambahkan spasi antara item dalam daftar vertikal dan horizontal dan menghilangkan margin trailing di akhir daftar

Kode sumber dalam inti .

Contoh:

<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
sumber
Seperti jawaban Elad, ini tidak berfungsi jika item anak ditambahkan / dihapus secara dinamis, seperti ItemsControlterikat pada koleksi yang berubah. Diasumsikan bahwa barang-barang itu statis sejak saat acara induknya Loadmenyala.
Drew Noakes
Juga, intisari Anda memberikan 404.
Drew Noakes
Tautan yang diperbarui ke inti.
angularsen
9

Hal yang benar-benar ingin Anda lakukan adalah membungkus semua elemen anak. Dalam hal ini Anda harus menggunakan kontrol item dan tidak menggunakan properti terlampir yang mengerikan yang pada akhirnya Anda akan memiliki jutaan properti yang ingin Anda ciptakan.

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

masukkan deskripsi gambar di sini

bradgonesurfing
sumber
Tip yang bagus, tetapi ini bekerja lebih baik dengan Style TargetType sebagai "FrameworkElement" (jika tidak, tidak berfungsi untuk Image, misalnya)
Puffin
1
Aku suka ide ini. Hanya satu tambahan: mengurangi jumlah spasi dari margin StackPanel ( Margin="0 0 -5 0") juga akan melawan jarak setelah item terakhir dalam daftar.
label17
Masalahnya adalah gaya yang Anda atur akan menimpa gaya lain yang mungkin sudah Anda miliki di Item. Untuk mengatasinya lihat pertanyaan terkait ini / jawaban yang diterima di sini
waxingsatirical
6

+1 untuk jawaban Sergey. Dan jika Anda ingin menerapkannya pada semua StackPanels Anda, Anda dapat melakukan ini:

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

Namun berhati-hatilah: jika Anda mendefinisikan gaya seperti ini di App.xaml Anda (atau kamus lain yang digabungkan ke dalam Aplikasi. Sumber) itu dapat mengganti gaya default kontrol. Untuk sebagian besar kontrol yang tidak terlihat seperti stackpanel itu bukan masalah, tetapi untuk kotak teks dll Anda mungkin menemukan masalah ini , yang untungnya memiliki beberapa solusi.

Andre Luus
sumber
<Style.Resources> Apakah Anda yakin karena itu ketika saya mencoba ini menunjukkan kesalahan.
grv_9098
Maaf, saya tidak dapat mengingat begitu saja. Saya harus mencobanya sendiri untuk melihatnya. Aku akan kembali padamu.
Andre Luus
Ya itu berhasil. Lakukan hal yang sama untuk mengatur TextWeight = "Bold" pada semua TextBlocks di StackPanel. Satu-satunya perbedaan adalah saya mengatur gaya secara eksplisit di StackPanel.
Andre Luus
Terima kasih atas perhatian Anda, tetapi saya masih ragu. Saya tahu itu disebut Gaya Lingkup. Saya kira itu akan menjadi <StackPanel.Resources> dan bukan <Style.Resources>. Akan lebih hebat jika Anda bisa menempelkan potongan kode ...
grv_9098
3

Menindaklanjuti saran Sergey, Anda dapat menentukan dan menggunakan kembali seluruh Gaya (dengan berbagai setter properti, termasuk Margin) alih-alih hanya objek Ketebalan:

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

Perhatikan bahwa trik di sini adalah penggunaan Style Inheritance untuk style implisit, yang diturunkan dari style di kamus sumber daya luar (mungkin digabung dari file XAML eksternal).

Sidenote:

Pada awalnya, saya secara naif mencoba menggunakan gaya implisit untuk mengatur properti Style dari kontrol ke sumber daya Style luar (katakanlah didefinisikan dengan kunci "MyStyle"):

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

yang menyebabkan Visual Studio 2010 segera ditutup dengan kesalahan CATASTROPHIC FAILURE (HRESULT: 0x8000FFFF (E_UNEXPECTED)), seperti yang dijelaskan di https://connect.microsoft.com/VisualStudio/feedback/details/753211/xaml-editor-window-fails -dengan-bencana-kegagalan-ketika-gaya-mencoba-untuk-mengatur-gaya-properti #

George Birbilis
sumber
3

Grid.ColumnSpacing , Grid.RowSpacing , StackPanel.Spacing sekarang di pratinjau UWP, semua akan memungkinkan untuk lebih memahami apa yang diminta di sini.

Properti ini saat ini hanya tersedia dengan Windows 10 Fall Creators Update Insider SDK, tetapi harus membuatnya ke bit akhir!

Pedro Lama
sumber
2

Pendekatan saya mewarisi StackPanel.

Pemakaian:

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

Yang dibutuhkan hanyalah kelas pendek berikut:

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

terkadang Anda perlu mengatur Padding, bukan Margin untuk membuat ruang antar item lebih kecil dari standar

Danil
sumber