Est-il facilement possible de spécifier une marge et/ou un remplissage pour des lignes ou des colonnes dans une grille WPF?
Je pourrais bien sûr ajouter des colonnes supplémentaires pour espacer, mais cela semble être un travail de remplissage/marges (cela donnera beaucoup plus simple XAML). Quelqu'un at-il dérivé de la grille standard pour ajouter cette fonctionnalité?
Vous pouvez écrire votre propre classe GridWithMargin
héritée de Grid
et remplacer la méthode ArrangeOverride
pour appliquer les marges.
RowDefinition
et ColumnDefinition
sont du type ContentElement
, et Margin
est strictement une propriété FrameworkElement
. Donc, à votre question, "est-ce facilement possible" la réponse est un non catégorique. Et non, je n'ai vu aucun panneau de présentation illustrant 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 élément Grid
lui-même, ou sur tout élément entrant dans une variable Grid
. C'est donc votre meilleure solution de contournement pour l'instant.
Utilisez un contrôle Border
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:
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 de 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>
Je pensais ajouter ma propre solution car personne ne l’avait encore mentionné. Au lieu de concevoir un contrôle UserControl basé sur la grille, vous pouvez cibler les contrôles contenus dans la grille avec une déclaration de style. Prend soin d’ajouter du rembourrage/des marges à tous les éléments sans avoir à les définir, ce qui est fastidieux et fastidieux. Par exemple, si votre grille ne contient que des TextBlocks, procédez comme suit:
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="10"/>
</Style>
Ce qui est comme l'équivalent de "remplissage de cellule".
Je me suis heurté à ce problème en développant récemment un logiciel et je me suis posé la question: POURQUOI? Pourquoi ont-ils fait cela… la réponse était devant moi. Une ligne de données est un objet. Par conséquent, si vous conservez 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 ultérieurement). J'ai donc commencé à utiliser des panneaux de pile liés à des données et des contrôles personnalisés pour la plupart des affichages de données. Les listes ont parfois fait leur apparition, mais la grille a principalement é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 du panneau de pile ou de la grille (une seule cellule de la grille peut contenir l’objet entier de la ligne. Cela présente également l’avantage de réagir correctement aux changements d’orientation, de développement/réduction, etc.
<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.
Je l'ai fait maintenant avec l'une de mes grilles.
dans uwp (version Windows10FallCreatorsUpdate et supérieure)
<Grid RowSpacing="3" ColumnSpacing="3">
Une possibilité serait d’ajouter des rangées et des colonnes de largeur fixe pour jouer le rôle de marge/remplissage que vous recherchez.
Vous pouvez également considérer que vous êtes limité par la taille de votre conteneur et qu'une grille devient aussi grande que l'élément contenant 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 l’espace total au sein de la grille. Ensuite, ce serait juste une question 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 grille à une ligne et une colonne ayant une taille et une marge fixes. Que votre grille contienne des zones largeur/hauteur fixes contenant vos éléments réels.
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>
J'avais un problème similaire récemment dans la grille à deux colonnes, j'avais besoin d'une marge sur les éléments dans 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>
Pour donner une marge à la grille, vous pouvez envelopper la grille avec une bordure comme celle-ci.
<!--...-->
<Border Padding="10">
<Grid>
<!--...-->
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 à l'époque.
Notez que si vous définissez explicitement une marge sur un enfant de la grille lors de l'utilisation de cette solution, cette marge apparaîtra dans le concepteur, mais pas lors de l'exécution.
Cette propriété peut être appliquée à Grid, StackPanel, WrapPanel, UniformGrid ou à n’importe quel autre descendant de Panel.
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)
{
PanelExt.SetChildMargin(MainGrid, null);
}
private void BigMarginClick(object sender, RoutedEventArgs e)
{
PanelExt.SetChildMargin(MainGrid, new Thickness(20));
}
}
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.
Ainsi, si vous affichez ou masquez le contrôle sur un clic de bouton, vous n'aurez pas besoin d'ajouter de marge sur chaque contrôle susceptible d'interagir avec celui-ci.
Pensez-y comme si vous isoliez les groupes de contrôles en unités, puis appliquez un style à ces unités.
Je suis surpris de ne pas avoir encore vu cette solution.
Venant du Web, les frameworks tels que bootstrap utilisent une marge négative pour extraire les lignes/colonnes.
Cela peut être un peu verbeux (bien que pas si mal), cela fonctionne et les éléments sont espacés et dimensionnés de manière égale.
Dans l'exemple ci-dessous, j'utilise une racine StackPanel
pour montrer comment les 3 boutons sont espacés régulièrement à l'aide de marges. Vous pouvez utiliser d'autres éléments, il suffit de changer le bouton x: Type de en votre élément.
L'idée est simple: utilisez une grille à l'extérieur pour extraire les marges des éléments de la moitié de la grille interne (en utilisant des marges négatives), utilisez la grille interne pour espacer les éléments avec la quantité souhaitée.
Mise à jour: Certains utilisateurs ont commenté que cela ne fonctionnait pas. Voici une vidéo montrant: https://youtu.be/rPx2OdtSOYI
<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>