Skip to content

Commit

Permalink
[SmartHint] Hint placement for "readonly" controls (#3764)
Browse files Browse the repository at this point in the history
* Demo app workaround/hack for initial miscalculation of hint placement

* Ensure ComboBox smart hint behaves according to MD spec

According to the "Behavior" section of the "Menus" page in the link below, the hint should float even when there is no selection, but the popup is open.

https://m2.material.io/components/menus#exposed-dropdown-menu

* Fix hint positioning issue when "readonly" element

The issue was present for all controls once they are in "readonly" state. Special handling of the case where the element is readonly, and the hint should follow the prefix/suffix needed to be added because of the "business rule" that prefix/suffix is always visible when the element is "readonly".

* Fixing so the hack is no longer needed

* Fix AutoSuggestBox as well

---------

Co-authored-by: Kevin Bost <[email protected]>
  • Loading branch information
nicolaihenriksen and Keboo authored Jan 10, 2025
1 parent 9e71c5c commit 070e8bb
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public class FloatingHintInitialHorizontalOffsetConverter : IMultiValueConverter
PrefixSuffixVisibility suffixVisibility,
PrefixSuffixHintBehavior prefixHintBehavior,
PrefixSuffixHintBehavior suffixHintBehavior,
HorizontalAlignment horizontalContentAlignment
HorizontalAlignment horizontalContentAlignment,
bool isEditable,
])
{
return 0;
Expand All @@ -37,8 +38,11 @@ double GetLeftOffset()
return prefixVisibility switch
{
PrefixSuffixVisibility.WhenFocusedOrNonEmpty
when prefixHintBehavior == PrefixSuffixHintBehavior.AlignWithText =>
when prefixHintBehavior == PrefixSuffixHintBehavior.AlignWithText && isEditable =>
prefixWidth + prefixMargin.Right,
PrefixSuffixVisibility.WhenFocusedOrNonEmpty
when prefixHintBehavior == PrefixSuffixHintBehavior.AlignWithPrefixSuffix && !isEditable =>
-(prefixWidth + prefixMargin.Right),
PrefixSuffixVisibility.Always
when prefixHintBehavior == PrefixSuffixHintBehavior.AlignWithPrefixSuffix =>
-(prefixWidth + prefixMargin.Right),
Expand All @@ -51,8 +55,11 @@ double GetRightOffset()
return suffixVisibility switch
{
PrefixSuffixVisibility.WhenFocusedOrNonEmpty
when suffixHintBehavior == PrefixSuffixHintBehavior.AlignWithText =>
when suffixHintBehavior == PrefixSuffixHintBehavior.AlignWithText && isEditable =>
-(suffixWidth + suffixMargin.Left),
PrefixSuffixVisibility.WhenFocusedOrNonEmpty
when suffixHintBehavior == PrefixSuffixHintBehavior.AlignWithPrefixSuffix && !isEditable =>
suffixWidth + suffixMargin.Left,
PrefixSuffixVisibility.Always
when suffixHintBehavior == PrefixSuffixHintBehavior.AlignWithPrefixSuffix =>
suffixWidth + suffixMargin.Left,
Expand Down
2 changes: 1 addition & 1 deletion src/MaterialDesignThemes.Wpf/SmartHint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ private void RefreshState(bool useTransitions)
string state = string.Empty;

bool isEmpty = proxy.IsEmpty();
bool isFocused = proxy.IsFocused();
bool isFocused = HintHost?.IsKeyboardFocusWithin ?? proxy.IsFocused();

if (UseFloating)
state = !isEmpty || isFocused ? HintFloatingPositionName : HintRestingPositionName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,29 @@
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{DynamicResource MaterialDesign.Brush.TextBox.HoverBackground}"
CornerRadius="{TemplateBinding wpf:TextFieldAssist.TextFieldCornerRadius}"
RenderTransformOrigin="0.5,0.5"
Visibility="{TemplateBinding wpf:TextFieldAssist.RippleOnFocusEnabled, Converter={x:Static converters:BooleanToVisibilityConverter.CollapsedInstance}}">
VerticalAlignment="Stretch"
Background="{DynamicResource MaterialDesign.Brush.TextBox.HoverBackground}"
CornerRadius="{TemplateBinding wpf:TextFieldAssist.TextFieldCornerRadius}"
RenderTransformOrigin="0.5,0.5"
Visibility="{TemplateBinding wpf:TextFieldAssist.RippleOnFocusEnabled, Converter={x:Static converters:BooleanToVisibilityConverter.CollapsedInstance}}">
<Border.RenderTransform>
<ScaleTransform x:Name="RippleOnFocusScaleTransform" ScaleX="0" ScaleY="0" />
</Border.RenderTransform>
</Border>
<AdornerDecorator>
<Border x:Name="OuterBorder"
Padding="{TemplateBinding Padding}"
wpf:BottomDashedLineAdorner.Brush="{TemplateBinding BorderBrush}"
wpf:BottomDashedLineAdorner.Thickness="{Binding RelativeSource={RelativeSource Self}, Path=BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding wpf:TextFieldAssist.TextFieldCornerRadius}"
SnapsToDevicePixels="True">
Padding="{TemplateBinding Padding}"
wpf:BottomDashedLineAdorner.Brush="{TemplateBinding BorderBrush}"
wpf:BottomDashedLineAdorner.Thickness="{Binding RelativeSource={RelativeSource Self}, Path=BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding wpf:TextFieldAssist.TextFieldCornerRadius}"
SnapsToDevicePixels="True">

<Grid x:Name="ContentGrid"
MinHeight="16"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
MinHeight="16"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
Expand All @@ -104,32 +104,14 @@
</Grid.ColumnDefinitions>

<wpf:PackIcon x:Name="LeadingPackIcon"
Grid.Column="0"
Width="{TemplateBinding wpf:TextFieldAssist.LeadingIconSize}"
Height="{TemplateBinding wpf:TextFieldAssist.LeadingIconSize}"
Margin="0,0,6,0"
VerticalAlignment="{TemplateBinding wpf:TextFieldAssist.IconVerticalAlignment}"
Kind="{TemplateBinding wpf:TextFieldAssist.LeadingIcon}"
Opacity="{TemplateBinding wpf:HintAssist.HintOpacity}"
Visibility="{TemplateBinding wpf:TextFieldAssist.HasLeadingIcon, Converter={x:Static converters:BooleanToVisibilityConverter.CollapsedInstance}}" />

<TextBlock x:Name="PrefixTextBlock"
Grid.Column="1"
Margin="0,0,2,0"
VerticalAlignment="Center"
FontSize="{TemplateBinding FontSize}"
Opacity="{TemplateBinding wpf:HintAssist.HintOpacity}"
Text="{TemplateBinding wpf:TextFieldAssist.PrefixText}">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource PrefixSuffixTextVisibilityConverter}">
<Binding ElementName="Hint" Path="IsHintInFloatingPosition" />
<Binding Path="(wpf:TextFieldAssist.PrefixText)" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="(wpf:TextFieldAssist.PrefixTextVisibility)" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsKeyboardFocusWithin" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsReadOnly" RelativeSource="{RelativeSource TemplatedParent}" Converter="{x:Static converters:InvertBooleanConverter.Instance}" />
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>
Grid.Column="0"
Width="{TemplateBinding wpf:TextFieldAssist.LeadingIconSize}"
Height="{TemplateBinding wpf:TextFieldAssist.LeadingIconSize}"
Margin="0,0,6,0"
VerticalAlignment="{TemplateBinding wpf:TextFieldAssist.IconVerticalAlignment}"
Kind="{TemplateBinding wpf:TextFieldAssist.LeadingIcon}"
Opacity="{TemplateBinding wpf:HintAssist.HintOpacity}"
Visibility="{TemplateBinding wpf:TextFieldAssist.HasLeadingIcon, Converter={x:Static converters:BooleanToVisibilityConverter.CollapsedInstance}}" />

<ScrollViewer x:Name="PART_ContentHost"
Grid.Column="2"
Expand Down Expand Up @@ -174,6 +156,7 @@
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:TextFieldAssist.PrefixTextHintBehavior)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:TextFieldAssist.SuffixTextHintBehavior)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="HorizontalContentAlignment" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="IsReadOnly" Converter="{x:Static converters:InvertBooleanConverter.Instance}" />
</MultiBinding>
</wpf:SmartHint.InitialHorizontalOffset>
<wpf:SmartHint.Margin>
Expand All @@ -191,20 +174,38 @@
</wpf:SmartHint.Margin>
<wpf:SmartHint.Hint>
<Border x:Name="HintBackgroundBorder"
Background="{TemplateBinding wpf:HintAssist.Background}"
CornerRadius="2">
Background="{TemplateBinding wpf:HintAssist.Background}"
CornerRadius="2">
<ContentPresenter x:Name="HintWrapper" Content="{TemplateBinding wpf:HintAssist.Hint}" />
</Border>
</wpf:SmartHint.Hint>
</wpf:SmartHint>

<TextBlock x:Name="PrefixTextBlock"
Grid.Column="1"
Margin="0,0,2,0"
VerticalAlignment="Center"
FontSize="{TemplateBinding FontSize}"
Opacity="{TemplateBinding wpf:HintAssist.HintOpacity}"
Text="{TemplateBinding wpf:TextFieldAssist.PrefixText}">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource PrefixSuffixTextVisibilityConverter}">
<Binding ElementName="Hint" Path="IsHintInFloatingPosition" />
<Binding Path="(wpf:TextFieldAssist.PrefixText)" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="(wpf:TextFieldAssist.PrefixTextVisibility)" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsKeyboardFocusWithin" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsReadOnly" RelativeSource="{RelativeSource TemplatedParent}" Converter="{x:Static converters:InvertBooleanConverter.Instance}" />
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>

<TextBlock x:Name="SuffixTextBlock"
Grid.Column="3"
Margin="2,0,0,0"
VerticalAlignment="Center"
FontSize="{TemplateBinding FontSize}"
Opacity="{TemplateBinding wpf:HintAssist.HintOpacity}"
Text="{TemplateBinding wpf:TextFieldAssist.SuffixText}">
Grid.Column="3"
Margin="2,0,0,0"
VerticalAlignment="Center"
FontSize="{TemplateBinding FontSize}"
Opacity="{TemplateBinding wpf:HintAssist.HintOpacity}"
Text="{TemplateBinding wpf:TextFieldAssist.SuffixText}">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource PrefixSuffixTextVisibilityConverter}">
<Binding ElementName="Hint" Path="IsHintInFloatingPosition" />
Expand Down Expand Up @@ -288,11 +289,10 @@
CornerRadius="{TemplateBinding wpf:TextFieldAssist.UnderlineCornerRadius}"
Visibility="{TemplateBinding wpf:TextFieldAssist.DecorationVisibility}" />

<Canvas VerticalAlignment="Bottom"
IsHitTestVisible="False">
<Canvas VerticalAlignment="Bottom" IsHitTestVisible="False">
<Border Canvas.Top="2"
Padding="{TemplateBinding Padding, Converter={StaticResource HelperTextMarginConverter}}"
Width="{Binding ActualWidth, ElementName=OuterBorder}">
Padding="{TemplateBinding Padding, Converter={StaticResource HelperTextMarginConverter}}"
Width="{Binding ActualWidth, ElementName=OuterBorder}">
<Grid x:Name="FooterGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:TextFieldAssist.PrefixTextHintBehavior)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:TextFieldAssist.SuffixTextHintBehavior)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="HorizontalContentAlignment" />
<Binding Path="IsEditable" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</wpf:SmartHint.InitialHorizontalOffset>
<wpf:SmartHint.Margin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:TextFieldAssist.PrefixTextHintBehavior)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:TextFieldAssist.SuffixTextHintBehavior)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="HorizontalContentAlignment" />
<Binding Source="{StaticResource TrueValue}" />
</MultiBinding>
</wpf:SmartHint.InitialHorizontalOffset>
<wpf:SmartHint.Margin>
Expand Down Expand Up @@ -836,6 +837,7 @@
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:TextFieldAssist.PrefixTextHintBehavior)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:TextFieldAssist.SuffixTextHintBehavior)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="HorizontalContentAlignment" />
<Binding Source="{StaticResource TrueValue}" />
</MultiBinding>
</wpf:SmartHint.InitialHorizontalOffset>
<wpf:SmartHint.Margin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,24 +151,6 @@
Opacity="{TemplateBinding wpf:HintAssist.HintOpacity}"
Visibility="{TemplateBinding wpf:TextFieldAssist.HasLeadingIcon, Converter={x:Static converters:BooleanToVisibilityConverter.CollapsedInstance}}" />

<TextBlock x:Name="PrefixTextBlock"
Grid.Column="1"
Margin="0,0,2,0"
VerticalAlignment="Center"
FontSize="{TemplateBinding FontSize}"
Opacity="{TemplateBinding wpf:HintAssist.HintOpacity}"
Text="{TemplateBinding wpf:TextFieldAssist.PrefixText}">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource PrefixSuffixTextVisibilityConverter}">
<Binding ElementName="Hint" Path="IsHintInFloatingPosition" />
<Binding Path="(wpf:TextFieldAssist.PrefixText)" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="(wpf:TextFieldAssist.PrefixTextVisibility)" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsKeyboardFocusWithin" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsReadOnly" RelativeSource="{RelativeSource TemplatedParent}" Converter="{x:Static converters:InvertBooleanConverter.Instance}" />
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>

<ScrollViewer x:Name="PART_ContentHost"
Grid.Column="2"
HorizontalAlignment="Stretch"
Expand Down Expand Up @@ -212,6 +194,7 @@
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:TextFieldAssist.PrefixTextHintBehavior)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="(wpf:TextFieldAssist.SuffixTextHintBehavior)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="HorizontalContentAlignment" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="IsReadOnly" Converter="{x:Static converters:InvertBooleanConverter.Instance}" />
</MultiBinding>
</wpf:SmartHint.InitialHorizontalOffset>
<wpf:SmartHint.Margin>
Expand All @@ -236,6 +219,24 @@
</wpf:SmartHint.Hint>
</wpf:SmartHint>

<TextBlock x:Name="PrefixTextBlock"
Grid.Column="1"
Margin="0,0,2,0"
VerticalAlignment="Center"
FontSize="{TemplateBinding FontSize}"
Opacity="{TemplateBinding wpf:HintAssist.HintOpacity}"
Text="{TemplateBinding wpf:TextFieldAssist.PrefixText}">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource PrefixSuffixTextVisibilityConverter}">
<Binding ElementName="Hint" Path="IsHintInFloatingPosition" />
<Binding Path="(wpf:TextFieldAssist.PrefixText)" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="(wpf:TextFieldAssist.PrefixTextVisibility)" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsKeyboardFocusWithin" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="IsReadOnly" RelativeSource="{RelativeSource TemplatedParent}" Converter="{x:Static converters:InvertBooleanConverter.Instance}" />
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>

<TextBlock x:Name="SuffixTextBlock"
Grid.Column="3"
Margin="2,0,0,0"
Expand Down

0 comments on commit 070e8bb

Please sign in to comment.