Во время работы над нашим десктопным приложением столкнулся с такой задачей: имеется элемент-график с некоторыми настройками для отображения (реализован в виде ControlTemplate подключаемого через привязку в ContentControl), к имеющимся настройкам нужно было добавить группу дополнительных. Чтобы не засорять уже имеющийся интерфейс, я решил поместить список этих настроек в элемент Expander, который, при необходимости, можно было бы развернуть, а в остальное время график занимал бы максимально возможное полезное пространство.
Один из способов размещения элементов — под катом.
В итоге была получена следующая иерархия элементов:
Использую Grid, поскольку этот контейнер занимает пространство внутри вкладки полностью и сам способен растягивать элементы в строках по ширине.
Разместил Expander в верхней части вкладки, контрол для работы со спектром — сразу под ним. Однако, результат не оправдал ожиданий: при разворачивании Expander'a контрол графика сдвигается вниз, тем самым приходится закрывать Expander и проверять результат, либо скроллить оставшуюся для графика половинку экрана.
Возникла мысль взять тот же Expander и заставить его открываться как бы над настраиваемым контролом. Для этого идеально подходит контейнер Canvas, ведь именно в нем можно задавать ZIndex (глубину слоя) для каждого содержащегося в нем элемента. Я перепробовал несколько вариантов компоновки, ниже — один из них:
Но тут меня опять ждало разочарование, Canvas либо вообще не отображался (поскольку по умолчанию его ширина и высота = 0), либо элементы внутри него то были слишком малы в размерах, то наооборот — выходили за границу видимости экрана.
Добиться нужно было следующего: растянуть Canvas по всей клиентской области вкладки; разместить в верхней правой части Canvas'а Expander фиксированной ширины, а под ним на всю оставшуюся область растянуть ContentControl.
Проведя некоторое время в поиске информации, её фильтрации и обобщении, пришел к следующему решению: внутри вкладки размещаем DockPanel. Этот контейнер также может автоматически задавать размеры элементам, находящимся внутри него. Устанавливаем для DockPanel свойство LastChildFill=«True», чтобы он растягивал последний из элементов на всю оставшуюся область. Задаем DockPanel имя, например, x:Name=«spectrumDock» и помещаем в него Canvas, который «прикрепляем» к верхней части панели (хотя размещение имеет особого значения). Внутри Canvas размещаем Expander и ContentControl следующим образом:
… тень для Expander'а создает ощущение, что этот элемент находится сверху, над остальными. Для задания размеров ContentControl'у используется просто биндинг свойств ширины и высоты (именно Actual) DockPanel. Однако, было одно «НО» — ContentControl сдвинут на 25 пикселей вниз и имеет высоту, равную высоте DockPanel, следовательно, он выезжает за видимую границу на эти самые 25 пикселей, т.е. нужно забиндить к ContentControl высоту DockPanel минус 25 пикселей. Эта задача решается довольно просто — использованием конвертера, которому в качестве параметра передается требуемое количество пикселей для отступа:
В описании главного контрола (UserControl или форма) указываем ссылку на пространство имен:
… и описываем конвертер в ресурсах:
… конвертеры лежат в отдельной библиотеке, поэтому указывается название_сборки.
В результате всё получилось так, как и было в ожиданиях. Прошу не судить строго, возможно, данный метод и является «костыльным», но моего опыта в использовании технологий WPF + XAML ещё достаточно не много. Надеюсь, мой опыт окажется кому-то полезен.
Один из способов размещения элементов — под катом.
В итоге была получена следующая иерархия элементов:
<TabItem> <Grid> <Grid.RowDefenitions> <RowDefenition Height="Auto" /> <RowDefenition /> </Grid.RowDefenitions> <Expander Header="Дополнительные настройки" Grid.Row="0" IsExpanded="False" MinWidth="270"> <Элемент для настройки 1 /> <Элемент для настройки 2 /> <Элемент для настройки n /> </Expander> <ContentControl <!--всякие настройки и привязки--> Grid.Row="1" /> </Grid> </TabItem>
Использую Grid, поскольку этот контейнер занимает пространство внутри вкладки полностью и сам способен растягивать элементы в строках по ширине.
Разместил Expander в верхней части вкладки, контрол для работы со спектром — сразу под ним. Однако, результат не оправдал ожиданий: при разворачивании Expander'a контрол графика сдвигается вниз, тем самым приходится закрывать Expander и проверять результат, либо скроллить оставшуюся для графика половинку экрана.
Возникла мысль взять тот же Expander и заставить его открываться как бы над настраиваемым контролом. Для этого идеально подходит контейнер Canvas, ведь именно в нем можно задавать ZIndex (глубину слоя) для каждого содержащегося в нем элемента. Я перепробовал несколько вариантов компоновки, ниже — один из них:
<TabItem> <Grid> <Canvas> <Expander Header="Дополнительные настройки" IsExpanded="False" Panel.ZIndex="1" MinWidth="270" Canvas.Right="10" > <Элемент для настройки 1 /> <Элемент для настройки 2 /> <Элемент для настройки n /> </Expander> <ContentControl <!--всякие настройки и привязки--> Canvas.Top="25" /> </Canvas> </Grid> </TabItem>
Но тут меня опять ждало разочарование, Canvas либо вообще не отображался (поскольку по умолчанию его ширина и высота = 0), либо элементы внутри него то были слишком малы в размерах, то наооборот — выходили за границу видимости экрана.
Добиться нужно было следующего: растянуть Canvas по всей клиентской области вкладки; разместить в верхней правой части Canvas'а Expander фиксированной ширины, а под ним на всю оставшуюся область растянуть ContentControl.
Проведя некоторое время в поиске информации, её фильтрации и обобщении, пришел к следующему решению: внутри вкладки размещаем DockPanel. Этот контейнер также может автоматически задавать размеры элементам, находящимся внутри него. Устанавливаем для DockPanel свойство LastChildFill=«True», чтобы он растягивал последний из элементов на всю оставшуюся область. Задаем DockPanel имя, например, x:Name=«spectrumDock» и помещаем в него Canvas, который «прикрепляем» к верхней части панели (хотя размещение имеет особого значения). Внутри Canvas размещаем Expander и ContentControl следующим образом:
<DockPanel x:Name="spectrumDock" LastChildFill="True"> <Canvas> <Expander Header="Дополнительные настройки" IsExpanded="False" Panel.ZIndex="1" MinWidth="270" Canvas.Right="10" > <Элемент для настройки 1 /> <Элемент для настройки 2 /> <Элемент для настройки n /> <Expander.Effect> <DropShadowEffect BlurRadius="6" Direction="270" ShadowDepth="1" Opacity="0.5"/> </Expander.Effect> </Expander> <ContentControl Content="{Binding Path=ControlTemplateName}" Canvas.Top="25" Height="{Binding ElementName=spectrumDock, Path=ActualHeight, Converter={StaticResource SizeTrimmerConverter}, ConverterParameter='25'}" Width="{Binding ElementName=spectrumDock, Path=ActualWidth}" /> </Canvas> </DockPanel>
… тень для Expander'а создает ощущение, что этот элемент находится сверху, над остальными. Для задания размеров ContentControl'у используется просто биндинг свойств ширины и высоты (именно Actual) DockPanel. Однако, было одно «НО» — ContentControl сдвинут на 25 пикселей вниз и имеет высоту, равную высоте DockPanel, следовательно, он выезжает за видимую границу на эти самые 25 пикселей, т.е. нужно забиндить к ContentControl высоту DockPanel минус 25 пикселей. Эта задача решается довольно просто — использованием конвертера, которому в качестве параметра передается требуемое количество пикселей для отступа:
namespace пространство_имен { [ValueConversion(typeof(double), typeof(double))] public class SizeTrimmerConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (double)value - ConvertParameter(parameter); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return (double)value + ConvertParameter(parameter); } private double ConvertParameter(object parameter) { string _stringValue = parameter.ToString(); double _result = 0; double.TryParse(_stringValue, out _result); return _result; } } }
В описании главного контрола (UserControl или форма) указываем ссылку на пространство имен:
xmlns:Converters="clr-namespace:пространство_имен;assembly=название_сборки"
… и описываем конвертер в ресурсах:
<Главный контрол.Resources> <Converters:SizeTrimmerConverter x:Key="SizeTrimmerConverter" /> </Главный контрол.Resources>
… конвертеры лежат в отдельной библиотеке, поэтому указывается название_сборки.
В результате всё получилось так, как и было в ожиданиях. Прошу не судить строго, возможно, данный метод и является «костыльным», но моего опыта в использовании технологий WPF + XAML ещё достаточно не много. Надеюсь, мой опыт окажется кому-то полезен.
