Viewbox

29

Единственное ограничение при использовании Canvas заключается в том, что графика не сможет самостоятельно подгонять свои размеры к большему или меньшему окну. Это имеет смысл для кнопок (которые не меняют своего размера в любых ситуациях), но не обязательно для графического содержимого других типов.

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

Viewbox — это простой класс, унаследованный от Decorator (который очень похож на класс Border). Он принимает единственный дочерний элемент, который растягивает или сжимает для заполнения доступного пространства. Естественно, этим дочерним элементом может быть контейнер компоновки, удерживающий множество фигур (или других элементов), которые могут синхронно изменять свои размеры. Однако чаще всего Viewbox применяют для векторной графики, а не для обычных элементов управления.

Хотя в элемент Viewbox можно поместить одиночную фигуру, это не дает никаких реальных преимуществ. Напротив, Viewbox проявляет себя во всей красе, когда необходимо обернуть группу фигур, образующих общий рисунок. Обычно внутрь Viewbox помещается контейнер Canvas, а уже в него — нужные фигуры.

В следующем примере во вторую строку Grid помещается Viewbox с контейнером Canvas. Элемент Viewbox занимает полную высоту и ширину строки. Строка занимает все свободное пространство, оставшееся после визуализации первой строки с автоматическим размером. Ниже приведена разметка:

<Grid Margin="5">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"></RowDefinition>
      <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>

    <TextBlock>The first row of a grid.</TextBlock>
    
    <Viewbox Grid.Row="1" HorizontalAlignment="Left"  MaxHeight="500">

      <Canvas Width="200" Height="150">
        <Ellipse Fill="Yellow" Stroke="Blue" Canvas.Left="10"  Canvas.Top="50"
               Width="100" Height="50" HorizontalAlignment="Left"></Ellipse>
      <Rectangle Fill="Yellow" Stroke="Blue" Canvas.Left="30"  Canvas.Top="40"                 
                 Width="100" Height="50" HorizontalAlignment="Left"></Rectangle>
    </Canvas>
    </Viewbox>
  </Grid>

На рисунке показано, как Viewbox подстраивается при изменении размеров окна. Первая строка остается неизменной. Однако вторая строка расширяется для заполнения всего свободного пространства. Как видите, фигура в Viewbox изменяется пропорционально по мере увеличения окна.

Исходный размер Viewbox
Увеличенный размер Viewbox

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

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

Вместо этого Viewbox использует наибольший масштабный множитель, который позволяет уместиться внутри доступного пространства. Однако это поведение можно изменить с помощью свойства Viewbox.Stretch. По умолчанию оно установлено в Uniform, но допускается любое значение. Измените Viewbox.Stretch на Fill, и содержимое Viewbox будет растягиваться в обоих направлениях, чтобы занять полностью все доступное пространство, даже если при этом исходный рисунок окажется искаженным.

С помощью свойства StretchDirection можно достичь несколько большего контроля. По умолчанию это свойство получает значение Both, но можно установить его в UpOnly, чтобы создать содержимое, которое сможет расти, но не более своего исходного размера, и DownOnly, чтобы создать содержимое, которое может сжиматься, но не расти.

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

Вторая деталь — новый размер — достаточно проста. Viewbox отводит внутреннему содержимому все доступное пространство, основываясь на значении своего свойства Stretch. Это значит, что чем больше Viewbox, тем больше содержимое.

Первая деталь — обычный размер (без вмешательства Viewbox) — вычисляется по способу определения вложенного содержимого. В предыдущем примере Canvas задается явный размер 200x150 единиц. Таким образом, Viewbox масштабирует изображение от этого начального размера. Например, если эллипс изначально имеет ширину 100 единиц, т.е. заполняет половину выделенного Canvas пространства, то по мере роста Canvas элемент Viewbox сохраняет эти пропорции и эллипс продолжает занимать половину отведенного пространства.

Однако посмотрим, что случится, если удалить свойства Width и Height из Canvas. Теперь Canvas задан размер 0x0 единиц, так что Viewbox не может изменить размер, и вложенное содержимое не появляется. (Совсем иначе обстоят дела при работе только с Canvas без Viewbox. Несмотря на то что Canvas также имеет размеры 0x0. содержащимся в нем фигурам разрешено рисовать себя за его пределами, до тех пор, пока свойство Canvas.ClipToBounds не установлено в true. Элемент viewbox не настолько терпим к ошибке подобного рода.)

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

Эту проблему можно обойти, поместив определенные фигуры (вроде Rectangle и Ellipse) непосредственно в контейнер, автоматически изменяющий размер (такой как Grid). Viewbox может затем вычислить минимальный размер, необходимый Grid для помещения своего содержимого, и затем масштабирует его соответствующим образом.

Однако простейший способ получить нужный размер Viewbox — это поместить содержимое в элемент фиксированного размера, будь то Canvas, кнопка или что-то еще. Этот фиксированный размер становится начальным размером, который элемент Viewbox использует для своих вычислений. Такое жесткое кодирование размера не ограничит гибкости компоновки, поскольку Viewbox изменяет размер пропорционально, на основе доступного пространства своего контейнера компоновки.

Пройди тесты
Лучший чат для C# программистов