WPF: Сетка с полем столбец / строка / отступы?

137

Можно ли легко указать поля и / или отступы для строк или столбцов в сетке WPF?

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

Брэд Лич
источник
4
Один полезный пример вы можете найти здесь: codeproject.com/Articles/107468/WPF-Padded-Grid
peter70
2
Кинда озадачил, что это не является частью базовых функций Grid ...
uceumern
На сегодняшний день, как показали 10-летние ответы, правда в том, что это не легко сделать, и лучшее, что нужно сделать (чтобы избежать дополнительной работы, склонной к ошибкам при каждом использовании ячейки), - это получить Grid (как предлагалось ранее @ peter70 ) добавить соответствующее свойство зависимости заполнения ячеек, которое будет управлять свойством Margin дочернего элемента ячейки. Это не долгое задание, и тогда вы получаете многоразовый контроль. Дополнительный комментарий ... Сетка - действительно плохо спроектированный элемент управления.
мин

Ответы:

10

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

Томас Левеск
источник
32
Я не понимаю, почему люди дают вам большие пальцы, потому что это не так просто, как вы думаете / описываете здесь. Просто попытайтесь сделать это 30 минут, и вы быстро увидите, что ваш ответ неприменим. Также подумайте о
Эрик
89

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

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

Чарли
источник
OP не пытается установить поле для RowDefinition или ColumnDefinition. Он пытается установить поле для визуальных дочерних элементов сетки, которые являются производными от FrameworkElement.
15ee8f99-57ff-4f92-890c-b56153
58

Используйте элемент 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>


Источник:

Самед
источник
2
@RichardEverett Check the Back Back Machine: ссылка обновлена ​​в ответе.
CJBS
Мне больше нравится этот ответ. Это обеспечивает решение исходного вопроса, и это прямо вперед. Спасибо!
Тобиас
Это легко и практично
aggsol
19

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

<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>
JayGee
источник
28
Спасибо JayGee, однако это решение для DataGrid, а не стандартного элемента управления Grid.
Брэд Лич
Нажатие на область отступа больше не выделяет строку.
Джор
9

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

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

Что похоже на «заполнение ячейки».

Джеймс М
источник
это сочится? например, если у вас есть панель стека в строке сетки, наследуют ли дочерние элементы текстовой блока этой панели?
Маслоу
Не уверен, что это проходит мимо ближайших детей, но вы можете узнать с помощью простого теста.
Джеймс М
2
@Maslow Ответ абсолютно «Да», но ваша формулировка немного вводит в заблуждение. Не происходит никакого «наследования» Marginсвойства, оно заключается в том, что любое из Stylein ResourceDictionaryбудет применяться к каждому элементу его TargetTypeвезде во всей области действия элемента-владельца этого словаря. Так что это то, Styleчто сочится, а не собственность.
Гленн
8

Это не очень сложно. Не могу сказать, насколько сложно было в 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);
            }
        }
    }
}

Демо-версия:

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

введите описание изображения здесь

введите описание изображения здесь

введите описание изображения здесь

15ee8f99-57ff-4f92-890c-b56153
источник
Знаете ли вы, почему ваш ответ опущен? Похоже , рабочего решения проблемы ФП в
Thesystem
4
@thesystem Я думаю, что кто-то что-то расстроился. Не так уж и важно. Но, возможно, в этом есть ошибка, которую я пропустил. Такое случается.
15ee8f99-57ff-4f92-890c-b56153
получил dw, потому что он требует buildили, runпрежде чем живой предварительный просмотр может быть отображен? Хороший и простой. 1 вверх
Мяу Кошка 2012
3

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

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

Мэтт Мейкл
источник
То, что вы пытались сделать здесь, это создать грубую версию элемента управления WPF ListViewв GridViewрежиме, но без каких-либо условий, чтобы все «RowObjects» имели одинаковую ширину столбца. В действительности это больше не имеет ничего общего с сеткой.
Гленн
3

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

<Grid RowSpacing="3" ColumnSpacing="3">
Курсат Туркай
источник
Это также относится и к Xamarin.Forms.
Джеймс М,
3

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

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

<!--...-->
    <Border Padding="10">
            <AnyControl>
<!--...-->
Хуан Пабло
источник
Проблема OP заключается не в том, чтобы иметь поле вокруг сетки, а в ячейках сетки (или, как на самом деле спрашивали вокруг строк и столбцов, позже опровергнув слова « добавление дополнительных столбцов / строк - это именно то, чего я пытался избежать ») ,
мин
2

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

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

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

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

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

Адам Ленда
источник
1
Спасибо Адам, однако добавление дополнительных столбцов / строк - это именно то, чего я пытался избежать. Я просто хочу уменьшить свою разметку, и возможность указать поле или отступ поможет в этом.
Брэд Лич
Вам нужно только определить дополнительные столбцы и строки, но не добавлять для них разметку. Например, если вы хотите добавить N ширины между столбцами для 3 столбцов, у вас будет 5 определений столбцов. Первым будет auto width, а следующий фиксированный, затем auto, затем фиксированный, затем auto. Затем назначьте только столбцы 1, 3 и 5 фактическим элементам содержимого. Я понимаю вашу точку зрения о дополнительной разметке столбцов, но если у вас нет сотен элементов, это кажется мне тривиальным. хотя ваш звонок Кроме того, вы пытались использовать панель стека панелей стека с установленными полями?
Адам Ленда
В моем случае добавление столбца «splitter / margin» было самым чистым и простым. Спасибо!
jchristof
1

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

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

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

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

ED13
источник
0

Как было сказано ранее, создайте класс 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>
Пол Бакстер
источник
1
Выдает System.ArgumentException: ширина должна быть неотрицательной. actual.Width - = Math.Min (actual.Width, RowMargin.Left + RowMargin.Right); actual.Height - = Math.Min (actual.Height, RowMargin.Top + RowMargin.Bottom);
Джейми Мелуэй
С исправлением Джейми, он работает, но все еще не делает то, что должен. Если для высоты строки и ширины столбца задано значение «Авто», это не так.
15ee8f99-57ff-4f92-890c-b56153
0

Я удивлен, что еще не видел этого решения.

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

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

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

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

Обновление: в некоторых комментариях от пользователя сказано, что это не работает, вот краткое видео, демонстрирующее: 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>
AntonB
источник
1
Моя ошибка - я не заметил неявного стиля Баттона. Я был отвлечен немного о отрицательных краях.
15ee8f99-57ff-4f92-890c-b56153
-6

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

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

роллы
источник
Возможно, потому что это не очень хороший метод. Использование пробелов может быть «легким выходом», но на самом деле это скорее взлом. Это не точно, это может (и, вероятно, будет) отображаться по-разному и ненадежно на мониторах с другим масштабированием, и вы не можете контролировать точный объем создаваемого пространства. Вставка пустых столбцов / строк испортит вашу сетку ...
Марк