WPF: griglia con margine colonna / riga / riempimento?


137

È possibile specificare facilmente un margine e / o un'imbottitura per righe o colonne in una griglia WPF?

Ovviamente potrei aggiungere colonne extra per sistemare le cose, ma questo sembra un lavoro per il riempimento / i margini (darà XAML molto più semplice). Qualcuno è derivato dalla griglia standard per aggiungere questa funzionalità?


4
Un utile esempio che puoi trovare qui: codeproject.com/Articles/107468/WPF-Padded-Grid
peter70

2
Kinda perplesso che questo non fa parte delle funzionalità di base della griglia ...
Uceumern

Ad oggi, dimostrato da 10 anni di risposte, la verità è che non è possibile facilmente, e la cosa migliore da fare (per evitare un ulteriore lavoro soggetto a errori ogni volta che viene utilizzata una cella) è derivare Grid (come suggerito in precedenza da @ peter70 ) per aggiungere la proprietà di dipendenza del riempimento di cella appropriata che controllerà la proprietà Margine del figlio di cella. Questo non è un compito lungo e quindi hai un controllo riutilizzabile. Commento laterale ... La griglia è davvero un controllo mal progettato.
minuti

Risposte:


10

È possibile scrivere la propria GridWithMarginclasse, ereditata da Gride sovrascrivere il ArrangeOverridemetodo per applicare i margini


32
Non capisco perché le persone ti danno il pollice in alto perché è tutt'altro che facile come pensi / descrivi qui. Prova a farlo 30 minuti e vedrai rapidamente che la tua risposta è inapplicabile. Pensa anche allo spanning di righe e colonne
Eric Ouellet,

89

RowDefinitione ColumnDefinitionsono di tipo ContentElemented Marginè rigorosamente una FrameworkElementproprietà. Quindi, alla tua domanda, "è facilmente possibile" la risposta è un no assolutamente definito. E no, non ho visto pannelli di layout che dimostrino questo tipo di funzionalità.

Puoi aggiungere righe o colonne extra come da te suggerito. Ma puoi anche impostare i margini su un Gridelemento stesso o su qualsiasi cosa possa andare all'interno di un Grid, quindi per ora è la soluzione migliore.


OP non sta tentando di impostare un margine su RowDefinition o ColumnDefinition. Sta tentando di stabilire un margine sui figli visivi della griglia, che derivano da FrameworkElement.
15ee8f99-57ff-4f92-890c-b56153

58

Utilizzare un Bordercontrollo esterno al controllo cella e definire il riempimento per quello:

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


Fonte:


2
@RichardEverett Check the Way Back Machine: link aggiornato in risposta.
CJBS,

Questa risposta mi piace di più. Fornisce una soluzione alla domanda originale ed è semplice. Grazie!
Tobias,

Questo è facile e pratico
aggsol

19

Puoi usare qualcosa del genere:

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

O se non hai bisogno di 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>

28
Grazie JayGee, tuttavia questa soluzione è per DataGrid, non per il controllo Grid standard.
Brad Leach,

Fare clic sullo spazio di riempimento non seleziona più la riga.
jor

9

Ho pensato di aggiungere la mia soluzione perché nessuno lo aveva ancora menzionato. Invece di progettare un UserControl basato su Grid, puoi indirizzare i controlli contenuti nella griglia con una dichiarazione di stile. Si prende cura di aggiungere riempimento / margine a tutti gli elementi senza dover definire per ciascuno, che è ingombrante e ad alta intensità di lavoro. Ad esempio, se la tua griglia non contiene altro che TextBlocks, puoi farlo:

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

Che è come l'equivalente di "imbottitura cellulare".


questo gocciola giù? ad esempio se si dispone di uno stackpanel in una riga della griglia, i figli del blocco di testo dello stackpanel ereditano questa proprietà?
Maslow,

Non sono sicuro se passa oltre i bambini immediati, ma puoi scoprirlo con un semplice test.
James M

2
@Maslow La risposta è assolutamente "Sì", ma le tue parole sono un po 'fuorvianti. Non c'è "ereditarietà" della Marginproprietà in corso, è che chiunque Stylein a ResourceDictionarysi applicherà a ogni suo elemento TargetTypeovunque all'interno dell'intero ambito dell'elemento proprietario di quel dizionario. Quindi è quello Styleche gocciola, non la proprietà.
Glenn Slayden,

8

Questo non è tremendamente difficile. Non posso dire quanto sia stato difficile nel 2009 quando è stata posta la domanda, ma è stato allora.

Si noti che se si imposta esplicitamente un margine direttamente su un figlio della griglia quando si utilizza questa soluzione, tale margine verrà visualizzato nella finestra di progettazione ma non in fase di esecuzione.

Questa proprietà può essere applicata a Grid, StackPanel, WrapPanel, UniformGrid o qualsiasi altro discendente di Panel. Colpisce i bambini immediati. Si presume che i bambini vorranno gestire il layout dei propri contenuti.

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);
            }
        }
    }
}

demo:

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));
    }
}

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine


Sai perché la tua risposta è sottoposta a downgrade? Sembra una soluzione funzionante al problema di OP
sistema l'

4
@thesystem Penso che qualcuno si sia arrabbiato per qualcosa. Non un grande affare. Ma forse c'è un bug che mi è mancato. Succede.
15ee8f99-57ff-4f92-890c-b56153

ottenuto dw perché richiede una buildo runprima che l'anteprima dal vivo possa essere visualizzata? Buono e semplice. 1 su.
Meow Cat 2012,

3

Ho riscontrato questo problema durante lo sviluppo di alcuni software di recente e mi è venuto in mente di chiedere PERCHÉ? Perché l'hanno fatto ... la risposta era proprio lì davanti a me. Una riga di dati è un oggetto, quindi se manteniamo l'orientamento dell'oggetto, allora il design per una particolare riga dovrebbe essere separato (supponiamo che tu debba riutilizzare la visualizzazione della riga in futuro). Così ho iniziato a utilizzare pannelli stack databound e controlli personalizzati per la maggior parte dei display di dati. Gli elenchi sono comparsi occasionalmente, ma principalmente la griglia è stata utilizzata solo per l'organizzazione della pagina principale (intestazione, area menu, area contenuto, altre aree). I tuoi oggetti personalizzati possono gestire facilmente qualsiasi requisito di spaziatura per ogni riga all'interno del pannello dello stack o della griglia (una singola cella della griglia può contenere l'intero oggetto riga. Ciò ha anche il vantaggio aggiuntivo di reagire correttamente ai cambiamenti di orientamento,

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

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

o

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

I controlli personalizzati erediteranno anche DataContext se si utilizza l'associazione dati ... il mio vantaggio personale preferito di questo approccio.


Quello che hai tentato di fare qui è creare una versione grezza del ListViewcontrollo WPF in GridViewmodalità, ma senza alcuna disposizione per fare in modo che tutti gli "RowObjects" condividano la stessa larghezza di colonna. In realtà, non ha più molto a che fare con una griglia.
Glenn Slayden,

3

in uwp (Windows 10FallCreatorsVersione versione e successive)

<Grid RowSpacing="3" ColumnSpacing="3">

Questo vale anche per Xamarin.Forms.
James M,

3

Modificato:

Per dare margine a qualsiasi controllo puoi avvolgere il controllo con un bordo come questo

<!--...-->
    <Border Padding="10">
            <AnyControl>
<!--...-->

Il problema OP non è quello di avere un margine attorno alla griglia, ma un margine attorno alle celle della griglia (o come effettivamente richiesto intorno alle righe e alle colonne, in seguito contraddetto da " aggiungere ulteriori colonne / righe è esattamente quello che stavo tentando di evitare " commento) .
Min

2

L'ho fatto proprio ora con una delle mie griglie.

  • Prima applica lo stesso margine a ogni elemento all'interno della griglia. Puoi farlo manualmente, usando gli stili o quello che ti piace. Supponiamo che tu voglia una spaziatura orizzontale di 6px e una spaziatura verticale di 2px. Quindi aggiungi i margini di "3px 1px" a ogni figlio della griglia.
  • Quindi rimuovere i margini creati intorno alla griglia (se si desidera allineare i bordi dei controlli all'interno della griglia nella stessa posizione della griglia). Effettuare questa impostazione di un margine di "-3px -1px" sulla griglia. In questo modo, altri controlli all'esterno della griglia verranno allineati con i controlli più esterni all'interno della griglia.

Applicare lo stesso margine su ogni elemento all'interno della griglia sembra essere il modo più semplice.
Envil,

1

Una possibilità sarebbe quella di aggiungere righe e colonne a larghezza fissa che fungano da riempimento / margine che stai cercando.

Potresti anche considerare che sei vincolato dalle dimensioni del tuo contenitore e che una griglia diventerà grande quanto l'elemento contenitore o la sua larghezza e altezza specificate. Puoi semplicemente usare colonne e righe senza larghezza o altezza impostate. In questo modo, di default, suddividono uniformemente lo spazio totale all'interno della griglia. Quindi sarebbe solo una questione di centrare i tuoi elementi verticalmente e orizzontalmente all'interno della griglia.

Un altro metodo potrebbe essere quello di avvolgere tutti gli elementi della griglia in una griglia fissa con riga e colonna singola con dimensioni e margine fissi. Che la griglia contenga caselle di larghezza / altezza fisse che contengono gli elementi effettivi.


1
Grazie Adam, tuttavia l'aggiunta di colonne / righe extra è esattamente ciò che stavo tentando di evitare. Voglio semplicemente ridurre il mio markup e essere in grado di specificare un margine o un'imbottitura sarebbe di aiuto.
Brad Leach,

Devi solo definire le colonne e le righe extra, non aggiungere il mark up per esse. Ad esempio, se si desidera aggiungere N larghezza tra le colonne per 3 colonne, si avrebbero 5 definizioni di colonna. Il primo sarebbe la larghezza automatica e il successivo fisso, quindi auto, quindi fisso, quindi auto. Quindi assegnare solo le colonne 1, 3 e 5 agli elementi di contenuto effettivi. Vedo il tuo punto sul markup della colonna aggiuntiva, ma a meno che tu non abbia centinaia di elementi, mi sembra banale. la tua chiamata però. Inoltre, hai provato a utilizzare un pannello stack di pannelli stack con i margini impostati?
Adam Lenda,

Nel mio caso l'aggiunta di una colonna "splitter / margin" è stata di gran lunga la più semplice e pulita. Grazie!
jchristof,

1

Recentemente ho avuto un problema simile nella griglia a due colonne, avevo bisogno di un margine sugli elementi solo nella colonna di destra. Tutti gli elementi in entrambe le colonne erano di tipo 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>

0

Sebbene non sia possibile aggiungere margine o riempimento a una griglia, è possibile utilizzare qualcosa come una cornice (o un contenitore simile), a cui è possibile applicarla.

In questo modo (se mostri o nascondi il controllo su un pulsante fai clic, ad esempio), non dovrai aggiungere margine su ogni controllo che potrebbe interagire con esso.

Pensalo come isolare i gruppi di controlli in unità, quindi applicare lo stile a quelle unità.


0

Come indicato prima, creare una classe GridWithMargins. Ecco il mio esempio di codice funzionante

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

Uso:

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

1
Genera System.ArgumentException: "La larghezza deve essere non negativa." actual.Width - = Math.Min (actual.Width, RowMargin.Left + RowMargin.Right); actual.Height - = Math.Min (actual.Height, RowMargin.Top + RowMargin.Bottom);
Jamie Mellway,

Con la correzione di Jamie, funziona, ma non fa ancora quello che dovrebbe. Con l'altezza della riga e la larghezza della colonna impostate su Auto, fa la cosa sbagliata in modo diverso.
15ee8f99-57ff-4f92-890c-b56153

0

Sono sorpreso di non aver ancora visto questa soluzione pubblicata.

Provenienti dal web, framework come bootstrap useranno un margine negativo per ritirare righe / colonne.

Potrebbe essere un po 'prolisso (anche se non così male), funziona e gli elementi sono equidistanti e dimensionati.

Nell'esempio seguente uso una StackPanelradice per dimostrare come i 3 pulsanti sono distribuiti uniformemente usando i margini. Puoi usare altri elementi, basta cambiare l'interno x: Type dal pulsante al tuo elemento.

L'idea è semplice, usa una griglia all'esterno per estrarre i margini degli elementi dai loro limiti di metà della quantità della griglia interna (usando i margini negativi), usa la griglia interna per spaziare uniformemente gli elementi con la quantità che desideri.

Aggiornamento: Alcuni commenti di un utente hanno detto che non funziona, ecco un breve video dimostrativo: https://youtu.be/rPx2OdtSOYI

inserisci qui la descrizione dell'immagine

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

1
Errore mio: non ho notato lo stile pulsante implicito. Ero un po 'distratto dai margini negativi.
15ee8f99-57ff-4f92-890c-b56153

-6

A volte il metodo semplice è il migliore. Riempi le corde con spazi. Se sono solo poche caselle di testo ecc, questo è di gran lunga il metodo più semplice.

Puoi anche semplicemente inserire colonne / righe vuote con una dimensione fissa. Estremamente semplice e puoi cambiarlo facilmente.


Probabilmente perché non è un metodo molto valido. L'uso degli spazi potrebbe essere la "via d'uscita facile", ma in realtà è più di un hack. Non è preciso, potrebbe (e probabilmente lo farà) renderizzare in modo diverso e sgradevole su monitor con un ridimensionamento diverso e non hai alcun controllo sull'esatta quantità di spazio che crei. L'inserimento di colonne / righe vuote creerà un pasticcio nella griglia ...
Segna il
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.