Browse Source

Merge pull request #4422 from AvaloniaUI/add-content-presenters-fluent-textbox

Add content presenters fluent textbox
pull/4492/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
e8d6462a0a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  2. 15
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  3. 54
      src/Avalonia.Controls/TextBox.cs
  4. 15
      src/Avalonia.Themes.Default/TextBox.xaml
  5. 250
      src/Avalonia.Themes.Fluent/TextBox.xaml
  6. 34
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

3
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -20,6 +20,7 @@
<TextBox Width="200" <TextBox Width="200"
Watermark="Password Box" Watermark="Password Box"
Classes="revealPasswordButton"
UseFloatingWatermark="True" UseFloatingWatermark="True"
PasswordChar="*" PasswordChar="*"
Text="Password" /> Text="Password" />
@ -37,6 +38,8 @@
Text="Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." /> Text="Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox AcceptsReturn="True" Width="200" Height="125" <TextBox AcceptsReturn="True" Width="200" Height="125"
Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." /> Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox Classes="clearButton" Text="Clear Content" Width="200" FontWeight="Normal" FontStyle="Normal" Watermark="Watermark" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" Spacing="8"> <StackPanel Orientation="Vertical" Spacing="8">
<TextBlock Classes="h2">resm fonts</TextBlock> <TextBlock Classes="h2">resm fonts</TextBlock>

15
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -14,6 +14,9 @@ namespace Avalonia.Controls.Presenters
o => o.CaretIndex, o => o.CaretIndex,
(o, v) => o.CaretIndex = v); (o, v) => o.CaretIndex = v);
public static readonly StyledProperty<bool> RevealPasswordProperty =
AvaloniaProperty.Register<TextPresenter, bool>(nameof(RevealPassword));
public static readonly StyledProperty<char> PasswordCharProperty = public static readonly StyledProperty<char> PasswordCharProperty =
AvaloniaProperty.Register<TextPresenter, char>(nameof(PasswordChar)); AvaloniaProperty.Register<TextPresenter, char>(nameof(PasswordChar));
@ -75,7 +78,7 @@ namespace Avalonia.Controls.Presenters
static TextPresenter() static TextPresenter()
{ {
AffectsRender<TextPresenter>(SelectionBrushProperty); AffectsRender<TextPresenter>(SelectionBrushProperty);
AffectsMeasure<TextPresenter>(TextProperty, PasswordCharProperty, AffectsMeasure<TextPresenter>(TextProperty, PasswordCharProperty, RevealPasswordProperty,
TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty, TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty,
TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty); TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty);
@ -84,7 +87,7 @@ namespace Avalonia.Controls.Presenters
TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed, TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed,
TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed, TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed,
SelectionStartProperty.Changed, SelectionEndProperty.Changed, SelectionStartProperty.Changed, SelectionEndProperty.Changed,
SelectionForegroundBrushProperty.Changed, PasswordCharProperty.Changed SelectionForegroundBrushProperty.Changed, PasswordCharProperty.Changed, RevealPasswordProperty.Changed
).AddClassHandler<TextPresenter>((x, _) => x.InvalidateFormattedText()); ).AddClassHandler<TextPresenter>((x, _) => x.InvalidateFormattedText());
CaretIndexProperty.Changed.AddClassHandler<TextPresenter>((x, e) => x.CaretIndexChanged((int)e.NewValue)); CaretIndexProperty.Changed.AddClassHandler<TextPresenter>((x, e) => x.CaretIndexChanged((int)e.NewValue));
@ -210,6 +213,12 @@ namespace Avalonia.Controls.Presenters
set => SetValue(PasswordCharProperty, value); set => SetValue(PasswordCharProperty, value);
} }
public bool RevealPassword
{
get => GetValue(RevealPasswordProperty);
set => SetValue(RevealPasswordProperty, value);
}
public IBrush SelectionBrush public IBrush SelectionBrush
{ {
get => GetValue(SelectionBrushProperty); get => GetValue(SelectionBrushProperty);
@ -426,7 +435,7 @@ namespace Avalonia.Controls.Presenters
var text = Text; var text = Text;
if (PasswordChar != default(char)) if (PasswordChar != default(char) && !RevealPassword)
{ {
result = CreateFormattedTextInternal(_constraint, new string(PasswordChar, text?.Length ?? 0)); result = CreateFormattedTextInternal(_constraint, new string(PasswordChar, text?.Length ?? 0));
} }

54
src/Avalonia.Controls/TextBox.cs

@ -95,6 +95,15 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<TextBox, string>(nameof(NewLine), AvaloniaProperty.RegisterDirect<TextBox, string>(nameof(NewLine),
textbox => textbox.NewLine, (textbox, newline) => textbox.NewLine = newline); textbox => textbox.NewLine, (textbox, newline) => textbox.NewLine = newline);
public static readonly StyledProperty<object> InnerLeftContentProperty =
AvaloniaProperty.Register<TextBox, object>(nameof(InnerLeftContent));
public static readonly StyledProperty<object> InnerRightContentProperty =
AvaloniaProperty.Register<TextBox, object>(nameof(InnerRightContent));
public static readonly StyledProperty<bool> RevealPasswordProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword));
struct UndoRedoState : IEquatable<UndoRedoState> struct UndoRedoState : IEquatable<UndoRedoState>
{ {
public string Text { get; } public string Text { get; }
@ -148,6 +157,8 @@ namespace Avalonia.Controls
horizontalScrollBarVisibility, horizontalScrollBarVisibility,
BindingPriority.Style); BindingPriority.Style);
_undoRedoHelper = new UndoRedoHelper<UndoRedoState>(this); _undoRedoHelper = new UndoRedoHelper<UndoRedoState>(this);
UpdatePseudoclasses();
} }
public bool AcceptsReturn public bool AcceptsReturn
@ -285,7 +296,7 @@ namespace Avalonia.Controls
else else
{ {
HandleTextInput(value); HandleTextInput(value);
} }
_undoRedoHelper.Snapshot(); _undoRedoHelper.Snapshot();
} }
} }
@ -326,6 +337,24 @@ namespace Avalonia.Controls
set { SetValue(UseFloatingWatermarkProperty, value); } set { SetValue(UseFloatingWatermarkProperty, value); }
} }
public object InnerLeftContent
{
get { return GetValue(InnerLeftContentProperty); }
set { SetValue(InnerLeftContentProperty, value); }
}
public object InnerRightContent
{
get { return GetValue(InnerRightContentProperty); }
set { SetValue(InnerRightContentProperty, value); }
}
public bool RevealPassword
{
get { return GetValue(RevealPasswordProperty); }
set { SetValue(RevealPasswordProperty, value); }
}
public TextWrapping TextWrapping public TextWrapping TextWrapping
{ {
get { return GetValue(TextWrappingProperty); } get { return GetValue(TextWrappingProperty); }
@ -351,6 +380,16 @@ namespace Avalonia.Controls
} }
} }
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == TextProperty)
{
UpdatePseudoclasses();
}
}
protected override void OnGotFocus(GotFocusEventArgs e) protected override void OnGotFocus(GotFocusEventArgs e)
{ {
base.OnGotFocus(e); base.OnGotFocus(e);
@ -374,6 +413,7 @@ namespace Avalonia.Controls
SelectionStart = 0; SelectionStart = 0;
SelectionEnd = 0; SelectionEnd = 0;
_presenter?.HideCaret(); _presenter?.HideCaret();
RevealPassword = false;
} }
protected override void OnTextInput(TextInputEventArgs e) protected override void OnTextInput(TextInputEventArgs e)
@ -756,7 +796,7 @@ namespace Avalonia.Controls
// if it did not, we change the selection to where the user clicked // if it did not, we change the selection to where the user clicked
var firstSelection = Math.Min(SelectionStart, SelectionEnd); var firstSelection = Math.Min(SelectionStart, SelectionEnd);
var lastSelection = Math.Max(SelectionStart, SelectionEnd); var lastSelection = Math.Max(SelectionStart, SelectionEnd);
var didClickInSelection = SelectionStart != SelectionEnd && var didClickInSelection = SelectionStart != SelectionEnd &&
caretIndex >= firstSelection && caretIndex <= lastSelection; caretIndex >= firstSelection && caretIndex <= lastSelection;
if (!didClickInSelection) if (!didClickInSelection)
{ {
@ -803,6 +843,11 @@ namespace Avalonia.Controls
} }
} }
public void Clear()
{
Text = string.Empty;
}
private int DeleteCharacter(int index) private int DeleteCharacter(int index)
{ {
var start = index + 1; var start = index + 1;
@ -1068,6 +1113,11 @@ namespace Avalonia.Controls
SelectionEnd = CaretIndex; SelectionEnd = CaretIndex;
} }
private void UpdatePseudoclasses()
{
PseudoClasses.Set(":empty", string.IsNullOrWhiteSpace(Text));
}
private bool IsPasswordBox => PasswordChar != default(char); private bool IsPasswordBox => PasswordChar != default(char);
UndoRedoState UndoRedoHelper<UndoRedoState>.IUndoRedoHost.UndoRedoState UndoRedoState UndoRedoHelper<UndoRedoState>.IUndoRedoHost.UndoRedoState

15
src/Avalonia.Themes.Default/TextBox.xaml

@ -35,14 +35,16 @@
<DataValidationErrors> <DataValidationErrors>
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"> VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<Panel> <Grid ColumnDefinitions="Auto,*,Auto">
<ContentPresenter Grid.Column="0" Grid.ColumnSpan="1" Content="{TemplateBinding InnerLeftContent}"/>
<TextBlock Name="watermark" <TextBlock Name="watermark"
Opacity="0.5" Opacity="0.5"
Text="{TemplateBinding Watermark}" Text="{TemplateBinding Watermark}"
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" TextWrapping="{TemplateBinding TextWrapping}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"/> IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
Grid.Column="1" Grid.ColumnSpan="1"/>
<TextPresenter Name="PART_TextPresenter" <TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}" Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}" CaretIndex="{TemplateBinding CaretIndex}"
@ -51,10 +53,13 @@
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" TextWrapping="{TemplateBinding TextWrapping}"
PasswordChar="{TemplateBinding PasswordChar}" PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}" SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretBrush="{TemplateBinding CaretBrush}"/> CaretBrush="{TemplateBinding CaretBrush}"
</Panel> Grid.Column="1" Grid.ColumnSpan="1"/>
<ContentPresenter Grid.Column="2" Grid.ColumnSpan="1" Content="{TemplateBinding InnerRightContent}"/>
</Grid>
</ScrollViewer> </ScrollViewer>
</DataValidationErrors> </DataValidationErrors>
</DockPanel> </DockPanel>

250
src/Avalonia.Themes.Fluent/TextBox.xaml

@ -4,13 +4,18 @@
<TextBox Text="Sample" <TextBox Text="Sample"
FontSize="20" FontSize="20"
Width="100" Width="100"
Classes="clearButton"
TextAlignment="Center"/> TextAlignment="Center"/>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Styles.Resources> <Styles.Resources>
<Thickness x:Key="TextBoxTopHeaderMargin">0,0,0,4</Thickness> <Thickness x:Key="TextBoxTopHeaderMargin">0,0,0,4</Thickness>
<StreamGeometry x:Key="TextBoxClearButtonData">M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry>
</Styles.Resources> </Styles.Resources>
<Style Selector="TextBox"> <Style Selector="TextBox">
<Setter Property="Foreground" Value="{DynamicResource TextControlForeground}" /> <Setter Property="Foreground" Value="{DynamicResource TextControlForeground}" />
<Setter Property="Background" Value="{DynamicResource TextControlBackground}" /> <Setter Property="Background" Value="{DynamicResource TextControlBackground}" />
@ -22,12 +27,13 @@
<Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" /> <Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" /> <Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" /> <Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
<Setter Property="FocusAdorner" Value="{x:Null}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<DockPanel> <DockPanel>
<!-- TODO bind Content -> Header and ContentTemplate -> HeaderTemplate --> <!-- TODO bind Content -> Header and ContentTemplate -> HeaderTemplate -->
<ContentPresenter x:Name="HeaderContentPresenter" <ContentPresenter x:Name="PART_HeaderContentPresenter"
DockPanel.Dock="Top" DockPanel.Dock="Top"
TextBlock.FontWeight="Normal" TextBlock.FontWeight="Normal"
TextBlock.Foreground="{DynamicResource TextControlHeaderForeground}" TextBlock.Foreground="{DynamicResource TextControlHeaderForeground}"
@ -36,7 +42,7 @@
<Panel> <Panel>
<Border <Border
Name="border" Name="PART_BorderElement"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
@ -46,57 +52,50 @@
</Border> </Border>
<Border <Border
Padding="{TemplateBinding Padding}"
Margin="{TemplateBinding BorderThickness}"> Margin="{TemplateBinding BorderThickness}">
<DockPanel> <Grid ColumnDefinitions="Auto,*,Auto" >
<TextBlock Name="floatingWatermark" <ContentPresenter Grid.Column="0" Grid.ColumnSpan="1" Content="{TemplateBinding InnerLeftContent}"/>
Foreground="{DynamicResource SystemAccentColor}" <DockPanel x:Name="PART_InnerDockPanel" Grid.Column="1" Grid.ColumnSpan="1" Margin="{TemplateBinding Padding}">
FontSize="{TemplateBinding FontSize}" <TextBlock Name="PART_FloatingWatermark"
Text="{TemplateBinding Watermark}" Foreground="{DynamicResource SystemAccentColor}"
DockPanel.Dock="Top"> FontSize="{TemplateBinding FontSize}"
<TextBlock.IsVisible> Text="{TemplateBinding Watermark}"
<MultiBinding Converter="{x:Static BoolConverters.And}"> DockPanel.Dock="Top" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="UseFloatingWatermark"/> <DataValidationErrors>
<Binding RelativeSource="{RelativeSource TemplatedParent}" <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
Path="Text" VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
Converter="{x:Static StringConverters.IsNotNullOrEmpty}"/> <Panel>
</MultiBinding> <TextBlock Name="PART_Watermark"
</TextBlock.IsVisible> Opacity="0.5"
</TextBlock> Text="{TemplateBinding Watermark}"
TextAlignment="{TemplateBinding TextAlignment}"
<DataValidationErrors> TextWrapping="{TemplateBinding TextWrapping}"
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"> HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<Panel> <!-- TODO eliminate this margin... text layout issue? -->
<TextBlock Name="watermark" <TextPresenter Name="PART_TextPresenter"
Opacity="0.5" Margin="0 1 0 0"
Text="{TemplateBinding Watermark}" Text="{TemplateBinding Text, Mode=TwoWay}"
TextAlignment="{TemplateBinding TextAlignment}" CaretIndex="{TemplateBinding CaretIndex}"
TextWrapping="{TemplateBinding TextWrapping}" SelectionStart="{TemplateBinding SelectionStart}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}" SelectionEnd="{TemplateBinding SelectionEnd}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> TextWrapping="{TemplateBinding TextWrapping}"
<!-- TODO eliminate this margin... text layout issue? --> PasswordChar="{TemplateBinding PasswordChar}"
<TextPresenter Name="PART_TextPresenter" RevealPassword="{TemplateBinding RevealPassword}"
Margin="0 1 0 0" SelectionBrush="{TemplateBinding SelectionBrush}"
Text="{TemplateBinding Text, Mode=TwoWay}" SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretIndex="{TemplateBinding CaretIndex}" CaretBrush="{TemplateBinding CaretBrush}"
SelectionStart="{TemplateBinding SelectionStart}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SelectionEnd="{TemplateBinding SelectionEnd}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
TextAlignment="{TemplateBinding TextAlignment}" </Panel>
TextWrapping="{TemplateBinding TextWrapping}" </ScrollViewer>
PasswordChar="{TemplateBinding PasswordChar}" </DataValidationErrors>
SelectionBrush="{TemplateBinding SelectionBrush}" </DockPanel>
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" <ContentPresenter Grid.Column="2" Grid.ColumnSpan="1" Content="{TemplateBinding InnerRightContent}"/>
CaretBrush="{TemplateBinding CaretBrush}" </Grid>
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Panel>
</ScrollViewer>
</DataValidationErrors>
</DockPanel>
</Border> </Border>
</Panel> </Panel>
</DockPanel> </DockPanel>
@ -109,12 +108,12 @@
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}" /> <Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}" />
</Style> </Style>
<Style Selector="TextBox:disabled /template/ Border#border"> <Style Selector="TextBox:disabled /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{DynamicResource TextControlBackgroundDisabled}" /> <Setter Property="Background" Value="{DynamicResource TextControlBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrushDisabled}" /> <Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrushDisabled}" />
</Style> </Style>
<Style Selector="TextBox:disabled /template/ TextBlock#watermark, TextBox:disabled /template/ TextBlock#floatingWatermark"> <Style Selector="TextBox:disabled /template/ TextBlock#PART_Watermark, TextBox:disabled /template/ TextBlock#PART_FloatingWatermark">
<Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForegroundDisabled}" /> <Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForegroundDisabled}" />
</Style> </Style>
@ -123,12 +122,12 @@
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundPointerOver}" /> <Setter Property="Foreground" Value="{DynamicResource TextControlForegroundPointerOver}" />
</Style> </Style>
<Style Selector="TextBox:pointerover /template/ Border#border"> <Style Selector="TextBox:pointerover /template/ Border#PART_BorderElement">
<Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrushPointerOver}"/> <Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrushPointerOver}"/>
<Setter Property="Background" Value="{DynamicResource TextControlBackgroundPointerOver}" /> <Setter Property="Background" Value="{DynamicResource TextControlBackgroundPointerOver}" />
</Style> </Style>
<Style Selector="TextBox:pointerover /template/ TextBlock#watermark, TextBox:pointerover /template/ TextBlock#floatingWatermark"> <Style Selector="TextBox:pointerover /template/ TextBlock#PART_Watermark, TextBox:pointerover /template/ TextBlock#PART_FloatingWatermark">
<Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForegroundPointerOver}" /> <Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForegroundPointerOver}" />
</Style> </Style>
@ -137,23 +136,152 @@
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundFocused}" /> <Setter Property="Foreground" Value="{DynamicResource TextControlForegroundFocused}" />
</Style> </Style>
<Style Selector="TextBox:focus /template/ TextBlock#watermark, TextBox:focus /template/ TextBlock#floatingWatermark"> <Style Selector="TextBox:focus /template/ TextBlock#PART_Watermark, TextBox:focus /template/ TextBlock#PART_FloatingWatermark">
<Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForegroundFocused}" /> <Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForegroundFocused}" />
</Style> </Style>
<Style Selector="TextBox:focus /template/ Border#border"> <Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{DynamicResource TextControlBackgroundFocused}"/> <Setter Property="Background" Value="{DynamicResource TextControlBackgroundFocused}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrushFocused}"/> <Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrushFocused}"/>
<Setter Property="BorderThickness" Value="{DynamicResource TextControlBorderThemeThicknessFocused}" /> <Setter Property="BorderThickness" Value="{DynamicResource TextControlBorderThemeThicknessFocused}" />
</Style> </Style>
<Style Selector="TextBox:error /template/ Border#PART_BorderElement">
<Style Selector="TextBox:error /template/ Border#border">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlErrorTextForegroundBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource SystemControlErrorTextForegroundBrush}"/>
</Style> </Style>
<Style Selector="TextBox /template/ DockPanel"> <Style Selector="TextBox /template/ DockPanel#PART_InnerDockPanel">
<Setter Property="Cursor" Value="IBeam" /> <Setter Property="Cursor" Value="IBeam" />
</Style> </Style>
<Style Selector="TextBox /template/ TextBlock#PART_FloatingWatermark">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="TextBox[UseFloatingWatermark=true]:not(TextBox:empty) /template/ TextBlock#PART_FloatingWatermark">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="TextBox.revealPasswordButton[AcceptsReturn=False][IsReadOnly=False]:not(TextBox:empty)">
<Setter Property="InnerRightContent">
<Template>
<ToggleButton Classes="PasswordBoxRevealButton"
IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}" />
</Template>
</Setter>
</Style>
<Style Selector="TextBox.clearButton[AcceptsReturn=False][IsReadOnly=False]:focus:not(TextBox:empty)">
<Setter Property="InnerRightContent">
<Template>
<Button Classes="TextBoxClearButton"
Command="{Binding $parent[TextBox].Clear}" />
</Template>
</Setter>
</Style>
<Style Selector="ToggleButton.PasswordBoxRevealButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border x:Name="PART_ButtonLayoutBorder"
BorderThickness="{TemplateBinding BorderThickness}">
<Panel>
<Path x:Name="PART_GlyphElement_Reveal"
Data="{StaticResource PasswordBoxRevealButtonData}"
Height="8"
Width="12"
Stretch="Uniform"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
<Path x:Name="PART_GlyphElement_Hide"
Data="{StaticResource PasswordBoxHideButtonData}"
Height="12"
Width="12"
Stretch="Uniform"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Panel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style Selector="Button.TextBoxClearButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="PART_ButtonLayoutBorder"
BorderThickness="{TemplateBinding BorderThickness}">
<Path x:Name="PART_GlyphElement"
Data="{StaticResource TextBoxClearButtonData}"
Height="10"
Width="10"
Stretch="Uniform"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- TextBox.Button Normal State -->
<Style Selector="Button.TextBoxClearButton, ToggleButton.PasswordBoxRevealButton">
<Setter Property="MinWidth" Value="34" />
<Setter Property="Width" Value="{Binding $self.Bounds.Height}"/>
<Setter Property="Focusable" Value="False" />
<Setter Property="VerticalAlignment" Value="Stretch" />
</Style>
<Style Selector="Button.TextBoxClearButton /template/ Border#PART_ButtonLayoutBorder,
ToggleButton.PasswordBoxRevealButton /template/ Border#PART_ButtonLayoutBorder">
<Setter Property="Background" Value="{DynamicResource TextControlButtonBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource TextControlButtonBorderBrush}" />
</Style>
<Style Selector="Button.TextBoxClearButton /template/ Path#PART_GlyphElement,
ToggleButton.PasswordBoxRevealButton /template/ Path#PART_GlyphElement_Reveal,
ToggleButton.PasswordBoxRevealButton /template/ Path#PART_GlyphElement_Hide">
<Setter Property="Fill" Value="{DynamicResource TextControlButtonForeground}" />
</Style>
<!-- TextBox.Button PointerOver State -->
<Style Selector="Button.TextBoxClearButton:pointerover /template/ Border#PART_ButtonLayoutBorder,
ToggleButton.PasswordBoxRevealButton:pointerover /template/ Border#PART_ButtonLayoutBorder">
<Setter Property="Background" Value="{DynamicResource TextControlButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource TextControlButtonBorderBrushPointerOver}" />
</Style>
<Style Selector="Button.TextBoxClearButton:pointerover /template/ Path#PART_GlyphElement,
ToggleButton.PasswordBoxRevealButton:pointerover /template/ Path#PART_GlyphElement_Reveal,
ToggleButton.PasswordBoxRevealButton:pointerover /template/ Path#PART_GlyphElement_Hide">
<Setter Property="Fill" Value="{DynamicResource TextControlButtonForegroundPointerOver}" />
</Style>
<!-- TextBox.Button Pressed State -->
<Style Selector="Button.TextBoxClearButton:pressed /template/ Border#PART_ButtonLayoutBorder,
ToggleButton.PasswordBoxRevealButton:pressed /template/ Border#PART_ButtonLayoutBorder,
ToggleButton.PasswordBoxRevealButton:checked /template/ Border#PART_ButtonLayoutBorder,
ToggleButton.PasswordBoxRevealButton:indeterminate /template/ Border#PART_ButtonLayoutBorder">
<Setter Property="Background" Value="{DynamicResource TextControlButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource TextControlButtonBorderBrushPressed}" />
</Style>
<Style Selector="Button.TextBoxClearButton:pressed /template/ Path#PART_GlyphElement,
ToggleButton.PasswordBoxRevealButton:pressed /template/ Path#PART_GlyphElement_Reveal,
ToggleButton.PasswordBoxRevealButton:checked /template/ Path#PART_GlyphElement_Hide,
ToggleButton.PasswordBoxRevealButton:indeterminate /template/ Path#PART_GlyphElement_Reveal">
<Setter Property="Fill" Value="{DynamicResource TextControlButtonForegroundPressed}" />
</Style>
<!-- TextBox.Button Disabled State -->
<Style Selector="Button.TextBoxClearButton:disabled /template/ Border#PART_ButtonLayoutBorder,
ToggleButton.PasswordBoxRevealButton:disabled /template/ Border#PART_ButtonLayoutBorder">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="ToggleButton.PasswordBoxRevealButton:not(ToggleButton:checked) /template/ Path#PART_GlyphElement_Hide">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="ToggleButton.PasswordBoxRevealButton:checked /template/ Path#PART_GlyphElement_Reveal">
<Setter Property="IsVisible" Value="False" />
</Style>
</Styles> </Styles>

34
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -521,6 +521,40 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, lfcount); Assert.Equal(1, lfcount);
} }
} }
[Fact]
public void TextBox_Reveal_Password_Reset_When_Lost_Focus()
{
using (UnitTestApplication.Start(FocusServices))
{
var target1 = new TextBox
{
Template = CreateTemplate(),
Text = "1234",
PasswordChar = '*'
};
var target2 = new TextBox
{
Template = CreateTemplate(),
Text = "5678"
};
var sp = new StackPanel();
sp.Children.Add(target1);
sp.Children.Add(target2);
target1.ApplyTemplate();
target2.ApplyTemplate();
var root = new TestRoot { Child = sp };
target1.Focus();
target1.RevealPassword = true;
target2.Focus();
Assert.False(target1.RevealPassword);
}
}
[Fact] [Fact]
public void Setting_Bound_Text_To_Null_Works() public void Setting_Bound_Text_To_Null_Works()

Loading…
Cancel
Save