This commit is contained in:
Giuseppe Lippolis 2023-11-16 11:59:10 +01:00
Родитель bdfe1444d0
Коммит daf3c8d341
1 изменённых файлов: 161 добавлений и 57 удалений

Просмотреть файл

@ -384,24 +384,24 @@ NOTE: Because we use the `as`-operator, our `Value` would become `null` if the `
=== Step 8: Add a Style for the RatingControl === Step 8: Add a `ControlTheme` for the RatingControl
While we can already add a `RatingControl` to our View, we will see nothing as there is no `Style` available. To change this we add another folder called `Styles`. Add a file called `RatingStyles.axaml` which is of type `Styles (Avalonia)`. While we can already add a `RatingControl` to our View, we will see nothing as there is no `RatingControl` available. To change this we add another folder called `Styles`. Add a file called `RatingStyles.axaml` which is of type `ResourceDictionary (Avalonia)`.
First of all we need to add the needed namespaces to our `Style`: First of all we need to add the needed namespaces to our `ResourceDictionary`:
[source,xml] [source,xml]
---- ----
<Styles xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:controls="using:RatingControlSample.Controls" xmlns:controls="using:RatingControlSample.Controls"
xmlns:converter="using:RatingControlSample.Converter" xmlns:converter="using:RatingControlSample.Converter"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Styles> </ResourceDictionary>
---- ----
[TIP] [TIP]
==== ====
If you want to have preview of the `Style`, just add one or more `RatingControls` to the `Design.PreviewWith`-section: If you want to have preview of the `ResourceDictionary`, just add one or more `RatingControls` to the `Design.PreviewWith`-section:
[source,xml] [source,xml]
---- ----
<Design.PreviewWith> <Design.PreviewWith>
@ -414,56 +414,56 @@ If you want to have preview of the `Style`, just add one or more `RatingControls
---- ----
==== ====
Now we can add the needed `Styles` to represent our `RatingControl`. The important part is the `ControlTemplate` which has the following hierarchy: Now we can add the needed `ControlTheme` to represent our `RatingControl`. The important part is the `ControlTemplate` which has the following hierarchy:
[source,xml] [source,xml]
---- ----
<!--This is the Style for our RatingControl--> <!--This is the ControlTheme for our RatingControl-->
<Style Selector="controls|RatingControl"> <ControlTheme x:Key="{x:Type controls:RatingControl}" TargetType="controls:RatingControl">
<!--We need to add our IsSmallerOrEqualConverter here as a Resource--> <ControlTheme.Resources>
<Style.Resources> <!--We need to add our IsSmallerOrEqualConverter here as a Resource-->
<converter:IsSmallerOrEqualConverter x:Key="IsSmallerOrEqualConverter" /> <converter:IsSmallerOrEqualConverter x:Key="IsSmallerOrEqualConverter" />
</Style.Resources> </ControlTheme.Resources>
<!--Every TemplatedControl needs to have a ControlTemplate applied. In this setter we define it.--> <!--Every TemplatedControl needs to have a ControlTemplate applied. In this setter we define it.-->
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<!--We wrap our content inside a DataValidationErrors-control, so error messages are displayed properly--> <!--We wrap our content inside a DataValidationErrors-control, so error messages are displayed properly-->
<DataValidationErrors> <DataValidationErrors>
<!--This is our stars-presenter. Make sure to set the name, so the control can be found.-->
<!--This is our stars-presenter. Make sure to set the name, so the control can be found.--> <ItemsControl x:Name="PART_StarsPresenter"
<ItemsControl x:Name="PART_StarsPresenter" ItemsSource="{TemplateBinding Stars}">
ItemsSource="{TemplateBinding Stars}"> <!--We want to have the stars drawn horizontally. Therefore we change the ItemsPanel accordingly-->
<!--We want to have the stars drawn horizontally. Therefore we change the ItemsPanel accordingly--> <ItemsControl.ItemsPanel>
<ItemsControl.ItemsPanel> <ItemsPanelTemplate>
<ItemsPanelTemplate> <StackPanel Orientation="Horizontal"
<StackPanel Orientation="Horizontal" Spacing="5" />
Spacing="5" /> </ItemsPanelTemplate>
</ItemsPanelTemplate> </ItemsControl.ItemsPanel>
</ItemsControl.ItemsPanel>
<!--Items is an array if integer. Let's add a Path as the DataTemplate-->
<!--Items is an array if integer. Let's add a Path as the DataTemplate--> <ItemsControl.ItemTemplate>
<ItemsControl.ItemTemplate> <DataTemplate>
<DataTemplate> <Path Classes="star">
<Path Classes="star"> <!--We enable or disable classes through a boolean value. We use our IsSmallerOrEqualConverter to do so. -->
<!--We enable or disable classes through a boolean value. We use our IsSmallerOrEqualConverter to do so. --> <Classes.selected>
<Classes.selected> <MultiBinding Converter="{StaticResource IsSmallerOrEqualConverter}">
<MultiBinding Converter="{StaticResource IsSmallerOrEqualConverter}"> <!--This is our dataContext, so the number of this item-->
<!--This is our dataContext, so the number of this item--> <Binding />
<Binding /> <!--This is the binding to the RatingControls current value-->
<!--This is the binding to the RatingControls current value--> <Binding RelativeSource="{RelativeSource AncestorType=controls:RatingControl}" Path="Value" />
<Binding RelativeSource="{RelativeSource AncestorType=controls:RatingControl}" Path="Value" /> </MultiBinding>
</MultiBinding> </Classes.selected>
</Classes.selected> </Path>
</Path> </DataTemplate>
</DataTemplate> </ItemsControl.ItemTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
</DataValidationErrors> </DataValidationErrors>
</ControlTemplate>
</ControlTemplate>
</Setter> </Setter>
</Style> </ControlTheme>
---- ----
In the above snippet you can see that the `ControlTemplate` our `RatingControl` has the following structure: In the above snippet you can see that the `ControlTemplate` our `RatingControl` has the following structure:
@ -534,17 +534,121 @@ Last but not least we want a visual feedback if the user hovers a star with thei
</Style> </Style>
---- ----
==== Step 9: Create a sample to try-out the custom Control === Step 9: Theme Variant
In Avalonia an external `Style`-file needs to be added via `StyleInclude` into the `Styles`-node of your choice before it gets applied. We will add it into `App.Styles` as shown below: if you want to add support to the theme variant, you need to replace the encoded setter value with `DynamicResource`. In this case we would like a different filling and stroke of the Path based on the theme variant.
To do this, modify our style selector like this:
[source,xml] [source,xml]
---- ----
<Application.Styles> <!--This Style is for a Path which has the Class "star" applied.-->
<FluentTheme /> <Style Selector="Path.star" >
<!-- Don't miss this line --> <Setter Property="Data" Value="M 3.9687501,0 5.1351364,2.3633569 7.7432556,2.7423389 5.8560028,4.5819556 6.3015226,7.1795363 3.96875,5.953125 1.6359772,7.1795361 2.0814972,4.5819556 0.19424448,2.7423387 2.8023636,2.3633569 Z" />
<StyleInclude Source="/Styles/RatingStyles.axaml" /> <Setter Property="Width" Value="32" />
</Application.Styles> <Setter Property="Height" Value="32" />
<Setter Property="Margin" Value="5" />
<Setter Property="Fill" Value="{DynamicResource RatingControlUnselectedBrush}" />
<Setter Property="Stroke" Value="{DynamicResource RatingControlUnselectedStrokenBrush}" />
<Setter Property="StrokeThickness" Value="2" />
<Setter Property="Stretch" Value="Uniform" />
</Style>
<Style Selector="Path.selected" >
<Setter Property="Fill" Value="{DynamicResource RatingControlSelectedBrush}" />
<Setter Property="Stroke" Value="{DynamicResource RatingControlSelectedStrokenBrush}" />
</Style>
<Style Selector="Path.star:pointerover" >
<Setter Property="RenderTransform" Value="scale(1.3)" />
<Setter Property="Fill" Value="{DynamicResource RatingControlSelectedStrokenBrush}" />
</Style>
----
now, will be define Resource for each Theme Variant
[source, xml]
----
<!-- Define the Theme Variants -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<!-- Selected Brushes-->
<SolidColorBrush x:Key="RatingControlSelectedBrush" Color="Gold"/>
<SolidColorBrush x:Key="RatingControlSelectedStrokenBrush" Color="Goldenrod"/>
<!-- Unselected Brushes-->
<SolidColorBrush x:Key="RatingControlUnselectedBrush" Color="White"/>
<SolidColorBrush x:Key="RatingControlUnselectedStrokenBrush" Color="Gray"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<!-- Selected Brushes-->
<SolidColorBrush x:Key="RatingControlSelectedBrush" Color="Gold"/>
<SolidColorBrush x:Key="RatingControlSelectedStrokenBrush" Color="Gray"/>
<!-- Unselected Brushes-->
<SolidColorBrush x:Key="RatingControlUnselectedBrush" Color="White"/>
<SolidColorBrush x:Key="RatingControlUnselectedStrokenBrush" Color="Gray"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<!-- Selected Brushes-->
<SolidColorBrush x:Key="RatingControlSelectedBrush" Color="Red"/>
<SolidColorBrush x:Key="RatingControlSelectedStrokenBrush" Color="White"/>
<!-- Unselected Brushes-->
<SolidColorBrush x:Key="RatingControlUnselectedBrush" Color="Transparent"/>
<SolidColorBrush x:Key="RatingControlUnselectedStrokenBrush" Color="White"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
----
[TIP]
====
If you want to have preview of the `ResourceDictionary` support Theme Variants, just add one or more `RatingControls` to the `Design.PreviewWith`-section:
[source,xml]
----
<!-- Design time preview -->
<Design.PreviewWith>
<StackPanel Width="400" Spacing="10">
<!-- Force using default Theme Variant -->
<ThemeVariantScope RequestedThemeVariant="Default">
<StackPanel Spacing="10" Background="{DynamicResource SystemRegionBrush}">
<controls:RatingControl Value="0" NumberOfStars="5" />
<controls:RatingControl Value="2" NumberOfStars="5" />
<controls:RatingControl Value="6" NumberOfStars="6" />
</StackPanel>
</ThemeVariantScope>
<!-- Force using Light Theme Variant -->
<ThemeVariantScope RequestedThemeVariant="Light">
<StackPanel Spacing="10" Background="{DynamicResource SystemRegionBrush}">
<controls:RatingControl Value="0" NumberOfStars="5" />
<controls:RatingControl Value="2" NumberOfStars="5" />
<controls:RatingControl Value="6" NumberOfStars="6" />
</StackPanel>
</ThemeVariantScope>
<!-- Force using Dark Theme Variant -->
<ThemeVariantScope RequestedThemeVariant="Dark">
<StackPanel Spacing="10" Background="{DynamicResource SystemRegionBrush}">
<controls:RatingControl Value="0" NumberOfStars="5" />
<controls:RatingControl Value="2" NumberOfStars="5" />
<controls:RatingControl Value="6" NumberOfStars="6" />
</StackPanel>
</ThemeVariantScope>
</StackPanel>
</Design.PreviewWith>
----
====
=== Step 10: Create a sample to try-out the custom Control
In Avalonia an external `ResourceDictionary`-file needs to be added via `ResourceInclude` into the `Resources`-node of your choice before it gets applied. We will add it into `App.Resources` as shown below:
[source,xml]
----
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Don't miss this line -->
<ResourceInclude Source="/Styles/RatingStyles.axaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
---- ----
WARNING: You need to do this for every project where you want to use this control. You will not see any custom control if you forgot to add this line. WARNING: You need to do this for every project where you want to use this control. You will not see any custom control if you forgot to add this line.
@ -574,7 +678,7 @@ Now we can use the control in any view like shown below:
NOTE: For the complete sample including the `ViewModel` please see the source code of this sample. NOTE: For the complete sample including the `ViewModel` please see the source code of this sample.
=== Step 10: See it in action === Step 11: See it in action
We are all done. Hit [Run] or [Debug] in your IDE and you can see the control in action. We are all done. Hit [Run] or [Debug] in your IDE and you can see the control in action.