Подтвердить что ты не робот

Почему размер звезды во вложенной сетке не работает?

Рассмотрим следующий XAML:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <Grid Grid.Column="1">
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Button Content="B" Margin="5"/>
    <Button Content="Button" Grid.Column="1" Margin="5"/>
  </Grid>
</Grid>

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

Как я ожидаю, это будет работать следующим образом:

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

Но вместо этого ширины столбцов для вложенной сетки устанавливаются в соответствии с вычисленным минимальным размером каждой кнопки без видимой взвешенной зависимости между двумя столбцами звездного размера (для ясности показаны линии сетки):

incorrectly distributed column widths

Он работает как ожидалось, если у меня нет внешней сетки, т.е. просто сделайте внутреннюю сетку единственной сеткой в ​​окне:

correctly distributed column widths

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


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

enter image description here

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


Кажется, что каким-то образом, находясь внутри ячейки другого элемента управления Grid, изменяется способ определения размера звезды для внутреннего элемента управления Grid (или предотвращения изменения размера звезды). Но почему это должно быть? Я упускаю (еще раз) какое-то эзотерическое правило размещения WPF, которое объясняет это как поведение "по дизайну"? Или это просто ошибка в рамках?


Update:

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

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

т.е. этот XAML:

<Grid ShowGridLines="True">
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <!--
    <ColumnDefinition Width="Auto"/>
    -->
    <ColumnDefinition Width="60"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <Grid Grid.Column="1" ShowGridLines="True">
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Button Content="B" Margin="5"/>
    <Button Content="Button" Grid.Column="1" Margin="5"/>
  </Grid>
</Grid>

производит этот вывод:

enter image description here

Другими словами, в худшем случае я ожидаю, что WPF сначала вычислит минимальный размер внутренней сетки, не учитывая размер звезды (например, если короткая кнопка занимает 10 пикселей, а длинная кнопка - 70, то общая ширина будет равна 80), а затем равномерно распределить ширину столбцов (т.е. в примере 10/70 каждый столбец будет содержать 40 пикселей, усекается более длинная кнопка, аналогичная приведенному выше изображению).

Почему размер звезды иногда равномерно распределяет ширину по столбцам, а иногда нет?


Обновление # 2:

Вот простой пример, который ясно и драматично показывает, как WPF относится к размеру звезд по-разному в зависимости от того, будет ли он вычислять ширину Grid или вы:

<Window x:Class="TestGridLayout2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        SizeToContent="WidthAndHeight"
        Title="MainWindow">
  <Window.Resources>
    <Style TargetType="Border">
      <Setter Property="BorderBrush" Value="Black"/>
      <Setter Property="BorderThickness" Value="1"/>
    </Style>
    <Style TargetType="TextBlock">
      <Setter Property="FontSize" Value="24"/>
    </Style>
  </Window.Resources>
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"/>
      <ColumnDefinition Width="3*"/>
    </Grid.ColumnDefinitions>

    <Border Grid.Column="0"/>
    <Border Grid.Column="1"/>

    <StackPanel>
      <TextBlock Text="Some text -- one"/>
      <TextBlock Text="Some text -- two"/>
      <TextBlock Text="Some text -- three"/>
    </StackPanel>
    <StackPanel Grid.Column="1">
      <TextBlock Text="one"/>
      <TextBlock Text="two"/>
      <TextBlock Text="three"/>
    </StackPanel>
  </Grid>
</Window>

При запуске программы вы увидите следующее:

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

WPF проигнорировал размер звезды, установив минимальную ширину столбца. Если вы просто нажимаете на границу окна, как если бы для изменения размера окна (вам даже не нужно перетаскивать границу в любом месте), макет Grid будет переделан, и вы получите следующее:

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

В этот момент применяется значение размера звезды (как и следовало ожидать), и столбцы распределяются в соответствии с объявлениями XAML.

4b9b3361

Ответ 1

Я бы согласился с тем, что Бен отвечает, что намекает на то, что может происходить под обложками:

Как указывает Бен, WPF игнорирует размер звезды в целях вычисления внутренней минимальной ширины объекта Grid (возможно, разумно & hellip; я думаю, что есть место для честных дебатов, но ясно, что один возможный и законный дизайн).

Что непонятно (и что Бен не отвечает), почему это должно означать, что звездное измерение также игнорируется, когда приходит время для вычисления ширины столбцов внутри этого внутреннего Grid. Поскольку, когда ширина вводится снаружи, пропорциональная ширина приведет к усечению содержимого, если необходимо, чтобы сохранить эти пропорции, почему то же самое не происходит, когда ширина вычисляется автоматически на основе минимально необходимых размеров содержимого.

т.е. Я все еще ищу ответ на свой вопрос.


Между тем, ИМХО полезные ответы включают в себя проблемы с этим вопросом. В то время как фактические ответы на мой вопрос сами по себе, они явно полезны для всех, кто может столкнуться с проблемой. Поэтому я пишу этот ответ, чтобы объединить все известные работы (лучше или хуже, одна большая "функция" WPF заключается в том, что всегда существует, по крайней мере, несколько разных способов добиться того же результата:)).


Обходной путь №1:

Используйте UniformGrid вместо Grid для внутреннего объекта сетки. Этот объект не имеет всех тех же функций, что и Grid, и, конечно же, не допускает, чтобы любые столбцы имели разную ширину. Таким образом, это не может быть полезно во всех сценариях. Но он легко справляется с простым здесь:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <UniformGrid Rows="1" Columns="2" Grid.Column="1">
    <Button Content="B" Margin="5"/>
    <Button Content="Button" Grid.Column="1" Margin="5"/>
  </UniformGrid>
</Grid>


Обходной путь №2:

Привязать свойство MinWidth объекта меньшего содержимого (например, здесь, первый Button в сетке) к свойству ActualWidth более крупного. Это, конечно, требует знания того, какой объект имеет наибольшую ширину. В сценариях локализации это может быть проблематично, так как XAML должен быть локализован в дополнение к текстовым ресурсам. Но иногда это необходимо, поэтому & hellip;:)

Это будет выглядеть примерно так (и, по сути, это то, что dub stylee предоставлено ответ здесь):

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <Grid Grid.Column="1">
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Button Content="B" Margin="5"
            MinWidth="{Binding ElementName=button2, Path=ActualWidth}"/>
    <Button x:Name="button2" Content="Button" Grid.Column="1" Margin="5"/>
  </Grid>
</Grid>

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

Существуют и другие варианты сценария привязки, в зависимости от того, какую ширину вы хотите использовать и/или установить. Ни один из них не идеален не только из-за потенциальных проблем локализации, но и потому, что он вставляет более явные отношения в XAML. Но во многих сценариях он будет работать отлично.


Обходной путь № 3:

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

Например:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <Grid Grid.Column="1" IsSharedSizeScope="True">
    <Grid.ColumnDefinitions>
      <ColumnDefinition SharedSizeGroup="buttonWidthGroup"/>
      <ColumnDefinition SharedSizeGroup="buttonWidthGroup"/>
    </Grid.ColumnDefinitions>
    <Button Content="B" Margin="5"/>
    <Button Content="Button" Grid.Column="1" Margin="5"/>
  </Grid>
</Grid>

Этот подход позволяет использовать Grid и таким образом получить все обычные функции этого объекта, обращаясь к конкретному поведению, вызывающему озабоченность. Я ожидаю, что это не будет мешать никакому другому законному использованию SharedSizeGroup.

Однако это подразумевает "Auto" калибровку и исключает размер "*" (звездочка). В этом конкретном сценарии это не проблема, но, как и в случае UniformGrid, он ограничивает один параметр при попытке комбинировать это с другим размером. Например. имея третий столбец, используйте "Auto" и хотите, чтобы столбцы SharedSizeGroup заняли оставшееся пространство Grid.

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


Обходной путь №4:

Я закончил пересмотр этого вопроса, потому что я столкнулся с вариацией темы. В этом случае я имею дело с ситуацией, когда я хочу иметь разные пропорции для размеров столбцов. Все описанные выше обходные решения предполагают столбцы равного размера. Но я хочу (например), чтобы один столбец имел 25% пространства, а другой столбец имел 75% пробела. Как и раньше, я хочу, чтобы общий размер Grid соответствовал минимальной ширине, необходимой для всех столбцов.

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

Вот такой способ:

private static double ComputeGridSizeForStarWidths(Grid grid)
{
    double maxTargetWidth = double.MinValue, otherWidth = 0;
    double starTotal = grid.ColumnDefinitions
        .Where(d => d.Width.IsStar).Sum(d => d.Width.Value);

    foreach (ColumnDefinition definition in grid.ColumnDefinitions)
    {
        if (!definition.Width.IsStar)
        {
            otherWidth += definition.ActualWidth;
            continue;
        }

        double targetWidth = definition.ActualWidth / (definition.Width.Value / starTotal);

        if (maxTargetWidth < targetWidth)
        {
            maxTargetWidth = targetWidth;
        }
    }

    return otherWidth + maxTargetWidth;
}

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

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

Аналогичный метод будет делать то же самое для высоты строк.


Обходной путь № 5:

Я предполагаю, что это указывает: можно просто указать размер Grid явно. Это работает только для контента, размер которого известен заранее, но опять же, во многих сценариях это было бы хорошо. (В других сценариях он обрезает контент, потому что даже если указанный размер слишком мал, когда он явный, WPF идет вперед и применяет пропорции размера звезды).


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

Ответ 2

Это немного обходное решение и не объясняет причину поведения, которое вы описываете, но оно обеспечивает то, что вы ищете:

<Grid ShowGridLines="True">
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <Button HorizontalAlignment="Left" Content="Button" />
    <Grid Grid.Column="1"
          ShowGridLines="True">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Button Grid.Column="0"
                Margin="5"
                MinWidth="{Binding ElementName=Button2, Path=ActualWidth}"
                Content="B"
                x:Name="Button1" />
        <Button Grid.Column="1"
                Margin="5"
                Content="Button"
                x:Name="Button2" />
    </Grid>
</Grid>

screen shot

Я должен предположить, что ширина столбца Auto вычисляется на основе минимальной ширины дочерних элементов управления. Я не смог подтвердить или опровергнуть это, просмотрев любую документацию, но это, вероятно, ожидаемое поведение. Чтобы заставить элементы управления Button принять равный размер, вы можете просто привязать свойство MinWidth к ActualWidth другого Button. В этом примере я привязал Button1.MinWidth к Button2.ActualWidth, чтобы проиллюстрировать ваше желаемое поведение.

Кроме того, пожалуйста, проигнорируйте Button.Height, я не стал устанавливать их по-другому, чем по умолчанию.

Ответ 3

Из документации (Современные приложения) (WPF):

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

Это не требует больше минимального пространства для запроса, оно влияет на распределение пространства сверх минимального.