HandyOrg / HandyControl

Contains some simple and commonly used WPF controls
https://handyorg.github.io/
MIT License
6.18k stars 1.04k forks source link

鼠标悬停在带 ShowClearButton 的 TextBox 的边缘时发生闪烁 #1489

Open CodingOctocat opened 1 year ago

CodingOctocat commented 1 year ago

Describe the bug

移动鼠标指针到 hc:TextBox 的边缘(靠近 Clear 按钮处),发现 ClearButton 快速闪烁。 这个问题有些控件可以通过设置 UseLayoutRounding=False 来解决,但是这个方法对 hc:TextBox 无效。

动画

更新

我发现官方 Demo 也会有这个问题。

动画2

Steps to reproduce the bug

<hc:TextBox hc:InfoElement.ShowClearButton="True" Text="1234567890"/>

Expected behavior

No response

Screenshots

No response

NuGet package version

HandyControl 3.4.0

IDE

Visual Studio 2022

Framework type

.Net 6.0

Windows version

Windows 11 (22000)

Additional context

No response

CodingOctocat commented 11 months ago

https://github.com/HandyOrg/HandyControl/blob/babf40e59fe9e57eb185e4658ce46036610083f9/src/Shared/HandyControl_Shared/Themes/Styles/Base/TextBoxBaseStyle.xaml#L172-L192

解决方案:此处的 root 元素建议改为:

<hc:SimplePanel x:Name="root"
                Grid.Column="1">
    <Border x:Name="border"
            Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}" />
    <TextBlock Margin="{TemplateBinding Padding}"
               HorizontalAlignment="Stretch"
               VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
               Style="{StaticResource TextBlockDefaultThiLight}"
               Text="{Binding Path=(hc:InfoElement.Placeholder), RelativeSource={RelativeSource TemplatedParent}}"
               Visibility="{TemplateBinding Text,
                                            Converter={StaticResource String2VisibilityReConverter}}" />
    <ScrollViewer x:Name="PART_ContentHost"
                  Margin="-2,0,0,0"
                  Padding="{TemplateBinding Padding}"
                  VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                  Focusable="false"
                  HorizontalScrollBarVisibility="Hidden"
                  VerticalScrollBarVisibility="Hidden" />
    <Button Name="ButtonClear"
            Width="Auto"
            Height="Auto"
            Padding="8,0"
            HorizontalAlignment="Right"
            VerticalAlignment="Stretch"
            HorizontalContentAlignment="Left"
            hc:BorderElement.CornerRadius="4"
            hc:IconElement.Geometry="{StaticResource DeleteFillCircleGeometry}"
            hc:IconElement.Width="14"
            Background="{TemplateBinding Background}"
            Command="{x:Static hc:ControlCommands.Clear}"
            Foreground="{Binding BorderBrush, ElementName=border}"
            Style="{StaticResource ButtonIcon}"
            Visibility="Collapsed" />
</hc:SimplePanel>

TextBoxPlusTopTemplate 、SearchBar(带 Clear 按钮) 及有类似实现的地方也需一并更改。 TopTemplate 中需要使用 hc:TitleElement.MarginOnTheTop,但实际没有这个属性(#1498),暂时使用硬编码。

不使用两列的 Grid 还有一个好处,就是如果我使用 WrapPanel,当 TextBox 接近 WrapPanel 一行的宽度时,如果鼠标悬停在 TextBox 上面,那么 TextBox 会因为显示 Clear 按钮而变宽,导致换行,而换行后又失去鼠标悬停使得 Clear 按钮被隐藏折叠,TextBox 宽度减少而回退位置,循环往复,此效果如下:

动画

CodingOctocat commented 11 months ago

SearchBar 带 Clear 按钮闪烁问题解决方案:

    <ControlTemplate x:Key="SearchBarPlusTopTemplate"
                     TargetType="hc:SearchBar">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="{Binding Path=(hc:InfoElement.ContentHeight), RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Double2GridLengthConverter}}"
                               MinHeight="{Binding Path=(hc:InfoElement.MinContentHeight), RelativeSource={RelativeSource TemplatedParent}}" />
            </Grid.RowDefinitions>
            <DockPanel Margin="6,0,0,1"
                       HorizontalAlignment="{Binding Path=(hc:TitleElement.HorizontalAlignment), RelativeSource={RelativeSource TemplatedParent}}"
                       LastChildFill="True"
                       Visibility="{Binding Path=(hc:InfoElement.Title), RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource String2VisibilityConverter}}">
                <ContentPresenter Margin="4,0,0,0"
                                  Content="{Binding Path=(hc:InfoElement.Symbol), RelativeSource={RelativeSource TemplatedParent}}"
                                  DockPanel.Dock="Right"
                                  TextElement.Foreground="{DynamicResource DangerBrush}"
                                  Visibility="{Binding Path=(hc:InfoElement.Necessary), RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Boolean2VisibilityConverter}}" />
                <TextBlock hc:TextBlockAttach.AutoTooltip="True"
                           Text="{Binding Path=(hc:InfoElement.Title), RelativeSource={RelativeSource TemplatedParent}}"
                           TextTrimming="CharacterEllipsis"
                           TextWrapping="NoWrap" />
            </DockPanel>
            <Border x:Name="border"
                    Grid.Row="1"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}" />
            <Grid x:Name="root"
                  Grid.Row="1"
                  SnapsToDevicePixels="true">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <Button Name="ButtonClear"
                        Width="Auto"
                        Height="Auto"
                        Padding="4,0"
                        HorizontalAlignment="Right"
                        VerticalAlignment="Stretch"
                        HorizontalContentAlignment="Left"
⚠️ 设置 Panel.ZIndex="99" 为了防止代码格式化重排序,手动将 Clear 按钮置于文本框上,不然光标选中的是文本。
                        Panel.ZIndex="99"
                        hc:BorderElement.CornerRadius="4"
                        hc:IconElement.Geometry="{StaticResource DeleteFillCircleGeometry}"
                        hc:IconElement.Width="14"
                        Background="{TemplateBinding Background}"
                        Command="{x:Static hc:ControlCommands.Clear}"
                        Foreground="{Binding BorderBrush, ElementName=border}"
                        Style="{StaticResource ButtonIcon}"
                        Visibility="Collapsed" />
                <TextBlock Grid.Row="0"
                           Grid.Column="0"
                           Margin="{TemplateBinding Padding}"
                           HorizontalAlignment="Stretch"
                           VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                           Style="{StaticResource TextBlockDefaultThiLight}"
                           Text="{Binding Path=(hc:InfoElement.Placeholder), RelativeSource={RelativeSource TemplatedParent}}"
                           Visibility="{TemplateBinding Text,
                                                        Converter={StaticResource String2VisibilityReConverter}}" />
                <ScrollViewer x:Name="PART_ContentHost"
                              Grid.Row="0"
                              Grid.Column="0"
                              Margin="-2,0"
                              Padding="{TemplateBinding Padding}"
                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                              Focusable="false"
                              HorizontalScrollBarVisibility="Hidden"
                              VerticalScrollBarVisibility="Hidden" />
                <Button Grid.Row="0"
                        Grid.Column="1"
                        Width="Auto"
                        Height="Auto"
                        Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ThicknessSplitConverter}, ConverterParameter='0,0,1,0'}"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        HorizontalContentAlignment="Left"
                        hc:IconElement.Geometry="{StaticResource SearchGeometry}"
                        hc:IconElement.Width="14"
                        Command="{x:Static hc:ControlCommands.Search}"
                        Focusable="False"
                        Foreground="{TemplateBinding BorderBrush}"
                        Style="{StaticResource ButtonIcon}" />
            </Grid>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="IsEnabled" Value="false">
                <Setter TargetName="border" Property="Opacity" Value="0.4" />
                <Setter TargetName="root" Property="Opacity" Value="0.4" />
            </Trigger>
            <Trigger SourceName="root" Property="IsMouseOver" Value="true">
                <Setter Property="BorderBrush" Value="{DynamicResource SecondaryBorderBrush}" />
            </Trigger>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition SourceName="root" Property="IsMouseOver" Value="true" />
                    <Condition Property="hc:InfoElement.ShowClearButton" Value="True" />
                </MultiTrigger.Conditions>
                <Setter TargetName="ButtonClear" Property="Visibility" Value="Visible" />
            </MultiTrigger>
            <Trigger Property="IsFocused" Value="true">
                <Setter Property="BorderBrush" Value="{DynamicResource PrimaryBrush}" />
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <ControlTemplate x:Key="SearchBarPlusLeftTemplate"
                     TargetType="hc:SearchBar">
        <Grid Height="{Binding Path=(hc:InfoElement.ContentHeight), RelativeSource={RelativeSource TemplatedParent}}"
              MinHeight="{Binding Path=(hc:InfoElement.MinContentHeight), RelativeSource={RelativeSource TemplatedParent}}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="{Binding Path=(hc:InfoElement.TitleWidth), RelativeSource={RelativeSource TemplatedParent}}" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <DockPanel Margin="{Binding Path=(hc:TitleElement.MarginOnTheLeft), RelativeSource={RelativeSource TemplatedParent}}"
                       HorizontalAlignment="{Binding Path=(hc:TitleElement.HorizontalAlignment), RelativeSource={RelativeSource TemplatedParent}}"
                       VerticalAlignment="{Binding Path=(hc:TitleElement.VerticalAlignment), RelativeSource={RelativeSource TemplatedParent}}"
                       LastChildFill="True"
                       Visibility="{Binding Path=(hc:InfoElement.Title), RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource String2VisibilityConverter}}">
                <ContentPresenter Margin="4,0,0,0"
                                  Content="{Binding Path=(hc:InfoElement.Symbol), RelativeSource={RelativeSource TemplatedParent}}"
                                  DockPanel.Dock="Right"
                                  TextElement.Foreground="{DynamicResource DangerBrush}"
                                  Visibility="{Binding Path=(hc:InfoElement.Necessary), RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Boolean2VisibilityConverter}}" />
                <TextBlock hc:TextBlockAttach.AutoTooltip="True"
                           Text="{Binding Path=(hc:InfoElement.Title), RelativeSource={RelativeSource TemplatedParent}}"
                           TextTrimming="CharacterEllipsis"
                           TextWrapping="NoWrap" />
            </DockPanel>
            <Border x:Name="border"
                    Grid.Column="1"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}" />
            <Grid x:Name="root"
                  Grid.Column="1"
                  SnapsToDevicePixels="true">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <Button Name="ButtonClear"
                        Width="Auto"
                        Height="Auto"
                        Padding="4,0"
                        HorizontalAlignment="Right"
                        VerticalAlignment="Stretch"
                        HorizontalContentAlignment="Left"
⚠️ 设置 Panel.ZIndex="99" 为了防止代码格式化重排序,手动将 Clear 按钮置于文本框上,不然光标选中的是文本。
                        Panel.ZIndex="99"
                        hc:BorderElement.CornerRadius="4"
                        hc:IconElement.Geometry="{StaticResource DeleteFillCircleGeometry}"
                        hc:IconElement.Width="14"
                        Background="{TemplateBinding Background}"
                        Command="{x:Static hc:ControlCommands.Clear}"
                        Foreground="{Binding BorderBrush, ElementName=border}"
                        Style="{StaticResource ButtonIcon}"
                        Visibility="Collapsed" />
                <TextBlock Grid.Row="0"
                           Grid.Column="0"
                           Margin="{TemplateBinding Padding}"
                           HorizontalAlignment="Stretch"
                           VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                           Style="{StaticResource TextBlockDefaultThiLight}"
                           Text="{Binding Path=(hc:InfoElement.Placeholder), RelativeSource={RelativeSource TemplatedParent}}"
                           Visibility="{TemplateBinding Text,
                                                        Converter={StaticResource String2VisibilityReConverter}}" />
                <ScrollViewer x:Name="PART_ContentHost"
                              Grid.Row="0"
                              Grid.Column="0"
                              Margin="-2,0"
                              Padding="{TemplateBinding Padding}"
                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                              Focusable="false"
                              HorizontalScrollBarVisibility="Hidden"
                              VerticalScrollBarVisibility="Hidden" />
                <Button Grid.Row="0"
                        Grid.Column="1"
                        Width="Auto"
                        Height="Auto"
                        Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ThicknessSplitConverter}, ConverterParameter='0,0,1,0'}"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        HorizontalContentAlignment="Left"
                        hc:IconElement.Geometry="{StaticResource SearchGeometry}"
                        hc:IconElement.Width="14"
                        Command="{x:Static hc:ControlCommands.Search}"
                        Focusable="False"
                        Foreground="{TemplateBinding BorderBrush}"
                        Style="{StaticResource ButtonIcon}" />
            </Grid>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="IsEnabled" Value="false">
                <Setter TargetName="border" Property="Opacity" Value="0.4" />
                <Setter TargetName="root" Property="Opacity" Value="0.4" />
            </Trigger>
            <Trigger SourceName="root" Property="IsMouseOver" Value="true">
                <Setter Property="BorderBrush" Value="{DynamicResource SecondaryBorderBrush}" />
            </Trigger>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition SourceName="root" Property="IsMouseOver" Value="true" />
                    <Condition Property="hc:InfoElement.ShowClearButton" Value="True" />
                </MultiTrigger.Conditions>
                <Setter TargetName="ButtonClear" Property="Visibility" Value="Visible" />
            </MultiTrigger>
            <Trigger Property="IsFocused" Value="true">
                <Setter Property="BorderBrush" Value="{DynamicResource PrimaryBrush}" />
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
CodingOctocat commented 11 months ago

最终解决方案参考:

FixHandyControlTextBoxPlusTemplate.xaml

FixHandyControlSearchBarPlusTemplate.xaml