📜 ⬆️ ⬇️

Extending, modifying, and creating controls on the UWP platform. Part 2



So, we again talk about controls on the UWP platform.

In the previous part, we learned about the means of expanding existing controls without interfering with their internal structure. However, it is not always possible to achieve the desired result with little blood by means of attached properties (Attached Properties) or behaviors (Behaviors).

Part 2. Modifying Existing Controls
')
For a better understanding of which vector the further discussion will develop in, let us give a picture illustrating the vision of what “levels” of influence on controls can be distinguished.


Impact Levels on Controls

It shows that the previous part concerned the outermost level. On it we influence the control through the above means of extensions, as well as through the redefinition of styles. To complete the picture, this level can also include the indication of the values ​​of the properties of the controls that they provide to the outside world.

The next level of exposure is to interfere with the layout of the template of an existing control.

To start working with the markup, we obviously need to get it. Two sources will help us with this:

Official Microsoft documentation . This page contains a list of controls supplied with UWP for each of which you can get a markup template.
• A Blend For Visual Studio application that can provide control patterns


Creating a copy of the control pattern in Blend


Create a copy of the Button template


Copy of the button template

Both methods have their own advantages.

• Documentation:

- easier and faster access to the template,
- explicit attention to the ThemeResources and VisualStateManager states it uses

• Blend:

- can be used as a sandbox to work with the desired template
- easy access to child object templates,
- additional IDE features to facilitate the work when creating animations

Having templates, we can start working with them. At first, they can cause a slight confusion in markup volumes, but it becomes much easier to navigate if you remember that the control patterns of UWP and most third-party developers adhere to a certain unwritten convention.

The general structure of the control template

Let's take a look at the example of the CheckBox control template.

<Style TargetType="CheckBox"> <Setter Property="Background" Value="Transparent" /> <Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}"/> <Setter Property="Padding" Value="8,5,0,0" /> <Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="HorizontalContentAlignment" Value="Left"/> <Setter Property="VerticalContentAlignment" Value="Top"/> <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" /> <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" /> <Setter Property="MinWidth" Value="120" /> <Setter Property="MinHeight" Value="32" /> <Setter Property="UseSystemFocusVisuals" Value="True" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="CheckBox"> <Grid Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <!--<VisualStateManager.VisualStateGroups> ... </VisualStateManager.VisualStateGroups>--> <Grid.ColumnDefinitions> <ColumnDefinition Width="20" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid VerticalAlignment="Top" Height="32"> <Rectangle x:Name="NormalRectangle" Fill="Transparent" Stroke="{ThemeResource SystemControlForegroundBaseMediumHighBrush}" StrokeThickness="{ThemeResource CheckBoxBorderThemeThickness}" UseLayoutRounding="False" Height="20" Width="20" /> <FontIcon x:Name="CheckGlyph" FontFamily="{ThemeResource SymbolThemeFontFamily}" Glyph="" FontSize="20" Foreground="{ThemeResource SystemControlHighlightAltChromeWhiteBrush}" Opacity="0" /> </Grid> <ContentPresenter x:Name="ContentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Grid.Column="1" AutomationProperties.AccessibilityView="Raw" TextWrapping="Wrap" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> 

Copy of checkbox template

In this template, we can distinguish several parts of it:

• A set of simple setters of the form <Setter Property = "Property_Name" Value = "Property_Value" /> . They perform the function of specifying default values ​​for the control's properties. In particular, for example, the default CheckBox cannot have a width less than 120. In this example, it can be seen that some problems in the layout process can come from the default template, which can “hinder” the achievement of the desired result.

• The list of simple setters ends with the <Setter Property = "Template"> ... </ Setter> setter, which defines the framework of the control structure. This framework can be divided into two main parts:

  1. Directly the markup of the control template consisting of other controls with an indication of the default property values
  2. The VisualStateGroups collection defines a set of visual states of controls, in one of which it can be located at a specific moment.


List of Visual States CheckBox

After reviewing the list of visual states of the CheckBox control, we see that there are definitely 4 visual states for each of the 3 values ​​of the IsChecked property : true, false, null.

For example, switching to the UncheckedPointerOver visual state sets the color of the NormalRectangle element to the SystemControlHighlightBaseHighBrush value, taken from the theme of the application.

 <VisualState x:Name="UncheckedPointerOver"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="NormalRectangle" Storyboard.TargetProperty="Stroke"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseHighBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="UncheckedPressed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="NormalRectangle" Storyboard.TargetProperty="Fill"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlBackgroundBaseMediumBrush}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="NormalRectangle" Storyboard.TargetProperty="Stroke"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightTransparentBrush}" /> </ObjectAnimationUsingKeyFrames> <DoubleAnimation Storyboard.TargetName="NormalRectangle" Storyboard.TargetProperty="StrokeThickness" To="{ThemeResource CheckBoxCheckedStrokeThickness}" Duration="0" /> </Storyboard> </VisualState> 

A couple of visual states CheckBox

Also note that the visual state of <VisualState x: Name = "UncheckedNormal" /> specified in the collection is empty. The reason for this is that the mechanics of the visual state manager are such that when transitioning from state A to state B, those properties that were affected by state A but not state B take the values ​​defined as default. This mechanic repeats the trigger mechanics implemented in WPF, but not included in UWP.

After reviewing the general structure of control templates, we will begin to work with them to obtain the required results.

With the existing control pattern, you can do the following:

• Remove "extra" items
• Modify required items already defined in it
• Add new items.

Modifying a template by removing its constituent elements

A common task we faced was to remove the text clear button from the TextBox control. Imagine one of the possible solutions to this problem. Having received the template, we find in it an element that needs to be deleted - <Button x: Name = "DeleteButton" ... />

 <Button x:Name="DeleteButton" Grid.Row="1" Style="{StaticResource DeleteButtonStyle}" BorderThickness="{TemplateBinding BorderThickness}" Margin="{ThemeResource HelperButtonThemePadding}" IsTabStop="False" Grid.Column="1" Visibility="Collapsed" FontSize="{TemplateBinding FontSize}" MinWidth="34" VerticalAlignment="Stretch"/> 

Delete button

It is also important not to forget to delete all other places in the markup that refer in one way or another to this element. So the following are deleted: <Style x: Name = "DeleteButtonStyle" /> and <VisualState x: Name = "ButtonVisible" /> . Removing the latter in general allows us to delete the entire <VisualStateGroup x: Name = "ButtonStates" /> .

Define the resulting style x: Key - <Style x: Key = "articleTextBox" TargetType = "TextBox"> and apply to the required text input field <TextBox Style = "{StaticResource articleTextBox}" />


TextBox without a clear button

It should be noted that this method of modifying the template does not always work. In the next article, we will examine the insides of controls at the Control.cs level and we will see that sometimes dependencies on the elements that make up the template are defined not only in the layout, but also in the code. There are cases when modifying a template by deleting leads to incorrect operation of the control or even exceptions.

Modification of the template through changes to its constituent elements

In our practice, there was a situation where we needed to make the following changes to the CalendarDatePicker control:

• Highlight weekend headers in red
• Increase the font size of weekend headers
• Change the vertical navigation arrows to horizontal
• Place the left arrow in the left side of the panel, the right arrow in the right side, and place the heading “year \ month / decade” in the middle between these arrows


Default CalendarDatePicker Calendar Appearance


New calendar view CalendarDatePicker

• Get the CalendarDatePicker control pattern.

In the process of analyzing the template, we find that it includes another control that contains what we are looking for - CalendarView .


CalendarView included in the CalendarDatePicker template

• Get the CalendarView control pattern. We make the following changes in it:
• For <Button x: Name = "PreviousButton" /> and <Button x: Name = "NextButton" /> buttons, set the values ​​of the Content property to "& # xE0e2;" and "& # xE0e3;" respectively
• For the <TextBlock x: Name = "WeekDay6" /> and <TextBlock x: Name = "WeekDay7" /> fields, set the value of the Foreground property to Red
• Add a setter to the <Style x: Key = "WeekDayNameStyle" /> style
• In the Grid container containing <Button x: Name = "PreviousButton" /> and <Button x: Name = "NextButton" /> we make obvious changes in the layout for the task.

Is done. Thus, we took a ready-made template and brought its appearance in accordance with the requirements.

Modifying a template by adding new elements to it

Obviously, this method without access to Control.cs is very limited. The maximum that can be obtained from it is the addition of some kind of visual component, which can be diversified through the states of the VisualStateManager . This refers to the third way to extend existing controls.

To the above three methods one could add one more, the most interesting - the creation of a new control inheriting from the existing one using its template. But this way more to the next part of our story, which will be devoted to the topic of creating new controls.

Yang Moroz, Senior .NET Developer

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


All Articles