WPF: Siatka z marginesem / dopełnieniem kolumny / wiersza?

137

Czy można łatwo określić margines i / lub wypełnienie dla wierszy lub kolumn w siatce WPF?

Mógłbym oczywiście dodać dodatkowe kolumny, aby oddzielić rzeczy, ale wydaje się, że to zadanie dla dopełnienia / marginesów (da znacznie prostszy XAML). Czy ktoś wyprowadził ze standardowej siatki, aby dodać tę funkcjonalność?

Brad Leach
źródło
4
Jeden przydatny przykład można znaleźć tutaj: codeproject.com/Articles/107468/WPF-Padded-Grid
peter70
2
Trochę zdziwiony, że nie jest to częścią podstawowych funkcji Grid ...
uceumern
Na dzień dzisiejszy, czego dowodzą 10 lat odpowiedzi, prawda jest taka, że ​​nie jest to łatwo możliwe, a najlepszym rozwiązaniem (aby uniknąć dodatkowej pracy podatnej na błędy za każdym razem, gdy używana jest komórka) jest wyprowadzenie siatki (jak sugerował wcześniej @ peter70 ), aby dodać odpowiednią właściwość zależności wypełnienia komórek, która będzie sterować właściwością Margin elementu podrzędnego komórki. To nie jest długie zadanie, a wtedy masz kontrolę wielokrotnego użytku. Komentarz boczny ... Siatka to naprawdę źle zaprojektowana kontrolka.
min

Odpowiedzi:

10

Możesz napisać własną GridWithMarginklasę, dziedziczoną z Gridi nadpisać ArrangeOverridemetodę, aby zastosować marginesy

Thomas Levesque
źródło
32
Nie rozumiem, dlaczego ludzie dają ci kciuki do góry, ponieważ nie jest to tak łatwe, jak myślisz / opisujesz tutaj. Po prostu spróbuj to zrobić 30 minut, a szybko zobaczysz, że Twoja odpowiedź nie ma zastosowania. Pomyśl także o łączeniu wierszy i kolumn
Eric Ouellet
89

RowDefinitioni ColumnDefinitionsą typu ContentElementi Marginsą ściśle FrameworkElementwłasnością. Więc jeśli chodzi o twoje pytanie, „czy to możliwe?” Odpowiedź brzmi zdecydowanie nie. I nie, nie widziałem żadnych paneli układu, które wykazywałyby taką funkcjonalność.

Zgodnie z sugestią możesz dodać dodatkowe wiersze lub kolumny. Ale możesz także ustawić marginesy dla Gridsamego elementu lub wszystkiego, co znajdowałoby się wewnątrz elementu a Grid, więc na razie jest to najlepsze obejście.

Charlie
źródło
OP nie próbuje ustawić marży na RowDefinition ani ColumnDefinition. Próbuje ustawić margines wizualnych dzieci siatki, które pochodzą z FrameworkElement.
15ee8f99-57ff-4f92-890c-b56153
58

Użyj Borderkontrolki poza kontrolką komórki i zdefiniuj wypełnienie dla tego:

    <Grid>
        <Grid.Resources >
            <Style TargetType="Border" >
                <Setter Property="Padding" Value="5,5,5,5" />
            </Style>
        </Grid.Resources>

        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Border Grid.Row="0" Grid.Column="0">
            <YourGridControls/>
        </Border>
        <Border Grid.Row="1" Grid.Column="0">
            <YourGridControls/>
        </Border>

    </Grid>


Źródło:

samad
źródło
2
@RichardEverett Check the Way Back Machine: zaktualizowano łącze w odpowiedzi.
CJBS
Najbardziej podoba mi się ta odpowiedź. Zapewnia rozwiązanie pierwotnego pytania i jest proste. Dzięki!
Tobias,
Jest to łatwe i praktyczne
aggsol
19

Możesz użyć czegoś takiego:

<Style TargetType="{x:Type DataGridCell}">
  <Setter Property="Padding" Value="4" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type DataGridCell}">
        <Border Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
          <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>

Lub jeśli nie potrzebujesz TemplateBindings:

<Style TargetType="{x:Type DataGridCell}">
   <Setter Property="Template">
      <Setter.Value>
          <ControlTemplate TargetType="{x:Type DataGridCell}">
              <Border Padding="4">
                  <ContentPresenter />
              </Border>
          </ControlTemplate>
      </Setter.Value>
  </Setter>
</Style>
JayGee
źródło
28
Dzięki JayGee, ale to rozwiązanie jest dla DataGrid - a nie standardowego kontrolki Grid.
Brad Leach,
Kliknięcie spacji wypełnienia nie powoduje już zaznaczenia wiersza.
jor
9

Pomyślałem, że dodam własne rozwiązanie, ponieważ nikt jeszcze o tym nie wspomniał. Zamiast projektować UserControl w oparciu o Grid, możesz kierować kontrolki zawarte w siatce z deklaracją stylu. Dba o dodanie dopełnienia / marginesu do wszystkich elementów bez konieczności definiowania dla każdego z nich, co jest uciążliwe i pracochłonne.Na przykład, jeśli twoja siatka zawiera tylko TextBlocks, możesz to zrobić:

<Style TargetType="{x:Type TextBlock}">
    <Setter Property="Margin" Value="10"/>
</Style>

Co jest odpowiednikiem „wypełniania komórek”.

James M.
źródło
czy to spływa? na przykład jeśli masz stackpanel w wierszu siatki, czy elementy potomne textblock panelu stosu dziedziczą tę właściwość?
Maslow
Nie jestem pewien, czy przedostaje się przez najbliższe dzieci, ale możesz się tego dowiedzieć za pomocą prostego testu.
James M
2
@Maslow Odpowiedź brzmi absolutnie „tak”, ale twoje sformułowanie jest trochę mylące. Nie ma żadnego „dziedziczenia” Marginwłaściwości, chodzi o to, że każda Stylez a ResourceDictionarybędzie miała zastosowanie do każdego jej elementu TargetTypewszędzie w całym zakresie elementu właściciela tego słownika. Więc to, Styleco się ścieka, a nie własność.
Glenn Slayden,
8

Nie jest to strasznie trudne. Nie potrafię powiedzieć, jak trudne było to w 2009 roku, kiedy padło pytanie, ale to było wtedy.

Zwróć uwagę, że jeśli jawnie ustawisz margines bezpośrednio na element podrzędny siatki podczas korzystania z tego rozwiązania, ten margines pojawi się w projektancie, ale nie w czasie wykonywania.

Tę właściwość można zastosować do Grid, StackPanel, WrapPanel, UniformGrid lub dowolnego innego elementu podrzędnego Panel. Wpływa na najbliższe dzieci. Zakłada się, że dzieci będą chciały zarządzać układem własnych treści.

PanelExt.cs

public static class PanelExt
{
    public static Thickness? GetChildMargin(Panel obj)
    {
        return (Thickness?)obj.GetValue(ChildMarginProperty);
    }

    public static void SetChildMargin(Panel obj, Thickness? value)
    {
        obj.SetValue(ChildMarginProperty, value);
    }

    /// <summary>
    /// Apply a fixed margin to all direct children of the Panel, overriding all other margins.
    /// Panel descendants include Grid, StackPanel, WrapPanel, and UniformGrid
    /// </summary>
    public static readonly DependencyProperty ChildMarginProperty =
        DependencyProperty.RegisterAttached("ChildMargin", typeof(Thickness?), typeof(PanelExt),
            new PropertyMetadata(null, ChildMargin_PropertyChanged));

    private static void ChildMargin_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = d as Panel;

        target.Loaded += (s, e2) => ApplyChildMargin(target, (Thickness?)e.NewValue);

        ApplyChildMargin(target, (Thickness?)e.NewValue);
    }

    public static void ApplyChildMargin(Panel panel, Thickness? margin)
    {
        int count = VisualTreeHelper.GetChildrenCount(panel);

        object value = margin.HasValue ? margin.Value : DependencyProperty.UnsetValue;

        for (var i = 0; i < count; ++i)
        {
            var child = VisualTreeHelper.GetChild(panel, i) as FrameworkElement;

            if (child != null)
            {
                child.SetValue(FrameworkElement.MarginProperty, value);
            }
        }
    }
}

Próbny:

MainWindow.xaml

<Grid
    local:PanelExt.ChildMargin="2"
    x:Name="MainGrid"
    >
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Rectangle Width="100" Height="40" Fill="Red" Grid.Row="0" Grid.Column="0" />
    <Rectangle Width="100" Height="40" Fill="Green" Grid.Row="1" Grid.Column="0" />
    <Rectangle Width="100" Height="40" Fill="Blue" Grid.Row="1" Grid.Column="1" />

    <Button Grid.Row="2" Grid.Column="0" Click="NoMarginClick">No Margin</Button>
    <Button Grid.Row="2" Grid.Column="1" Click="BigMarginClick">Big Margin</Button>
    <ComboBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" />
</Grid>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void NoMarginClick(object sender, RoutedEventArgs e)
    {
        //  In real life, if we wanted to change PanelExt.ChildMargin at runtime, we 
        //  would prefer to bind it to something, probably a dependency property of 
        //  the view. But this will do for a demonstration. 
        PanelExt.SetChildMargin(MainGrid, null);
    }
    private void BigMarginClick(object sender, RoutedEventArgs e)
    {
        PanelExt.SetChildMargin(MainGrid, new Thickness(20));
    }
}

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

15ee8f99-57ff-4f92-890c-b56153
źródło
Czy wiesz, dlaczego Twoja odpowiedź została odrzucona? Wygląda na to, działającego rozwiązania problemu OP
Thesystem
4
@thesystem Myślę, że ktoś się o coś zdenerwował. Nic takiego. Ale może jest w tym błąd, którego przegapiłem. Zdarza się.
15ee8f99-57ff-4f92-890c-b56153
dostałem dw, ponieważ wymaga buildlub runprzed wyświetleniem podglądu na żywo? Dobry i prosty. 1 w górę.
Meow Cat 2012,
3

Niedawno napotkałem ten problem podczas tworzenia oprogramowania i przyszło mi do głowy zapytać DLACZEGO? Dlaczego to zrobili ... odpowiedź była tuż przede mną. Wiersz danych jest obiektem, więc jeśli zachowamy orientację obiektową, projekt dla określonego wiersza powinien zostać oddzielony (przypuśćmy, że będziesz musiał ponownie użyć wyświetlania wiersza później w przyszłości). Zacząłem więc używać paneli stosu danych i niestandardowych elementów sterujących dla większości wyświetlaczy danych. Sporadycznie pojawiały się listy, ale najczęściej siatka była używana tylko do organizacji strony głównej (nagłówek, obszar menu, obszar zawartości, inne obszary). Twoje obiekty niestandardowe mogą z łatwością zarządzać dowolnymi wymaganiami dotyczącymi odstępów dla każdego wiersza w panelu stosu lub siatce (pojedyncza komórka siatki może zawierać cały obiekt wiersza. Dodatkową zaletą jest prawidłowe reagowanie na zmiany orientacji,

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
  </Grid.RowDefinitions>

  <custom:MyRowObject Style="YourStyleHereOrGeneralSetter" Grid.Row="0" />
  <custom:MyRowObject Style="YourStyleHere" Grid.Row="1" />
</Grid>

lub

<StackPanel>
  <custom:MyRowObject Style="YourStyleHere" Grid.Row="0" />
  <custom:MyRowObject Style="YourStyleHere" Grid.Row="1" />
</StackPanel>

Twoje niestandardowe kontrolki odziedziczą również DataContext, jeśli używasz powiązania danych ... moja ulubiona korzyść z tego podejścia.

Matt Meikle
źródło
To, co próbujesz tutaj zrobić, to utworzenie prymitywnej wersji ListViewformantu WPF w GridViewtrybie, ale bez przepisu na to, aby wszystkie „RowObjects” miały tę samą szerokość kolumny. W rzeczywistości nie ma to już wiele wspólnego z siatką.
Glenn Slayden,
3

na platformie UWP (wersja Windows10FallCreatorsUpdate i nowsze)

<Grid RowSpacing="3" ColumnSpacing="3">
Kursat Turkay
źródło
Dotyczy to również Xamarin.Forms.
James M
3

Edytowano:

Aby nadać margines dowolnej kontroli, można ją owinąć obramowaniem w ten sposób

<!--...-->
    <Border Padding="10">
            <AnyControl>
<!--...-->
Juan Pablo
źródło
Problem OP nie polega na tym, aby mieć margines wokół siatki, ale margines wokół komórek siatki (lub, jak faktycznie pytano wokół wierszy i kolumn, później zaprzeczono przez „ dodanie dodatkowych kolumn / wierszy jest dokładnie tym, czego starałem się uniknąć ” komentarz) .
min
2

Zrobiłem to teraz z jedną z moich siatek.

  • Najpierw zastosuj ten sam margines do każdego elementu wewnątrz siatki. Możesz to zrobić ręcznie, używając stylów lub cokolwiek chcesz. Załóżmy, że chcesz uzyskać odstęp w poziomie 6 pikseli i odstęp w pionie 2 piksele. Następnie dodajesz marginesy „3px 1px” do każdego elementu podrzędnego siatki.
  • Następnie usuń marginesy utworzone wokół siatki (jeśli chcesz wyrównać krawędzie elementów sterujących wewnątrz siatki do tego samego położenia siatki). Zrób to ustawiając margines „-3px -1px” do siatki. W ten sposób inne kontrolki poza siatką zostaną wyrównane z najbardziej zewnętrznymi kontrolkami wewnątrz siatki.
isierra
źródło
Wydaje się, że najłatwiejszym sposobem jest zastosowanie tego samego marginesu do każdego elementu wewnątrz siatki.
Envil
1

Jedną z możliwości byłoby dodanie wierszy i kolumn o stałej szerokości, które pełnią rolę dopełnienia / marginesu, którego szukasz.

Możesz również wziąć pod uwagę, że jesteś ograniczony rozmiarem kontenera i że siatka stanie się tak duża, jak element zawierający lub określoną szerokość i wysokość. Możesz po prostu użyć kolumn i wierszy bez ustawionej szerokości ani wysokości. W ten sposób domyślnie równomiernie dzielą całkowitą przestrzeń w siatce. Wtedy wystarczyłoby wyśrodkować elementy w pionie i poziomie w obrębie siatki.

Inną metodą może być zawinięcie wszystkich elementów siatki w ustaloną siatkę z jednym wierszem i kolumną, która ma stały rozmiar i marginesy. Twoja siatka zawiera pola o stałej szerokości / wysokości, które zawierają Twoje rzeczywiste elementy.

Adam Lenda
źródło
1
Dzięki Adam, jednak dodawanie dodatkowych kolumn / wierszy jest dokładnie tym, czego starałem się uniknąć. Chcę po prostu zredukować moje znaczniki, a możliwość określenia marginesu lub wypełnienia pomogłaby w tym.
Brad Leach,
Musisz tylko zdefiniować dodatkowe kolumny i wiersze, a nie dodawać do nich znacznika. Na przykład, jeśli chcesz dodać szerokość N między kolumnami dla 3 kolumn, będziesz mieć 5 definicji kolumn. Pierwszą byłaby szerokość automatyczna, a następna stała, potem automatyczna, potem stała, a potem automatyczna. Następnie przypisz tylko kolumny 1, 3 i 5 do rzeczywistych elementów treści. Rozumiem twój punkt widzenia na temat dodatkowych znaczników w kolumnie, ale jeśli nie masz setek elementów, wydaje mi się to trywialne. jednak twoja decyzja. Czy próbowałeś również użyć panelu stosu paneli stosu z ustawionymi marginesami?
Adam Lenda
W moim przypadku dodanie kolumny „rozdzielacz / margines” było zdecydowanie najczystsze i najłatwiejsze. Dzięki!
jchristof
1

Miałem ostatnio podobny problem w siatce dwóch kolumn, potrzebowałem marginesu na elementach tylko w prawej kolumnie. Wszystkie elementy w obu kolumnach były typu TextBlock.

<Grid.Resources>
    <Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource OurLabelStyle}">
        <Style.Triggers>
            <Trigger Property="Grid.Column" Value="1">
                <Setter Property="Margin" Value="20,0" />
            </Trigger>
        </Style.Triggers>
    </Style>
</Grid.Resources>
Miloš Auder
źródło
0

Chociaż nie możesz dodać marginesu lub wypełnienia do siatki, możesz użyć czegoś takiego jak Frame (lub podobnego kontenera), do którego możesz go zastosować.

W ten sposób (jeśli pokażesz lub ukryjesz kontrolkę po kliknięciu przycisku, powiedz), nie będziesz musiał dodawać marginesu do każdej kontrolki, która może z nią współdziałać.

Pomyśl o tym jak o wyodrębnieniu grup kontrolek w jednostki, a następnie zastosowaniu stylu do tych jednostek.

ed13
źródło
0

Jak wspomniano wcześniej, utwórz klasę GridWithMargins. Oto mój działający przykład kodu

public class GridWithMargins : Grid
{
    public Thickness RowMargin { get; set; } = new Thickness(10, 10, 10, 10);
    protected override Size ArrangeOverride(Size arrangeSize)
    {
        var basesize = base.ArrangeOverride(arrangeSize);

        foreach (UIElement child in InternalChildren)
        {
            var pos = GetPosition(child);
            pos.X += RowMargin.Left;
            pos.Y += RowMargin.Top;

            var actual = child.RenderSize;
            actual.Width -= (RowMargin.Left + RowMargin.Right);
            actual.Height -= (RowMargin.Top + RowMargin.Bottom);
            var rec = new Rect(pos, actual);
            child.Arrange(rec);
        }
        return arrangeSize;
    }

    private Point GetPosition(Visual element)
    {
        var posTransForm = element.TransformToAncestor(this);
        var areaTransForm = posTransForm.Transform(new Point(0, 0));
        return areaTransForm;
    }
}

Stosowanie:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApplication1"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:GridWithMargins ShowGridLines="True">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Rectangle Fill="Red" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
            <Rectangle Fill="Green" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
            <Rectangle Fill="Blue" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
        </local:GridWithMargins>
    </Grid>
</Window>
Paul Baxter
źródło
1
Zgłasza System.ArgumentException: „Szerokość musi być nieujemna”. current.Width - = Math.Min (current.Width, RowMargin.Left + RowMargin.Right); current.Height - = Math.Min (tual.Height, RowMargin.Top + RowMargin.Bottom);
Jamie Mellway
Z poprawką Jamiego działa, ale nadal nie robi tego, co powinien. Gdy wysokość wiersza i szerokość kolumny są ustawione na Auto, robi to źle w inny sposób.
15ee8f99-57ff-4f92-890c-b56153
0

Dziwię się, że jeszcze nie widziałem tego rozwiązania.

Pochodzące z sieci frameworki takie jak bootstrap będą używać ujemnego marginesu do wycofywania wierszy / kolumn.

Może być trochę gadatliwy (choć nie taki zły), działa, a elementy są równomiernie rozmieszczone i dopasowane.

W poniższym przykładzie używam StackPanelkorzenia, aby zademonstrować, jak 3 przyciski są równomiernie rozmieszczone przy użyciu marginesów. Możesz użyć innych elementów, po prostu zmień wewnętrzny x: Type z przycisku na swój element.

Pomysł jest prosty, użyj siatki na zewnątrz, aby wyciągnąć marginesy elementów poza ich granice o połowę wielkości siatki wewnętrznej (używając marginesów ujemnych), użyj siatki wewnętrznej, aby równomiernie rozmieścić elementy w żądanej ilości.

Aktualizacja: jakiś komentarz od użytkownika powiedział, że to nie działa, oto krótki film demonstrujący: https://youtu.be/rPx2OdtSOYI

wprowadź opis obrazu tutaj

    <StackPanel>
        <Grid>
            <Grid.Resources>
                <Style TargetType="{x:Type Grid}">
                    <Setter Property="Margin" Value="-5 0"/>
                </Style>
            </Grid.Resources>

            <Grid>
                <Grid.Resources>
                    <Style TargetType="{x:Type Button}">
                        <Setter Property="Margin" Value="10 0"/>
                    </Style>
                </Grid.Resources>

                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Button Grid.Column="0" Content="Btn 1" />
                <Button Grid.Column="1" Content="Btn 2" />
                <Button Grid.Column="2" Content="Btn 3" />
            </Grid>

        </Grid>

        <TextBlock FontWeight="Bold" Margin="0 10">
            Test
        </TextBlock>
    </StackPanel>
AntonB
źródło
1
Mój błąd - nie zauważyłem ukrytego stylu przycisku. Odwróciło mnie trochę o ujemnych marżach.
15ee8f99-57ff-4f92-890c-b56153
-6

Czasami prosta metoda jest najlepsza. Po prostu dopełnij struny spacjami. Jeśli jest to tylko kilka pól tekstowych itp., Jest to zdecydowanie najprostsza metoda.

Możesz także po prostu wstawić puste kolumny / wiersze o stałym rozmiarze. Niezwykle proste i możesz je łatwo zmienić.

rolki
źródło
Prawdopodobnie dlatego, że nie jest to zbyt dobra metoda. Używanie spacji może być „łatwym wyjściem”, ale w rzeczywistości jest to raczej hack. Nie jest precyzyjny, może (i prawdopodobnie będzie) renderować się inaczej i niezawodnie na monitorach z innym skalowaniem i nie masz kontroli nad dokładną ilością utworzonej przestrzeni. Wstawienie pustych kolumn / wierszy spowoduje bałagan w siatce ...
Oznacz