📜 ⬆️ ⬇️

Stretch the Canvas and elements inside it across the entire client area

While working on our desktop application, I ran into such a task: there is a graphic element with some settings for display (implemented as a ControlTemplate connected via a binding in ContentControl), an additional group needed to be added to the existing settings. In order not to clutter up an existing interface, I decided to put a list of these settings in the Expander element, which, if necessary, could be expanded, and the rest of the time schedule would occupy the maximum possible usable space.

One of the ways to place items is under the cut.

As a result, the following hierarchy of elements was obtained:

<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> 

')
I use a Grid, because this container occupies the space inside the tab completely and is able to stretch the elements in rows in width.

Placed Expander at the top of the tab, control for working with the spectrum - immediately below it. However, the result did not live up to expectations: when Expander'a is deployed, the control of the schedule shifts down, thereby closing the Expander and checking the result, or scrolling the remaining half of the screen for the schedule.

There was a thought to take the same Expander and make it open as if over a custom control. The container Canvas is ideal for this, because it is in it that you can set the ZIndex (layer depth) for each element contained in it. I tried several layout options, below is one of them:

 <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> 


But here I was again disappointed, the Canvas was either not displayed at all (since by default its width and height = 0), or the elements inside it were too small in size, then vice versa - they went beyond the limits of the screen.

It was necessary to achieve the following: stretch the Canvas over the entire client area of ​​the tab; place the fixed width in the upper right part of the Canvas Expander, and stretch ContentControl under it for the rest of the area.

After spending some time searching for information, filtering and generalizing it, I came up with the following solution: inside the tab we place the DockPanel. This container can also automatically dimension the elements inside it. We set the LastChildFill = “True” property for DockPanel, so that it stretches the last of the elements to the rest of the area. Set the DockPanel name, for example, x: Name = "spectrumDock" and place the Canvas into it, which we "attach" to the top of the panel (although the placement is of particular importance). Inside the Canvas, we place the Expander and ContentControl as follows:

  <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> 

... the shadow for Expander creates the feeling that this element is on top, above the rest. To set the dimensions of ContentControl, you simply use the width and height properties (Actual) of the DockPanel. However, there was one “BUT” - ContentControl is shifted 25 pixels down and has a height equal to the height of the DockPanel, therefore, it travels beyond the visible border to those 25 pixels, i.e. You need to bind to the ContentControl DockPanel height minus 25 pixels. This problem is solved quite simply - using the converter, which is given the required number of pixels for indenting as a parameter:

 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; } } } 

In the description of the main control (UserControl or form) we indicate the reference to the namespace:

 xmlns:Converters="clr-namespace:_;assembly=_" 


... and describe the converter in the resources:

 < .Resources> <Converters:SizeTrimmerConverter x:Key="SizeTrimmerConverter" /> </ .Resources> 

... the converters are in a separate library, so the assembly_name is indicated.

As a result, everything turned out as expected. Please do not judge strictly, perhaps this method is a “crutch”, but my experience in using WPF + XAML technologies is still not enough. I hope my experience will be useful to someone.

Source: https://habr.com/ru/post/306008/


All Articles