powered by simpleCommunicator - 2.0.36     © 2025 Programmizd 02
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / WPF: Шпаргалка для создания панели
14 сообщений из 14, страница 1 из 1
WPF: Шпаргалка для создания панели
    #39771968
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Написал себе (и другим) шпаргалку, чтобы не забывать особенности работы с компоновкой с помощью панели. А то делаешь панели не так уж часто, и мелкие нюансы забываются.

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
public class MyPanel : Panel
{
	protected override Size MeasureOverride(Size availableSize)
	{
		// availableSize - содержит доступные размеры, предоставляемые контейнером.
		// Может быть бесконечность по любому или обоим измерениям, если панель находится в StackPanel, ScrollViewer и т.п.

		var size = new Size(100, 200);
		foreach (UIElement child in InternalChildren)
		{
			var elementSize = size;
			child.Measure(elementSize);
			var desiredSize = child.DesiredSize;
		}

		// В elementSize передаем максимальные размеры, которые может предоставить элементу панель, исходя из логики панели и availableSize.
		// Можно передавать бесконечные размеры, в частности availableSize.
		// Панель может и не использовать результат измерения своих элементов, но вызов child.Measure обязателен.
		// Каждый элемент формирует в child.DesiredSize минимальные требования по своим размерам, но не более elementSize (Margin будут учтены автоматически)
		// Элемент не может вернуть бесконечные размеры.

		return new Size(100, 200); ;

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

	}

	protected override Size ArrangeOverride(Size finalSize)
	{
		var size = new Size(100, 200);
		foreach (UIElement child in InternalChildren)
		{
			var elementRect = new Rect(new Point(0, 0), size);
			child.Arrange(elementRect);
		}

		// Вызов child.Arrange обязателен, без него элементы не будут отрендерены.
		// В elementRect передаем финальное расположение и размер области, предоставленной для элемента, согласно логике панели, finalSize и child.DesiredSize.
		// Если у элемента есть Margin, система сама их учтет, дополнительно уменьшив предоставленную элементу область.
		// Если элемент меньше отведенной области, он будет располагаться в ней согласно Horizontal- и VerticalAlignment.
		// Если элемент больше отведенной области, он будет рендериться в полный размер, но выходящая за пределы elementRect часть видна не будет.
		// Можно указать область, выходящую за пределы области самой панели, видимость за границей панели будет определяться ClipToBounds самой панели.
		// Нельзя указать бесконечный размер.
		// После вызова child.Arrange можно прочитать отрендеренный реальный размер элемента из ActualHeight и ActualWidth

		return size;

		// Как правило, возвращается finalSize
		// Можно вернуть размер меньше finalSize, тогда панель будет позиционироваться в контейнере согласно своим Horizontal- и VerticalAlignment.
		// Можно вернуть размер больше finalSize (в том числе и бесконечный размер), но выходящая за пределы finalSize часть видна не будет
	}
}




Гляньте, может у кого дополнения есть, или я где-то не очень прав.
В частности по поводу обрезания выходящих за границы частей (на что-то влияет ClipToBounds, на что-то нет).

ЗЫ: В учебниках везде написано про измерения, что элемент возвращает желаемый размер. Но ведь это не совсем правильный термин, на мой взгляд, я употребляю слово "минимальный". Например тот же <Border /> (без настроек) затребует себе Size(0,0).
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39772071
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.Pro,

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

А так шпаргалка отличная, я правда это и так всё помню
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39772139
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Roman Mejtesне знаю, чем вам термин желаемый размер не нравится. Так как это объект класса, с точки зрения объекта, этот как раз желаемый размер объекта, который зависит от его состояния .Я трактую так: если у элемента стоит HorizontalAlignment=Stretch, то его желаемая ширина - занять всё доступное пространство. Но затребует он именно минимальное . То есть <Border BorderThickness="1" HorizontalAlignment="Stretch" /> затребует ширину в 2 дипа, даже если ему передают бесконечность в распоряжение.


Roman Mejtesя правда это и так всё помню- Доктор, у меня это. Я всё забываю!
- А я всё помню! Вы у меня уже были.
(с)


С нюансами по обрезанию пришлось поэкспериментировать. Как-то делал панель, никак не мог понять, почему элементы подрезаются, тыкал везде ClipToBounds - не помогало. То, что надо резать желаемый размер по availableSize тоже наверняка не знал, посмотрел, как штатные контролы поступают.

Roman Mejtesчем вам
Может в этом году, может в следующем... хочу проехать на машине от Питера до Владика. Проезжая через Пермь, отловлю тебя там, затащу в ресторан и заставлю выпить на брудершафт, чтобы уж наконец ты перестал мне вы-кать
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39774907
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Roman Mejtes,

Подскажи, InternalChildren заведомо неизменен между MeasureOverride и ArrangeOverride?

Смысл вопроса в следующем: так как зачастую основной расчет компоновки происходит уже на этапе измерения, логично было бы уже составить карту, которую просто использовать в ArrangeOverride, не обращаясь уже к коллекции InternalChildren заново.
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39774917
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
И второй вопрос - нужно ли рендерить (да и измерять тоже) элементы, которые заведомо покинули область видимости при расстановке (да и при измерении)?
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39775021
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
И третий вопрос, правомерен ли комментарий:
"finalSize не может быть больше availableSize"?
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39805622
WinterGraveyard
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Вроде как эта тема хорошо подходит для моего вопроса.
Ситуация: в листбоксе нужно отображать квадратные миниатюры неких объектов с подписью. Все квадраты должны быть одинакового размера, размер квадрата определяется длиной максимальной подписи (все подписи однострочные).
Сейчас я нахожу подпись с максимальной длиной ( вот так ), и привязываю к ней ширину и высоту border'а в DataTemplate. Схематично и в упрощенном виде это выглядит так:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
namespace ListBoxTest
{
  public partial class MainWindow
  {
    public MainWindow()
    {
      InitializeComponent();
    }
  }

  public class MainModel
  {
    public MainModel()
    {
      Items = typeof(Type).GetProperties().Select(p => p.Name).ToList();
      var maxName = Items.OrderByDescending(n => n.Length).First();
      Console.WriteLine(maxName);
      var tb = new TextBlock();
      MaxItemWidth = new FormattedText(
        maxName,
        CultureInfo.CurrentCulture,
        FlowDirection.LeftToRight,
        new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch),
        tb.FontSize,
        Brushes.Black,
        new NumberSubstitution(),
        TextFormattingMode.Display
      ).Width + 16D;
    }
    public IReadOnlyCollection<string> Items { get; }
    public double MaxItemWidth { get; }
  }
}


Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
<Window
  x:Class="ListBoxTest.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:ListBoxTest"
  Height="400"
  Width="600"
  WindowStartupLocation="CenterOwner">

  <Window.Resources>
    <local:MainModel x:Key="MainModel" />
    <DataTemplate x:Key="ItemsTemplate">
      <Border
        Margin="5"
        Width="{Binding Source={StaticResource MainModel}, Path=MaxItemWidth}"
        Height="{Binding Source={StaticResource MainModel}, Path=MaxItemWidth}"
        BorderThickness="1"
        BorderBrush="Gray"
        Background="Transparent">
        <TextBlock
          VerticalAlignment="Center"
          HorizontalAlignment="Center"
          Text="{Binding}"/>
      </Border>
    </DataTemplate>
  </Window.Resources>

  <Window.DataContext>
    <StaticResource ResourceKey="MainModel" />
  </Window.DataContext>

  <ListBox
    ItemsSource="{Binding Items}"
    SelectionMode="Single"
    ScrollViewer.HorizontalScrollBarVisibility="Disabled"
    ItemTemplate="{StaticResource ItemsTemplate}">
    <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
        <WrapPanel IsItemsHost="True" Orientation="Horizontal"/>
      </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
  </ListBox>
</Window>


- всё нормально и без нареканий работает, но что-то мне подсказывает, что тут правильнее было бы сделать для ListBox.ItemsPanel свою панель. Но вот как - пока не могу сообразить.
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39805623
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ну то есть ты хочешь избавится от вычисления общей ширины, которую сейчас ты вынужден вычислять вручную. Ну логично, в принципе, но в чем вопрос-то?
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39805630
WinterGraveyard
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Shocker.Proно в чем вопрос-то?
В том, что я вообще не представляю, с какого боку тут подступиться. Допустим, я наследуюсь от WrapPanel. Что тут нужно переопределить? MeasureOverride? ArrangeOverride? Каким образом? Допустим, из InternalChildren я получу элемент с максимальной шириной - в каком из методов (и как) мне задать для всех остальных элементов аналогичную ширину и высоту?
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39805641
WinterGraveyard
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Нашел еще вот такое решение:
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
<Window
  x:Class="ListBoxTest.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:ListBoxTest"
  Height="400"
  Width="600"
  WindowStartupLocation="CenterOwner">

  <Window.Resources>
    <local:MainModel x:Key="MainModel" />
    <DataTemplate x:Key="ItemsTemplate">
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto" SharedSizeGroup="CellGroup" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto" SharedSizeGroup="CellGroup" />
        </Grid.RowDefinitions>
        <Border
          Margin="5"
          BorderThickness="1"
          BorderBrush="Gray"
          Background="Transparent">
          <TextBlock
            Margin="5"
            VerticalAlignment="Center"
            HorizontalAlignment="Center"
            Text="{Binding}"/>
        </Border>
      </Grid>
    </DataTemplate>
  </Window.Resources>

  <Window.DataContext>
    <StaticResource ResourceKey="MainModel" />
  </Window.DataContext>

  <ListBox
    Grid.IsSharedSizeScope="True"
    ItemsSource="{Binding Items}"
    SelectionMode="Single"
    ScrollViewer.HorizontalScrollBarVisibility="Disabled"
    ItemTemplate="{StaticResource ItemsTemplate}">
    <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
        <WrapPanel IsItemsHost="True" Orientation="Horizontal"/>
      </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
  </ListBox>
</Window>


- и оно вполне работает без всяких предварительных вычислений. Теперь хотелось бы аналогичное всё-таки сделать через кастомную панель - чисто в целях самообразования, и общего развития.
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39805700
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В чем проблема то?
В методе MeasureOverride вычислите все размеры для элементов панели с помощью метода child.Measure(size).
Найдите максимальный размер высоты и ширины элементов.
Сохраните этот размер элементов в полях панели, чтоб воспользоваться им в методе ArrangeOverride.

Если в метод MeasureOverride был передан бесконечный размер, значит панель находится в прокручиваемой области. Размер будет бесконечным в том измерении, в котором разрешена прокрутка в элементе ScrollViewer. Если включена прокрутка по горизонтали, то нужно как то ограничить максимальное количество в 1 ряду, иначе все элементу будут в 1 ряд.
Всё это необходимо, чтоб вычислить предполагаемый размер самой панели, которую вам нужно вернуть в конце метода.
Предположим, что максимальное количество элементов в ряд 10 и панель бесконечна по вертикали и горизонтали. Тогда вы берете количество элементов делите на 10, округляете к верхней границе, получаете количество строк. Умножаете ширину элементов на 10 и высоту на количество строк, получаете размер панели.
Если прокрутка только по вертикали, тогда делите ширину на полученный размер элементов, получаете количество столбцов, умножаете на ширину элементов, получаете ширину панели, вычисляете количество рядов, вычисляете высоту.
Чтоб не образовывалось дыр слева, при таком раскладе, можно вычислить размер пустой области, поделить на количество столбцов и прибавить к размеру элементов.

Так вы получите размер всех элементов списка. Затем в методе ArrangeOverride расположите эти элементы плиткой с вычисленным размером с помощью метода child.Arrange(rect)
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39805738
WinterGraveyard
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Roman MejtesВ чем проблема то?
В методе MeasureOverride вычислите все размеры для элементов панели с помощью метода child.Measure(size).
Найдите максимальный размер высоты и ширины элементов.
Сохраните этот размер элементов в полях панели, чтоб воспользоваться им в методе ArrangeOverride.

Если в метод MeasureOverride был передан бесконечный размер, значит панель находится в прокручиваемой области. Размер будет бесконечным в том измерении, в котором разрешена прокрутка в элементе ScrollViewer. Если включена прокрутка по горизонтали, то нужно как то ограничить максимальное количество в 1 ряду, иначе все элементу будут в 1 ряд.
Всё это необходимо, чтоб вычислить предполагаемый размер самой панели, которую вам нужно вернуть в конце метода.
Предположим, что максимальное количество элементов в ряд 10 и панель бесконечна по вертикали и горизонтали. Тогда вы берете количество элементов делите на 10, округляете к верхней границе, получаете количество строк. Умножаете ширину элементов на 10 и высоту на количество строк, получаете размер панели.
Если прокрутка только по вертикали, тогда делите ширину на полученный размер элементов, получаете количество столбцов, умножаете на ширину элементов, получаете ширину панели, вычисляете количество рядов, вычисляете высоту.
Чтоб не образовывалось дыр слева, при таком раскладе, можно вычислить размер пустой области, поделить на количество столбцов и прибавить к размеру элементов.

Так вы получите размер всех элементов списка. Затем в методе ArrangeOverride расположите эти элементы плиткой с вычисленным размером с помощью метода child.Arrange(rect)
Ну, по сути это всё - воспроизведение логики методов MeasureOverride/ArrangeOverride самой WrapPanel, только с заданными размерами элемента. Зачем всё это нужно, если это всё уже реализовано у WrapPanel (и реализация там, надо сказать, не очень проста). Плюс у WrapPanel есть свойства ItemWidth и ItemHeight, которые как раз определяют фиксированный размер элемента панели, и они используются внутри реализации MeasureOverride/ArrangeOverride, и они помечены флагом FrameworkPropertyMetadataOptions.AffectsMeasure. Поэтому я сделал проще:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
namespace ListBoxTest
{
  public class MyWrapPanel : WrapPanel
  {
    public MyWrapPanel()
    {
      Loaded += (s, ea) =>
      {
        var maxWidth = InternalChildren.OfType<FrameworkElement>().Max(e => e.ActualWidth);
        if (maxWidth > 0)
        {
          ItemWidth = maxWidth;
          ItemHeight = maxWidth;
        }
      };
    }
  }
}


и в XAML сделал так:
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
<ListBox
  ItemsSource="{Binding Items}"
  SelectionMode="Single"
  ScrollViewer.HorizontalScrollBarVisibility="Disabled"
  HorizontalContentAlignment="Stretch"
  VerticalContentAlignment="Stretch"
  ItemTemplate="{StaticResource ItemsTemplate}">
  <ListBox.ItemsPanel>
    <ItemsPanelTemplate>
      <local:MyWrapPanel Orientation="Horizontal"/>
    </ItemsPanelTemplate>
  </ListBox.ItemsPanel>
</ListBox>


Но вариант с SharedSizeGroup мне нравится больше - остановлюсь на нём.
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39805748
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Событие Loaded, это не совсем то, что вы себе представляете, за время жизни объекта оно может быть вызвано более 1 раза.
Выше вы писали, что хотите, чисто в академических целях разобраться как работает макетирование в WPF (Measure\Arrange), по этому ваша отсылка в существующим реализациям, мне не совсем понятна. Используйте WrapPanel, я не против. Если поищите в интернетах, даже найдете реализацию этой панели с поддержкой виртуализации
...
Рейтинг: 0 / 0
WPF: Шпаргалка для создания панели
    #39805822
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Роман ответил подробно, я лишь немного общих словWinterGraveyardЧто тут нужно переопределить? MeasureOverride? ArrangeOverride?Переопределять нужно оба метода в любом случае, потому что в базовом классе стоят заглушки

WinterGraveyardв каком из методов (и как) мне задать для всех остальных элементов аналогичную ширину и высоту?НЕ НАДО переопределять ширину и высоту дочерних элементов! Ты так зациклишься - будет бесконечное переизмерение и переразмещение.
Задача панели не задавать размер элемента, а размещать эти элементы, то есть выделить им координаты и пространство в соответствии с твоей логикой. А задача элемента - разместиться внутри этого пространства (например с HorizontalAlignment=Stretch)


WinterGraveyardWrapPanel (и реализация там, надо сказать, не очень проста)У Метью МакДональда в книжке есть реализация WrapPanel с возможностью добавления принудительного конца строки. Ничего сложного в коде панели у него нет.
...
Рейтинг: 0 / 0
14 сообщений из 14, страница 1 из 1
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / WPF: Шпаргалка для создания панели
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]