Поставляющаяся из коробки схема разбиения экрана для больших мониторов (см. под катом) разбивает рабочий стол на 2 главные зоны — центральную (я обычно использую её для браузеров и IDE) и боковую (туда попадают RSS Reader, мессенжеры и окна терминалов). Эта схема фиксирована. Т.е. если у вас открыт только браузер, Stack оставит пустое место слева от него. В этой статье я покажу как использовать WPF Data Triggers и Data Binding, чтобы схопывать эту область автоматически, когда она пустая.
С чего начнём?
Вот так выглядит схема разбиения до начала работы над ней:

Тут можно взять исходный код последней версии.
Для начала придётся создать копию с другим именем, потому что в оригинале есть пометка
This file is overwritten after every update. Please, modify a copy!
Я назвал свою схему Large Horizontal Left Autocollapse.
Собственно, работать в основном придётся с самым внешним Grid, т.к. он определяет левую колонку и всё остальное.
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="3*"/>
Как видим, колонки фиксированы по размерам как 1:3. Нам такое не надо, и мы попытаемся сжимать левую колонку, если в ней ничего нет.
Data Trigger
К счастью, у нас уже есть табы в левой колонке, которые должны показывать все окна, находящиеся в ней. А это значит мы можем использовать их Items.Count в качестве источника для DataTrigger.
Чтобы это сделать, придётся дать им имя (они определены в начале схемы внутри первой колонки):
<zones:WindowTabs>...
->
<zones:WindowTabs x:Name="SideTabs">
<zones:WindowTabs.ItemsSource>
<CompositeCollection>
<zones:ZoneElement Content="{Binding ViewModel, Source={x:Reference SideStack}}"/>
<CollectionContainer Collection="{Binding Windows, Source={x:Reference SideSingle}}"/>
</CompositeCollection>
</zones:WindowTabs.ItemsSource>
</zones:WindowTabs>
Тут вступает в бой магия DataTrigger’ов. Берём определение первой колонки (см. выше) и меняем фиксированный Width на стиль с DataTrigger:
<ColumnDefinition>
<ColumnDefinition.Style>
<Style>
<Setter Property="ColumnDefinition.Width" Value="1*"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, ElementName=SideTabs}" Value="0">
<Setter Property="ColumnDefinition.Width" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
Обратите внимание на то, что 1* используется как значение по умолчанию, а триггер вкючается при 0 элементов. Было бы логичнее указать триггер при > 0 элементах, но WPF XAML так не умеет.
Итак, сохраняем, выбираем в Stack’е новую схему и видим …, что больше не можем ничего положить на левую сторону, т.к. она схлопывается при запуске из-за того, что в ней ничего нет (мы сами так написали), а значит и навести мышкой туда нельзя. Win + стрелочка, кстати, уже работает, так что если вы мышкой не пользуетесь, следующую часть можно будет пропустить.

Что же делать с мышкой
Если вы раньше делали кастомыные схемы для Stack, вы уже знаете, что зона, куда можно положить окно не обязана совпадать с зоной, где это окно окажется. Поэтому мы сделаем очень просто — добавим зону слева, которая будет видима даже когда колонка будет свёрнута. Так можно сделать, если положить в Grid левой колонки Canvas — Canvas не обрезает элементы, которые в него не вмещаются.
Эта Canvas заменит собой Border, который в оригинальной схеме разметки использовался для того, чтобы растягивать элементы на всю левую колонку. Вот как это будет выглядеть:
<!-- Canvas is visible even though its container is collapsed -->
<Canvas x:Name="DropOverlay"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
zones:Layout.IsHint="True">
<zones:Zone Target="{Binding ElementName=SideStack}" MinWidth="160"
Width="{Binding ActualWidth, ElementName=DropOverlay}"
Height="{Binding ActualHeight, ElementName=DropOverlay}"/>
<Grid MinWidth="160"
Width="{Binding ActualWidth, ElementName=DropOverlay}"
Height="{Binding ActualHeight, ElementName=DropOverlay}"
HorizontalAlignment="Stretch">
<Border Height="160" Width="160" Background="#44F">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
FontFamily="Segoe UI Symbol" Foreground="White" Text="" FontSize="80"/>
</Border>
<zones:Zone HorizontalAlignment="Center" VerticalAlignment="Center"
Height="160" Width="160"
Target="{Binding ElementName=SideSingle}"/>
</Grid>
</Canvas>
Поскольку Canvas в общем-то предназначен для рисования, нет простого способа растянуть элементы во всю его ширину. Поэтому размеры элементов в нём привинчены к размерам самого Canvas через data-binding’и:
Width="{Binding ActualWidth, ElementName=DropOverlay}"
Также у них у всех указан MinWidth чтобы когда колонка вместе с Canvas’ом были свёрнуты, элементы всё равно имели бы ненулевую ширину.
В общем-то это уже выглядит как надо, но если вы сейчас сохраните и попробуете мышкой перенести что-нибудь налево, у вас ничего не получится. Из-за порядка элементов в XAML файле основная зона находится выше нашего DropOverlay в z-order, превращая его в бесполезный «DropUnderlay» 🙂
Исправить это очень просто — поменяйте местами Grid’ы, описывающие колонки. Было:
...
</Grid.ColumnDefinitions>
<Grid>
...LEFT...
</Grid>
<Grid Grid.Column="1">
...CENTER...
</Grid>
...
Стало:
...
</Grid.ColumnDefinitions>
<Grid Grid.Column="1">
...CENTER...
</Grid>
<Grid Grid.Column="0">
...LEFT...
</Grid>
...
Теперь левая колонка будет выше в z-order, чем центральная часть, и DropOverlay сможет принимать мышку.
Вот что мы увидим:

Полный код схемы разбиения тут: pastebin
Послесловие
С такой схемой разбивки экрана гораздо удобнее, чем с фиксированной. Однако не помешали бы дополнительные доработки. Например, не удобно, что при драге первого окна мышкой налево не показывается куда именно оно переедет, т.к. соответствующая зона свёрнута. Эту пробему можно решить c помощью MouseOver триггера, который бы разворачивал всю левую колонку при наведении мыши.
Вообще, я ещё не пробовал, но можно было бы использовать в триггерах и элементах разметки данные, полученные из внешних источников. Получилось бы что-то вроде кастомных виджетов на рабочем столе, например, с котировками курсов. Единственная проблема — WPF не умеет обновлять данные из источника по таймеру.
Вообще это очень круто, что можно использовать XAML для кастомизации рабочего стола в Windows. Открывает много возможностей.
- Метки: