WPF: сетка с полем столбца/строки/дополнением?

Легко ли указать поле и/или заполнение строк или столбцов в сетке WPF?

Я мог бы, конечно, добавить дополнительные столбцы в пробелы, но это похоже на работу для заполнения/полей (это даст гораздо более простой XAML). Кто-то получил из стандартной Grid, чтобы добавить эту функциональность?

Ответ 1

Вы можете написать свой собственный класс GridWithMargin, унаследованный от Grid, и переопределить метод ArrangeOverride для применения полей

Ответ 2

RowDefinition и ColumnDefinition имеют тип ContentElement, а Margin - строго a FrameworkElement. Итак, на ваш вопрос, "легко ли это возможно", ответ является наиболее определенным. И нет, я не видел никаких панелей макетов, которые демонстрируют такую ​​функциональность.

Вы можете добавить дополнительные строки или столбцы, как вы предложили. Но вы также можете установить поля для самого элемента Grid или всего, что попадет внутрь Grid, так что теперь ваш лучший способ решения проблемы.

Ответ 3

Используйте элемент управления Border вне элемента управления ячейки и определите для этого дополнение:

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


Источник:

Ответ 4

Вы можете использовать что-то вроде этого:

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

Или, если вам не нужны 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>

Ответ 5

Думал, что добавлю свое собственное решение, потому что никто еще не упомянул об этом. Вместо разработки UserControl на основе Grid вы можете настроить таргетинг на элементы управления, содержащиеся в сетке, с помощью объявления стиля. Принимает во внимание добавление отступов/полей для всех элементов без необходимости определять для каждого, что является громоздким и трудоемким. Например, если ваша сетка содержит ничего, кроме TextBlocks, вы можете сделать это:

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

Это похоже на эквивалент "заполнения ячейки".

Ответ 6

Это не очень сложно. Не могу сказать, насколько сложно было в 2009 году, когда был задан вопрос, но это было тогда.

Обратите внимание, что если вы явно задаете поле непосредственно для дочернего элемента сетки при использовании этого решения, это поле будет отображаться в конструкторе, но не во время выполнения.

Это свойство может быть применено к Grid, StackPanel, WrapPanel, UniformGrid или любому другому потомку 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);
            }
        }
    }
}

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)
    {
        PanelExt.SetChildMargin(MainGrid, null);
    }
    private void BigMarginClick(object sender, RoutedEventArgs e)
    {
        PanelExt.SetChildMargin(MainGrid, new Thickness(20));
    }
}

enter image description here

enter image description here

enter image description here

Ответ 7

Я столкнулся с этой проблемой при разработке программного обеспечения в последнее время, и мне потребовалось спросить ПОЧЕМУ? Почему они это сделали... ответ был прямо там передо мной. Строка данных - это объект, поэтому, если мы поддерживаем ориентацию объекта, тогда дизайн для определенной строки должен быть разделен (предположим, что вам нужно будет повторно использовать отображение строки позже в будущем). Поэтому я начал использовать панели стека данных и настраиваемые элементы управления для большинства дисплеев данных. Списки делали случайный внешний вид, но в основном сетка использовалась только для первичной организации страниц (Заголовок, Область меню, область содержимого, другие области). Ваши пользовательские объекты могут легко управлять любыми требованиями к интервалу для каждой строки в панели или сетке стека (одна ячейка сетки может содержать весь объект строки. Это также имеет дополнительное преимущество для правильной реакции на изменения ориентации, расширения/свертывания и т.д.

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

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

или

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

Пользовательские элементы управления также наследуют DataContext, если вы используете привязку данных... мое личное любимое преимущество этого подхода.

Ответ 8

Я сделал это прямо сейчас с одной из моих сетей.

  • Сначала примените один и тот же маркер к каждому элементу внутри сетки. Вы можете сделать это mannualy, используя стили или что угодно. Допустим, вам нужен горизонтальный интервал 6px и вертикальный интервал 2px. Затем вы добавляете поля "3px 1px" каждому дочернему элементу сетки.
  • Затем удалите поля, созданные вокруг сетки (если вы хотите выровнять границы элементов управления внутри сетки в том же положении сетки). Сделайте это, задав для сетки значение "-3px -1px". Таким образом, другие элементы управления вне сетки будут выровнены с внешними элементами управления внутри сетки.

Ответ 9

Одна из возможностей заключается в добавлении строк и столбцов фиксированной ширины, которые будут использоваться в качестве отступов/полей, которые вы ищете.

Вы также можете подумать, что вы ограничены размером вашего контейнера и что сетка станет такой же большой, как содержащий элемент, или указанная ширина и высота. Вы можете просто использовать столбцы и строки без установки ширины и высоты. Таким образом, они по умолчанию равномерно разбивают общее пространство внутри сетки. Тогда это будет просто центрировать ваши элементы вертикально и горизонтально внутри вашей сетки.

Другим методом может быть обертка всех элементов сетки в фиксированной сетке с одной строкой и столбцом, которая имеет фиксированный размер и маржу. То, что ваша сетка содержит фиксированные поля ширины/высоты, которые содержат ваши фактические элементы.

Ответ 10

в uwp (версия Windows10FallCreatorsUpdate и выше)

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

Ответ 11

Отредактировано:

Чтобы добавить поле любому элементу управления, вы можете обернуть элемент управления границей, например, так:

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

Ответ 12

Хотя вы не можете добавить маркер или дополнение к Grid, вы можете использовать что-то вроде фрейма (или аналогичного контейнера), к которому вы можете применить его.

Таким образом (если вы покажете или спрячете элемент управления нажатием кнопки, скажите), вам не нужно будет добавлять маркер для каждого элемента управления, который может взаимодействовать с ним.

Подумайте об этом как о разделении групп элементов управления на единицы, а затем примените стиль к этим единицам.

Ответ 13

Как было сказано ранее, создайте класс GridWithMargins. Вот мой пример рабочего кода

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

Использование:

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

Ответ 14

Я удивлен, что еще не видел это решение опубликовано.

Исходя из Интернета, фреймворки, такие как начальная загрузка, будут использовать отрицательное поле для возврата строк/столбцов.

Это может быть немного многословно (хотя и не так уж плохо), это работает, и элементы равномерно распределены и измерены.

В приведенном ниже примере я использую корень StackPanel чтобы продемонстрировать, как 3 кнопки равномерно распределены с помощью полей. Вы можете использовать другие элементы, просто измените внутренний x: Type с кнопки на ваш элемент.

Идея проста: использовать сетку снаружи, чтобы вытащить поля элементов за их пределы на половину внутренней сетки (с использованием отрицательных полей), используйте внутреннюю сетку, чтобы равномерно расставить элементы на желаемую сумму.

Обновление: некоторый комментарий от пользователя сказал, что это не работает, вот короткое видео, демонстрирующее: https://youtu.be/rPx2OdtSOYI

enter image description here

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

Ответ 15

Недавно у меня была похожая проблема в сетке из двух столбцов, мне нужно было поле только для элементов в правом столбце. Все элементы в обоих столбцах имели тип 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>

Ответ 16

Иногда простой метод является лучшим. Просто поместите свои строки с пробелами. Если это всего лишь несколько текстовых полей и т.д., Это самый простой способ.

Вы также можете просто вставить пустые столбцы/строки с фиксированным размером. Чрезвычайно просто, и вы можете легко изменить его.