HandyControl icon indicating copy to clipboard operation
HandyControl copied to clipboard

Do not use the Style property to provide default appearance, use DefaultStyleKey instead.

Open yll690 opened this issue 2 years ago β€’ 7 comments

You should use the DefaultStyle property to provide default appearance and let user to use the Style property to modify it.

Now you occupied the Style property, so we can only use a style with a BasedOn. Without it, the new style with few setters will replace the original style and makes the control invisible.

Why not set the DefaultStyleKey property like normal custom controls? Using the DefaultStyleKey can also help the control display normally even without adding the HandyControl's resource dictionary.

yll690 avatar Oct 15 '21 06:10 yll690

It is a problem to have 2 Style properties. What style setters have the priority on the others ?

  • If the Style setter properties have the priority : in that case, It goes the same way that the BasedOn. (And you have the same problem)
  • If the DefaultStyleKey setter properties have the priority : Here , you will be able to modify only the setter that are not in the DefaultStyleKey

You can find here the NumericUpDownBaseStyle.xaml

If you give a little of context, me or someone else should be able to help you.

Yopler avatar Oct 15 '21 08:10 Yopler

You can create a new project using HandyControl and add a NumericUpDown to the window. Then set its Style property to null: Style="{x:Null}" , it will disappear.

Then add a CheckBox to the window and set itsStyle property to null. It will not disappear. See the difference?

CheckBox uses DefaultStyle or DefaultStyleKey to provide the default appearence, so setting the Style to null will not affect it. The Style setter properties definitely have the priority. But if I set an empty style or null to Style, nothing should happen.

yll690 avatar Oct 15 '21 15:10 yll690

  1. you said 'so we can only use a style with a BasedOn', it's wrong, for instance:
<Style x:Key="NumericUpDownExtend" TargetType="hc:NumericUpDown">
...
</Style>

It will works without BasedOn

  1. you also said 'Using the DefaultStyleKey can also help the control display normally even without adding the HandyControl's resource dictionary', it's only half right, we also need generic.xaml, yes, we can put hc default styles in it, make styles invalid without adding resource dictionary, while for native controls, we can not put detault styles in generic.xaml, for instance, try this style without key:
<Style TargetType="Button"/>

thus, we can only divide styles into HC and native, that means you have to refer to the resource dictionary. Instead, it's better not to divide, just put them all together.

NaBian avatar Oct 15 '21 17:10 NaBian

  1. Define a style with key: <Style x:Key="NumericUpDownExtend" TargetType="hc:NumericUpDown"/> and then apply to a NumericUpDown: <hc:NumericUpDown Style="{StaticResource NumericUpDownExtend}"/>, it will disappear, too. Meanwhile, doing the same thing to CheckBox will not make it disappear.

  2. According to the official document or magazine, you must use DefaultStyleKey

When you put a ControlTemplate in any of the theme-specific resource dictionary files, you must create a static constructor for your control and call the OverrideMetadata(Type, PropertyMetadata) method on the DefaultStyleKey.

Also, most times I just want to use some controls in HandyControl, not apply your skin to my whole project. So you can put your style of native controls to generic.xaml without key if you use DefaultStyleKey. Then if I just want to use some controls, I can only add the control, without referencing the generic.xaml. If I want to apply your skin to my whole window or project, I can reference the generic.xaml. Also this is a simpler way to solve this issue.

yll690 avatar Oct 18 '21 02:10 yll690

@ghost1372 Any ideas?

yll690 avatar Oct 18 '21 02:10 yll690

HeyπŸ– , @NaBian @ghost1372 @akimusume I think I just found why this happen 😊 ! I got a similar problem by creating new control using the same code structure as Handy ! The fact is that Handy create a "Template" for a control in a Style. So if you make it {x:Null} you will break the Template and found the default style of WPF control on which the control inherit (not sure of the formulation there πŸ˜…).

πŸ‘‰ The only link of the Template of a new Control and the Control is the Style property.

If I take an example : hc:CheckComboBox If I give {x:Null} to the style property : i found a listbox with the basic wpf style. (because checkcobobox inherit listbox) CheckComboBox.cs

public class CheckComboBox : ListBox, IDataInput
{
     ....
}

and his style with the Template : CheckComboBoxBaseStyle.xaml

<ControlTemplate x:Key="CheckComboBoxTemplate" TargetType="hc:CheckComboBox">
        <Grid x:Name="templateRoot" SnapsToDevicePixels="true">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition Width="30"/>
            </Grid.ColumnDefinitions>
            <Popup x:Name="PART_Popup" PlacementTarget="{Binding ElementName=toggleButton}" StaysOpen="False" IsOpen="{Binding IsDropDownOpen,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" AllowsTransparency="true" Grid.ColumnSpan="2" PopupAnimation="{StaticResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
                <Decorator Margin="8 0">
                    <Border Effect="{StaticResource EffectShadow2}" BorderThickness="0,1,0,0" Margin="0,0,0,8" CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" x:Name="dropDownBorder" MaxHeight="{TemplateBinding MaxDropDownHeight}" BorderBrush="{DynamicResource BorderBrush}" Background="{DynamicResource RegionBrush}">
                        <hc:ToggleBlock IsChecked="{Binding HasItems,RelativeSource={RelativeSource TemplatedParent},Mode=OneWay}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch">
                            <hc:ToggleBlock.CheckedContent>
                                <Grid Margin="0,4" ClipToBounds="False">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition/>
                                    </Grid.RowDefinitions>
                                    <hc:CheckComboBoxItem x:Name="PART_SelectAll"  Style="{TemplateBinding ItemContainerStyle}" IsEnabled="{TemplateBinding ShowSelectAllButton}" Visibility="{Binding ShowSelectAllButton,RelativeSource={RelativeSource TemplatedParent},Converter={StaticResource Boolean2VisibilityConverter}}" HorizontalContentAlignment="Stretch" Content="{ex:Lang Key={x:Static langs:LangKeys.All}}"/>
                                    <ScrollViewer x:Name="DropDownScrollViewer" Grid.Row="1">
                                        <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                    </ScrollViewer>
                                </Grid>
                            </hc:ToggleBlock.CheckedContent>
                            <hc:ToggleBlock.UnCheckedContent>
                                <hc:Empty/>
                            </hc:ToggleBlock.UnCheckedContent>
                        </hc:ToggleBlock>
                    </Border>
                </Decorator>
            </Popup>
            <ToggleButton Grid.Column="0" x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" Style="{StaticResource CheckComboBoxToggleButton}"/>
            <Border Grid.Column="0" Margin="-1,1">
                <hc:UniformSpacingPanel x:Name="PART_Panel" Margin="{TemplateBinding Padding}" Spacing="4" ChildWrapping="Wrap" ItemVerticalAlignment="Center"/>
            </Border>
        </Grid>
    </ControlTemplate>

πŸ’‘ The idea: It is good that Handy create style for all control and his new ones !! But the Template property should not be define in the Style ! I have not finish my test but : If new control was create like a UserControl the template will not be broken. It is better with an example :

CheckComboBox.xaml.cs (still the same except that I make it .xaml.cs as a usercontrol but actually it is just a convention)

public class CheckComboBox : ListBox, IDataInput
{
     ....
}

Here the Template and "Style" to imitate basic style of wpf

CheckComboBox.xaml

<ListBox  x:Class="HandyControl.Controls.CheckComboBox"
...
Border="..."
<!-- default property-->
>
<ListBox.Template>
        <ControlTemplate x:Key="CheckComboBoxTemplate" TargetType="hc:CheckComboBox">
        <Grid x:Name="templateRoot" SnapsToDevicePixels="true">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition Width="30"/>
            </Grid.ColumnDefinitions>
            <Popup x:Name="PART_Popup" PlacementTarget="{Binding ElementName=toggleButton}" StaysOpen="False" IsOpen="{Binding IsDropDownOpen,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" AllowsTransparency="true" Grid.ColumnSpan="2" PopupAnimation="{StaticResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
                <Decorator Margin="8 0">
                    <Border Effect="{StaticResource EffectShadow2}" BorderThickness="0,1,0,0" Margin="0,0,0,8" CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" x:Name="dropDownBorder" MaxHeight="{TemplateBinding MaxDropDownHeight}" BorderBrush="{DynamicResource BorderBrush}" Background="{DynamicResource RegionBrush}">
                        <hc:ToggleBlock IsChecked="{Binding HasItems,RelativeSource={RelativeSource TemplatedParent},Mode=OneWay}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch">
                            <hc:ToggleBlock.CheckedContent>
                                <Grid Margin="0,4" ClipToBounds="False">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition/>
                                    </Grid.RowDefinitions>
                                    <hc:CheckComboBoxItem x:Name="PART_SelectAll"  Style="{TemplateBinding ItemContainerStyle}" IsEnabled="{TemplateBinding ShowSelectAllButton}" Visibility="{Binding ShowSelectAllButton,RelativeSource={RelativeSource TemplatedParent},Converter={StaticResource Boolean2VisibilityConverter}}" HorizontalContentAlignment="Stretch" Content="{ex:Lang Key={x:Static langs:LangKeys.All}}"/>
                                    <ScrollViewer x:Name="DropDownScrollViewer" Grid.Row="1">
                                        <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                    </ScrollViewer>
                                </Grid>
                            </hc:ToggleBlock.CheckedContent>
                            <hc:ToggleBlock.UnCheckedContent>
                                <hc:Empty/>
                            </hc:ToggleBlock.UnCheckedContent>
                        </hc:ToggleBlock>
                    </Border>
                </Decorator>
            </Popup>
            <ToggleButton Grid.Column="0" x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" Style="{StaticResource CheckComboBoxToggleButton}"/>
            <Border Grid.Column="0" Margin="-1,1">
                <hc:UniformSpacingPanel x:Name="PART_Panel" Margin="{TemplateBinding Padding}" Spacing="4" ChildWrapping="Wrap" ItemVerticalAlignment="Center"/>
            </Border>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="HasItems" Value="false">
                <Setter Property="Height" TargetName="dropDownBorder" Value="95"/>
            </Trigger>
            <Trigger Property="hc:DropDownElement.ConsistentWidth" Value="True">
                <Setter Property="MaxWidth" TargetName="dropDownBorder" Value="{Binding ActualWidth, ElementName=toggleButton}"/>
                <Setter Property="MinWidth" TargetName="dropDownBorder" Value="{Binding ActualWidth, ElementName=toggleButton}"/>
            </Trigger>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsGrouping" Value="true"/>
                    <Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
                </MultiTrigger.Conditions>
                <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
            </MultiTrigger>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsMouseOver" Value="true" SourceName="toggleButton"/>
                    <Condition Property="IsOpen" Value="false" SourceName="PART_Popup"/>
                </MultiTrigger.Conditions>
                <Setter Property="BorderBrush" Value="{DynamicResource SecondaryBorderBrush}"/>
            </MultiTrigger>
            <Trigger Property="IsOpen" Value="True" SourceName="PART_Popup">
                <Setter Property="BorderBrush" Value="{DynamicResource PrimaryBrush}"/>
            </Trigger>
            <Trigger Property="IsFocused" Value="True">
                <Setter Property="BorderBrush" Value="{DynamicResource PrimaryBrush}"/>
            </Trigger>
            <Trigger Property="IsEnabled" Value="false">
                <Setter Property="Opacity" Value="0.4" TargetName="toggleButton"/>
                <Setter Property="Opacity" Value="0.4" TargetName="PART_Panel"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
</ListBox.Template>
</ListBox>

CheckComboxBoxStyle.xaml

<!-- style of handy -->
<Style TargetType="hc:CheckComboBox" BasedOn="{StaticResource CheckComboBoxBaseStyle}"/>

In fact there is no main change for the final user and his way to access the control, and control become true new wpf control. I need to finish my test but for now it works this way !

Yopler avatar Nov 05 '21 14:11 Yopler

To be honest, if you understand the difference between Style and DefaultStyleKey, you will find the current implementation is incorrect. You really should follow the instruction of official documents and examples(this, this, this and this).

The style assigned by Style has higher priority over whitch assigned by DefaultStyleKey . DefaultStyleKey is for control developers to define the basic appearance and template of custom controls. Style is for control users to unify the controls' appearance in their project. The control developers should not use Style and the control users should not use DefaultStyleKey, too.

The other control developers also use DefaultStyleKey to provide default appearance: wpftoolkit-Calculator, ModernWpf-SplitButton, Fluent.Ribbon-Gallery, MahApps.Metro-FlipView. There are way more examples you can reference.

yll690 avatar Jan 06 '22 07:01 yll690