WPF: Grille avec marge de colonne / ligne / remplissage?


137

Est-il facilement possible de spécifier une marge et / ou un remplissage pour les lignes ou les colonnes d'une grille WPF?

Je pourrais bien sûr ajouter des colonnes supplémentaires pour espacer les choses, mais cela semble être un travail pour le remplissage / les marges (cela donnera du XAML beaucoup plus simple). Quelqu'un a-t-il dérivé de la grille standard pour ajouter cette fonctionnalité?


4
Un exemple utile que vous pouvez trouver ici: codeproject.com/Articles/107468/WPF-Padded-Grid
peter70

2
Un peu perplexe que cela ne fasse pas partie des fonctionnalités de base de la grille ...
uceumern

À partir d'aujourd'hui, démontré par 10 ans de réponses, la vérité est que ce n'est pas possible facilement, et le mieux à faire (pour éviter un travail supplémentaire sujet aux erreurs chaque fois qu'une cellule est utilisée) est de dériver Grid (comme suggéré précédemment par @ peter70 ) pour ajouter la propriété de dépendance de remplissage de cellule appropriée qui contrôlera la propriété Margin de l'enfant de cellule. Ce n'est pas une tâche longue et vous obtenez un contrôle réutilisable. Côté commentaire ... Grid est vraiment un contrôle mal conçu.
min

Réponses:


10

Vous pouvez écrire votre propre GridWithMarginclasse, héritée de Grid, et remplacer la ArrangeOverrideméthode pour appliquer les marges


32
Je ne comprends pas pourquoi les gens vous abandonnent parce que c'est loin d'être aussi simple que vous le pensez / décrivez ici. Essayez simplement de le faire 30 minutes et vous verrez rapidement que votre réponse est inapplicable. Pensez aussi à la répartition des lignes et des colonnes
Eric Ouellet

89

RowDefinitionet ColumnDefinitionsont de type ContentElement, et Marginest strictement une FrameworkElementpropriété. Donc, à votre question, «est-ce facilement possible», la réponse est un non des plus catégoriques. Et non, je n'ai vu aucun panneau de mise en page qui démontre ce type de fonctionnalité.

Vous pouvez ajouter des lignes ou des colonnes supplémentaires comme vous l'avez suggéré. Mais vous pouvez également définir des marges sur un Gridélément lui-même, ou tout ce qui pourrait entrer dans un Grid, c'est donc votre meilleure solution de contournement pour le moment.


OP ne tente pas de définir une marge sur RowDefinition ou ColumnDefinition. Il tente de définir une marge sur les enfants visuels de la grille, qui dérivent de FrameworkElement.
15ee8f99-57ff-4f92-890c-b56153

58

Utilisez un Bordercontrôle en dehors du contrôle de cellule et définissez le remplissage pour cela:

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


La source:


2
@RichardEverett Check the Way Back Machine: lien mis à jour dans la réponse.
CJBS

J'aime mieux cette réponse. Il fournit une solution à la question initiale et c'est simple. Merci!
Tobias

C'est facile et pratique
aggsol

19

Vous pouvez utiliser quelque chose comme ceci:

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

Ou si vous n'avez pas besoin des 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
Merci JayGee, mais cette solution est pour le DataGrid - pas le contrôle Grid standard.
Brad Leach le

Cliquer sur l'espace de remplissage ne sélectionne plus la ligne.
jor

9

J'ai pensé que j'ajouterais ma propre solution parce que personne n'a encore mentionné cela. Au lieu de concevoir un UserControl basé sur Grid, vous pouvez cibler les contrôles contenus dans la grille avec une déclaration de style. Prend soin d'ajouter padding / margin à tous les éléments sans avoir à définir pour chacun, ce qui est lourd et laborieux.Par exemple, si votre Grid ne contient rien d'autre que des TextBlocks, vous pouvez le faire:

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

Ce qui est comme l'équivalent du «remplissage de cellules».


est-ce que ça coule? par exemple, si vous avez un stackpanel dans une ligne de grille, les enfants textblock du stackpanel héritent-ils de cette propriété?
Maslow

Je ne sais pas si cela passe devant les enfants immédiats, mais vous pouvez le découvrir avec un simple test.
James M

2
@Maslow La réponse est absolument «oui», mais votre formulation est un peu trompeuse. Il n'y a pas «d'héritage» de la Marginpropriété, c'est que n'importe lequel Styledans un ResourceDictionarys'appliquera à chaque élément de son TargetTypepartout dans la portée entière de l'élément propriétaire de ce dictionnaire. C'est donc ça Stylequi coule, pas la propriété.
Glenn Slayden du

8

Ce n'est pas terriblement difficile. Je ne peux pas dire à quel point c'était difficile en 2009 lorsque la question a été posée, mais c'était alors.

Notez que si vous définissez explicitement une marge directement sur un enfant de la grille lors de l'utilisation de cette solution, cette marge apparaîtra dans le concepteur mais pas au moment de l'exécution.

Cette propriété peut être appliquée à Grid, StackPanel, WrapPanel, UniformGrid ou tout autre descendant de Panel. Cela affecte les enfants immédiats. Il est présumé que les enfants voudront gérer la mise en page de leur propre contenu.

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

Démo:

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

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici


Savez-vous pourquoi votre réponse est rejetée? Semble être une solution de travail au problème d'OP
système

4
@thesystem Je pense que quelqu'un s'est énervé à propos de quelque chose. Pas grand-chose. Mais peut-être qu'il y a un bug là-dedans que j'ai manqué. Ça arrive.
15ee8f99-57ff-4f92-890c-b56153

obtenu dw parce qu'il nécessite un buildou runavant que l'aperçu en direct puisse être affiché? Bon et simple. 1 vers le haut.
Meow Cat 2012

3

J'ai récemment rencontré ce problème en développant un logiciel et il m'est venu à l'idée de demander POURQUOI? Pourquoi ont-ils fait ça ... la réponse était juste là devant moi. Une ligne de données est un objet, donc si nous conservons l'orientation de l'objet, la conception d'une ligne particulière doit être séparée (supposons que vous deviez réutiliser l'affichage de la ligne plus tard dans le futur). J'ai donc commencé à utiliser des panneaux de pile de données et des contrôles personnalisés pour la plupart des affichages de données. Les listes ont fait des apparitions occasionnelles, mais la grille a surtout été utilisée uniquement pour l'organisation de la page principale (en-tête, zone de menu, zone de contenu, autres zones). Vos objets personnalisés peuvent facilement gérer les exigences d'espacement pour chaque ligne dans le panneau de pile ou la grille (une seule cellule de grille peut contenir la totalité de l'objet de ligne. Cela présente également l'avantage supplémentaire de réagir correctement aux changements d'orientation,

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

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

ou

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

Vos contrôles personnalisés hériteront également du DataContext si vous utilisez la liaison de données ... mon avantage personnel préféré de cette approche.


Ce que vous avez tenté de faire ici, c'est de créer une version brute du ListViewcontrôle WPF en GridViewmode, mais sans aucune disposition pour que tous les «RowObjects» partagent la même largeur de colonne. En réalité, cela n'a plus grand chose à voir avec une grille.
Glenn Slayden du

3

dans uwp (version Windows10FallCreatorsUpdate et supérieure)

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

Cela s'applique également avec Xamarin.Forms.
James M

3

Édité:

Pour donner une marge à n'importe quel contrôle, vous pouvez envelopper le contrôle avec une bordure comme celle-ci

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

Le problème OP n'est pas d'avoir une marge autour de la grille, mais une marge autour des cellules de la grille (ou comme demandé autour des lignes et des colonnes, plus tard contredite par "l' ajout de colonnes / lignes supplémentaires est exactement ce que j'essayais d'éviter " commentaire) .
min

2

Je l'ai fait maintenant avec l'une de mes grilles.

  • Appliquez d'abord la même marge à chaque élément à l'intérieur de la grille. Vous pouvez le faire manuellement, en utilisant des styles ou ce que vous voulez. Disons que vous voulez un espacement horizontal de 6px et un espacement vertical de 2px. Ensuite, vous ajoutez des marges de «3px 1px» à chaque enfant de la grille.
  • Supprimez ensuite les marges créées autour de la grille (si vous souhaitez aligner les bordures des contrôles à l'intérieur de la grille sur la même position de la grille). Faites cela en définissant une marge de "-3px -1px" à la grille. De cette façon, les autres contrôles en dehors de la grille seront alignés avec les contrôles les plus extérieurs à l'intérieur de la grille.

Appliquer la même marge à chaque élément à l'intérieur de la grille semble être le moyen le plus simple.
Envil

1

Une possibilité serait d'ajouter des lignes et des colonnes de largeur fixe pour agir comme le remplissage / marge que vous recherchez.

Vous pouvez également considérer que vous êtes contraint par la taille de votre conteneur et qu'une grille deviendra aussi grande que l'élément conteneur ou sa largeur et sa hauteur spécifiées. Vous pouvez simplement utiliser des colonnes et des lignes sans largeur ni hauteur. De cette façon, ils divisent par défaut de manière égale l'espace total dans la grille. Ensuite, il s'agirait simplement de centrer vos éléments verticalement et horizontalement dans votre grille.

Une autre méthode pourrait consister à envelopper tous les éléments de la grille dans une grille fixe avec une seule ligne et colonne qui a une taille et une marge fixes. Que votre grille contient des cases de largeur / hauteur fixes qui contiennent vos éléments réels.


1
Merci Adam, mais l'ajout de colonnes / lignes supplémentaires est exactement ce que j'essayais d'éviter. Je veux simplement réduire mon balisage et être capable de spécifier une marge ou un remplissage aiderait avec cela.
Brad Leach le

Il vous suffit de définir les colonnes et les lignes supplémentaires, et non de les ajouter. Par exemple, si vous souhaitez ajouter N largeur entre les colonnes pour 3 colonnes, vous auriez 5 définitions de colonne. Le premier serait la largeur automatique, et le suivant fixe, puis automatique, puis fixe, puis automatique. Attribuez ensuite les colonnes 1, 3 et 5 uniquement aux éléments de contenu réels. Je vois votre point sur le balisage de colonne supplémentaire, mais à moins que vous n'ayez des centaines d'éléments, cela me semble trivial. votre appel cependant. De plus, avez-vous essayé d'utiliser un panneau empilé de panneaux empilés avec les marges définies?
Adam Lenda

Pour mon cas, l'ajout d'une colonne "splitter / margin" était de loin le plus simple et le plus simple. Merci!
jchristof

1

J'ai eu un problème similaire récemment dans la grille à deux colonnes, j'avais besoin d'une marge sur les éléments de la colonne de droite uniquement. Tous les éléments des deux colonnes étaient de type 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

Bien que vous ne puissiez pas ajouter de marge ou de remplissage à une grille, vous pouvez utiliser quelque chose comme un cadre (ou un conteneur similaire), auquel vous pouvez l'appliquer.

De cette façon (si vous affichez ou masquez le contrôle lors d'un clic sur un bouton, par exemple), vous n'aurez pas besoin d'ajouter de marge sur chaque contrôle susceptible d'interagir avec lui.

Pensez-y comme isoler les groupes de contrôles en unités, puis appliquer un style à ces unités.


0

Comme indiqué précédemment, créez une classe GridWithMargins. Voici mon exemple de code de travail

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

Usage:

<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
Lève System.ArgumentException: "La largeur doit être non négative." actual.Width - = Math.Min (actual.Width, RowMargin.Left + RowMargin.Right); actual.Height - = Math.Min (actual.Height, RowMargin.Top + RowMargin.Bottom);
Jamie Mellway

Avec le correctif de Jamie, il fonctionne, mais ne fait toujours pas ce qu'il devrait faire. Avec la hauteur de ligne et la largeur de colonne définies sur Auto, il fait la mauvaise chose d'une manière différente.
15ee8f99-57ff-4f92-890c-b56153

0

Je suis surpris de ne pas avoir encore vu cette solution publiée.

Venant du Web, les frameworks comme bootstrap utiliseront une marge négative pour retirer les lignes / colonnes.

Cela peut être un peu verbeux (mais pas si mal), cela fonctionne et les éléments sont uniformément espacés et dimensionnés.

Dans l'exemple ci-dessous, j'utilise une StackPanelracine pour montrer comment les 3 boutons sont régulièrement espacés en utilisant des marges. Vous pouvez utiliser d'autres éléments, il suffit de changer le x: Type interne du bouton à votre élément.

L'idée est simple, utilisez une grille à l'extérieur pour extraire les marges des éléments de leurs limites de moitié par rapport à la grille intérieure (en utilisant des marges négatives), utilisez la grille intérieure pour espacer uniformément les éléments avec le montant souhaité.

Mise à jour: un commentaire d'un utilisateur dit que cela ne fonctionne pas, voici une rapide vidéo de démonstration: https://youtu.be/rPx2OdtSOYI

entrez la description de l'image ici

    <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
Mon erreur - je n'ai pas remarqué le style implicite Button. J'ai été distrait par le peu de marges négatives.
15ee8f99-57ff-4f92-890c-b56153

-6

Parfois, la méthode simple est la meilleure. Remplissez simplement vos chaînes d'espaces. S'il ne s'agit que de quelques zones de texte, etc., c'est de loin la méthode la plus simple.

Vous pouvez également simplement insérer des colonnes / lignes vides avec une taille fixe. Extrêmement simple et vous pouvez facilement le changer.


Probablement parce que ce n'est pas une très bonne méthode. Utiliser des espaces peut être la "solution de facilité", mais c'est plus un hack en fait. Ce n'est pas précis, il pourrait (et le sera probablement) rendre différemment et de manière peu fiable sur des moniteurs avec une mise à l'échelle différente et vous n'avez aucun contrôle sur la quantité exacte d'espace que vous créez. L'insertion de colonnes / lignes vides va faire un désordre de votre grille ...
Mark
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.