|
|
|
@ -17,7 +17,6 @@ using Avalonia.Controls.Metadata; |
|
|
|
using Avalonia.Media.TextFormatting; |
|
|
|
using Avalonia.Media.TextFormatting.Unicode; |
|
|
|
using Avalonia.Automation.Peers; |
|
|
|
using System.Diagnostics; |
|
|
|
using Avalonia.Threading; |
|
|
|
|
|
|
|
namespace Avalonia.Controls |
|
|
|
@ -29,60 +28,108 @@ namespace Avalonia.Controls |
|
|
|
[PseudoClasses(":empty")] |
|
|
|
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// Gets a platform-specific <see cref="KeyGesture"/> for the Cut action
|
|
|
|
/// </summary>
|
|
|
|
public static KeyGesture? CutGesture { get; } = AvaloniaLocator.Current |
|
|
|
.GetService<PlatformHotkeyConfiguration>()?.Cut.FirstOrDefault(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets a platform-specific <see cref="KeyGesture"/> for the Copy action
|
|
|
|
/// </summary>
|
|
|
|
public static KeyGesture? CopyGesture { get; } = AvaloniaLocator.Current |
|
|
|
.GetService<PlatformHotkeyConfiguration>()?.Copy.FirstOrDefault(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets a platform-specific <see cref="KeyGesture"/> for the Paste action
|
|
|
|
/// </summary>
|
|
|
|
public static KeyGesture? PasteGesture { get; } = AvaloniaLocator.Current |
|
|
|
.GetService<PlatformHotkeyConfiguration>()?.Paste.FirstOrDefault(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="AcceptsReturn"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<bool> AcceptsReturnProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="AcceptsTab"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<bool> AcceptsTabProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsTab)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="CaretIndex"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, int> CaretIndexProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, int>( |
|
|
|
nameof(CaretIndex), |
|
|
|
o => o.CaretIndex, |
|
|
|
(o, v) => o.CaretIndex = v); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="IsReadOnly"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<bool> IsReadOnlyProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>(nameof(IsReadOnly)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="PasswordChar"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<char> PasswordCharProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="SelectionBrush"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<IBrush?> SelectionBrushProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionBrush)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="SelectionForegroundBrush"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<IBrush?> SelectionForegroundBrushProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, IBrush?>(nameof(SelectionForegroundBrush)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="CaretBrush"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<IBrush?> CaretBrushProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, IBrush?>(nameof(CaretBrush)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="SelectionStart"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, int> SelectionStartProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, int>( |
|
|
|
nameof(SelectionStart), |
|
|
|
o => o.SelectionStart, |
|
|
|
(o, v) => o.SelectionStart = v); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="SelectionEnd"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, int> SelectionEndProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, int>( |
|
|
|
nameof(SelectionEnd), |
|
|
|
o => o.SelectionEnd, |
|
|
|
(o, v) => o.SelectionEnd = v); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="MaxLength"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<int> MaxLengthProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="MaxLines"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<int> MaxLinesProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="Text"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, string?> TextProperty = |
|
|
|
TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>( |
|
|
|
o => o.Text, |
|
|
|
@ -90,6 +137,9 @@ namespace Avalonia.Controls |
|
|
|
defaultBindingMode: BindingMode.TwoWay, |
|
|
|
enableDataValidation: true); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="TextAlignment"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<TextAlignment> TextAlignmentProperty = |
|
|
|
TextBlock.TextAlignmentProperty.AddOwner<TextBox>(); |
|
|
|
|
|
|
|
@ -120,45 +170,78 @@ namespace Avalonia.Controls |
|
|
|
public static readonly StyledProperty<double> LetterSpacingProperty = |
|
|
|
TextBlock.LetterSpacingProperty.AddOwner<TextBox>(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="Watermark"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<string?> WatermarkProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, string?>(nameof(Watermark)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="UseFloatingWatermark"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<bool> UseFloatingWatermarkProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>(nameof(UseFloatingWatermark)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="NewLine"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, string> NewLineProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, string>(nameof(NewLine), |
|
|
|
textbox => textbox.NewLine, (textbox, newline) => textbox.NewLine = newline); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="InnerLeftContent"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<object> InnerLeftContentProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, object>(nameof(InnerLeftContent)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="InnerRightContent"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<object> InnerRightContentProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, object>(nameof(InnerRightContent)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="RevealPassword"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<bool> RevealPasswordProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="CanCut"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, bool> CanCutProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, bool>( |
|
|
|
nameof(CanCut), |
|
|
|
o => o.CanCut); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="CanCopy"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, bool> CanCopyProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, bool>( |
|
|
|
nameof(CanCopy), |
|
|
|
o => o.CanCopy); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="CanPaste"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, bool> CanPasteProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, bool>( |
|
|
|
nameof(CanPaste), |
|
|
|
o => o.CanPaste); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="IsUndoEnabled"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<bool> IsUndoEnabledProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>( |
|
|
|
nameof(IsUndoEnabled), |
|
|
|
defaultValue: true); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="UndoLimit"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, int> UndoLimitProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, int>( |
|
|
|
nameof(UndoLimit), |
|
|
|
@ -166,6 +249,18 @@ namespace Avalonia.Controls |
|
|
|
(o, v) => o.UndoLimit = v, |
|
|
|
unsetValue: -1); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="CanUndo"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, bool> CanUndoProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, bool>(nameof(CanUndo), x => x.CanUndo); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="CanRedo"/> property
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<TextBox, bool> CanRedoProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, bool>(nameof(CanRedo), x => x.CanRedo); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="CopyingToClipboard"/> event.
|
|
|
|
/// </summary>
|
|
|
|
@ -201,9 +296,13 @@ namespace Avalonia.Controls |
|
|
|
RoutedEvent.Register<TextBox, TextChangingEventArgs>( |
|
|
|
nameof(TextChanging), RoutingStrategies.Bubble); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Stores the state information for available actions in the UndoRedoHelper
|
|
|
|
/// </summary>
|
|
|
|
readonly struct UndoRedoState : IEquatable<UndoRedoState> |
|
|
|
{ |
|
|
|
public string? Text { get; } |
|
|
|
|
|
|
|
public int CaretPosition { get; } |
|
|
|
|
|
|
|
public UndoRedoState(string? text, int caretPosition) |
|
|
|
@ -232,6 +331,8 @@ namespace Avalonia.Controls |
|
|
|
private bool _canPaste; |
|
|
|
private string _newLine = Environment.NewLine; |
|
|
|
private static readonly string[] invalidCharacters = new String[1] { "\u007f" }; |
|
|
|
private bool _canUndo; |
|
|
|
private bool _canRedo; |
|
|
|
|
|
|
|
private int _wordSelectionStart = -1; |
|
|
|
private int _selectedTextChangesMadeSinceLastUndoSnapshot; |
|
|
|
@ -268,24 +369,34 @@ namespace Avalonia.Controls |
|
|
|
ScrollViewer.HorizontalScrollBarVisibilityProperty, |
|
|
|
horizontalScrollBarVisibility, |
|
|
|
BindingPriority.Style); |
|
|
|
|
|
|
|
_undoRedoHelper = new UndoRedoHelper<UndoRedoState>(this); |
|
|
|
_selectedTextChangesMadeSinceLastUndoSnapshot = 0; |
|
|
|
_hasDoneSnapshotOnce = false; |
|
|
|
UpdatePseudoclasses(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a value that determines whether the TextBox allows and displays newline or return characters
|
|
|
|
/// </summary>
|
|
|
|
public bool AcceptsReturn |
|
|
|
{ |
|
|
|
get => GetValue(AcceptsReturnProperty); |
|
|
|
set => SetValue(AcceptsReturnProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a value that determins whether the TextBox allows and displays tabs
|
|
|
|
/// </summary>
|
|
|
|
public bool AcceptsTab |
|
|
|
{ |
|
|
|
get => GetValue(AcceptsTabProperty); |
|
|
|
set => SetValue(AcceptsTabProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the index of the text caret
|
|
|
|
/// </summary>
|
|
|
|
public int CaretIndex |
|
|
|
{ |
|
|
|
get => _caretIndex; |
|
|
|
@ -302,36 +413,54 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a value whether this TextBox is read-only
|
|
|
|
/// </summary>
|
|
|
|
public bool IsReadOnly |
|
|
|
{ |
|
|
|
get => GetValue(IsReadOnlyProperty); |
|
|
|
set => SetValue(IsReadOnlyProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the <see cref="char"/> that should be used for password masking
|
|
|
|
/// </summary>
|
|
|
|
public char PasswordChar |
|
|
|
{ |
|
|
|
get => GetValue(PasswordCharProperty); |
|
|
|
set => SetValue(PasswordCharProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a brush that is used to highlight selected text
|
|
|
|
/// </summary>
|
|
|
|
public IBrush? SelectionBrush |
|
|
|
{ |
|
|
|
get => GetValue(SelectionBrushProperty); |
|
|
|
set => SetValue(SelectionBrushProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a brush that is used for the foreground of selected text
|
|
|
|
/// </summary>
|
|
|
|
public IBrush? SelectionForegroundBrush |
|
|
|
{ |
|
|
|
get => GetValue(SelectionForegroundBrushProperty); |
|
|
|
set => SetValue(SelectionForegroundBrushProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a brush that is used for the text caret
|
|
|
|
/// </summary>
|
|
|
|
public IBrush? CaretBrush |
|
|
|
{ |
|
|
|
get => GetValue(CaretBrushProperty); |
|
|
|
set => SetValue(CaretBrushProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the starting position of the text selected in the TextBox
|
|
|
|
/// </summary>
|
|
|
|
public int SelectionStart |
|
|
|
{ |
|
|
|
get => _selectionStart; |
|
|
|
@ -352,6 +481,13 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the end position of the text selected in the TextBox
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// When the SelectionEnd is equal to <see cref="SelectionStart"/>, there is no
|
|
|
|
/// selected text and it marks the caret position
|
|
|
|
/// </remarks>
|
|
|
|
public int SelectionEnd |
|
|
|
{ |
|
|
|
get => _selectionEnd; |
|
|
|
@ -371,19 +507,28 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the maximum character length of the TextBox
|
|
|
|
/// </summary>
|
|
|
|
public int MaxLength |
|
|
|
{ |
|
|
|
get => GetValue(MaxLengthProperty); |
|
|
|
set => SetValue(MaxLengthProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the maximum number of lines the TextBox can contain
|
|
|
|
/// </summary>
|
|
|
|
public int MaxLines |
|
|
|
{ |
|
|
|
get => GetValue(MaxLinesProperty); |
|
|
|
set => SetValue(MaxLinesProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the spacing between characters
|
|
|
|
/// </summary>
|
|
|
|
public double LetterSpacing |
|
|
|
{ |
|
|
|
get => GetValue(LetterSpacingProperty); |
|
|
|
@ -399,6 +544,9 @@ namespace Avalonia.Controls |
|
|
|
set => SetValue(LineHeightProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the Text content of the TextBox
|
|
|
|
/// </summary>
|
|
|
|
[Content] |
|
|
|
public string? Text |
|
|
|
{ |
|
|
|
@ -413,14 +561,20 @@ namespace Avalonia.Controls |
|
|
|
SelectionStart = CoerceCaretIndex(selectionStart, value); |
|
|
|
SelectionEnd = CoerceCaretIndex(selectionEnd, value); |
|
|
|
|
|
|
|
var textChanged = SetAndRaise(TextProperty, ref _text, value); |
|
|
|
|
|
|
|
if (textChanged && IsUndoEnabled && !_isUndoingRedoing) |
|
|
|
// Before #9490, snapshot here was done AFTER text change - this doesn't make sense
|
|
|
|
// since intial state would never be no text and you'd always have to make a text
|
|
|
|
// change before undo would be available
|
|
|
|
// The undo/redo stacks were also cleared at this point, which also doesn't make sense
|
|
|
|
// as it is still valid to want to undo a programmatic text set
|
|
|
|
// So we snapshot text now BEFORE the change so we can always revert
|
|
|
|
// Also don't need to check IsUndoEnabled here, that's done in SnapshotUndoRedo
|
|
|
|
if (!_isUndoingRedoing) |
|
|
|
{ |
|
|
|
_undoRedoHelper.Clear(); |
|
|
|
SnapshotUndoRedo(); // so we always have an initial state
|
|
|
|
SnapshotUndoRedo(); |
|
|
|
} |
|
|
|
|
|
|
|
var textChanged = SetAndRaise(TextProperty, ref _text, value); |
|
|
|
|
|
|
|
if (textChanged) |
|
|
|
{ |
|
|
|
RaiseTextChangeEvents(); |
|
|
|
@ -428,6 +582,9 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the text selected in the TextBox
|
|
|
|
/// </summary>
|
|
|
|
public string SelectedText |
|
|
|
{ |
|
|
|
get => GetSelection(); |
|
|
|
@ -464,6 +621,9 @@ namespace Avalonia.Controls |
|
|
|
set => SetValue(VerticalContentAlignmentProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the <see cref="Media.TextAlignment"/> of the TextBox
|
|
|
|
/// </summary>
|
|
|
|
public TextAlignment TextAlignment |
|
|
|
{ |
|
|
|
get => GetValue(TextAlignmentProperty); |
|
|
|
@ -490,24 +650,36 @@ namespace Avalonia.Controls |
|
|
|
set => SetValue(UseFloatingWatermarkProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets custom content that is positioned on the left side of the text layout box
|
|
|
|
/// </summary>
|
|
|
|
public object InnerLeftContent |
|
|
|
{ |
|
|
|
get => GetValue(InnerLeftContentProperty); |
|
|
|
set => SetValue(InnerLeftContentProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets custom content that is positioned on the right side of the text layout box
|
|
|
|
/// </summary>
|
|
|
|
public object InnerRightContent |
|
|
|
{ |
|
|
|
get => GetValue(InnerRightContentProperty); |
|
|
|
set => SetValue(InnerRightContentProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets whether text masked by <see cref="PasswordChar"/> should be revealed
|
|
|
|
/// </summary>
|
|
|
|
public bool RevealPassword |
|
|
|
{ |
|
|
|
get => GetValue(RevealPasswordProperty); |
|
|
|
set => SetValue(RevealPasswordProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the <see cref="Media.TextWrapping"/> of the TextBox
|
|
|
|
/// </summary>
|
|
|
|
public TextWrapping TextWrapping |
|
|
|
{ |
|
|
|
get => GetValue(TextWrappingProperty); |
|
|
|
@ -567,6 +739,9 @@ namespace Avalonia.Controls |
|
|
|
set => SetValue(IsUndoEnabledProperty, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the maximum number of items that can reside in the Undo stack
|
|
|
|
/// </summary>
|
|
|
|
public int UndoLimit |
|
|
|
{ |
|
|
|
get => _undoRedoHelper.Limit; |
|
|
|
@ -590,18 +765,45 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets a value that indicates whether the undo stack has an action that can be undone
|
|
|
|
/// </summary>
|
|
|
|
public bool CanUndo |
|
|
|
{ |
|
|
|
get => _canUndo; |
|
|
|
private set => SetAndRaise(CanUndoProperty, ref _canUndo, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets a value that indicates whether the redo stack has an action that can be redone
|
|
|
|
/// </summary>
|
|
|
|
public bool CanRedo |
|
|
|
{ |
|
|
|
get => _canRedo; |
|
|
|
private set => SetAndRaise(CanRedoProperty, ref _canRedo, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Raised when content is being copied to the clipboard
|
|
|
|
/// </summary>
|
|
|
|
public event EventHandler<RoutedEventArgs>? CopyingToClipboard |
|
|
|
{ |
|
|
|
add => AddHandler(CopyingToClipboardEvent, value); |
|
|
|
remove => RemoveHandler(CopyingToClipboardEvent, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Raised when content is being cut to the clipboard
|
|
|
|
/// </summary>
|
|
|
|
public event EventHandler<RoutedEventArgs>? CuttingToClipboard |
|
|
|
{ |
|
|
|
add => AddHandler(CuttingToClipboardEvent, value); |
|
|
|
remove => RemoveHandler(CuttingToClipboardEvent, value); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Raised when content is being pasted from the clipboard
|
|
|
|
/// </summary>
|
|
|
|
public event EventHandler<RoutedEventArgs>? PastingFromClipboard |
|
|
|
{ |
|
|
|
add => AddHandler(PastingFromClipboardEvent, value); |
|
|
|
@ -831,6 +1033,9 @@ namespace Avalonia.Controls |
|
|
|
return text; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Cuts the current text onto the clipboard
|
|
|
|
/// </summary>
|
|
|
|
public async void Cut() |
|
|
|
{ |
|
|
|
var text = GetSelection(); |
|
|
|
@ -851,6 +1056,9 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Copies the current text onto the clipboard
|
|
|
|
/// </summary>
|
|
|
|
public async void Copy() |
|
|
|
{ |
|
|
|
var text = GetSelection(); |
|
|
|
@ -869,6 +1077,9 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Pastes the current clipboard text content into the TextBox
|
|
|
|
/// </summary>
|
|
|
|
public async void Paste() |
|
|
|
{ |
|
|
|
var eventArgs = new RoutedEventArgs(PastingFromClipboardEvent); |
|
|
|
@ -943,30 +1154,13 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
else if (Match(keymap.Undo) && IsUndoEnabled) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
SnapshotUndoRedo(); |
|
|
|
_isUndoingRedoing = true; |
|
|
|
_undoRedoHelper.Undo(); |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_isUndoingRedoing = false; |
|
|
|
} |
|
|
|
Undo(); |
|
|
|
|
|
|
|
handled = true; |
|
|
|
} |
|
|
|
else if (Match(keymap.Redo) && IsUndoEnabled) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
_isUndoingRedoing = true; |
|
|
|
_undoRedoHelper.Redo(); |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_isUndoingRedoing = false; |
|
|
|
} |
|
|
|
Redo(); |
|
|
|
|
|
|
|
handled = true; |
|
|
|
} |
|
|
|
@ -1420,6 +1614,9 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Clears the text in the TextBox
|
|
|
|
/// </summary>
|
|
|
|
public void Clear() |
|
|
|
{ |
|
|
|
Text = string.Empty; |
|
|
|
@ -1703,5 +1900,62 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Undoes the first action in the undo stack
|
|
|
|
/// </summary>
|
|
|
|
public void Undo() |
|
|
|
{ |
|
|
|
if (IsUndoEnabled && CanUndo) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
// Snapshot the current Text state - this will get popped on to the redo stack
|
|
|
|
// when we call undo below
|
|
|
|
SnapshotUndoRedo(); |
|
|
|
_isUndoingRedoing = true; |
|
|
|
_undoRedoHelper.Undo(); |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_isUndoingRedoing = false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Reapplies the first item on the redo stack
|
|
|
|
/// </summary>
|
|
|
|
public void Redo() |
|
|
|
{ |
|
|
|
if (IsUndoEnabled && CanRedo) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
_isUndoingRedoing = true; |
|
|
|
_undoRedoHelper.Redo(); |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_isUndoingRedoing = false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Called from the UndoRedoHelper when the undo stack is modified
|
|
|
|
/// </summary>
|
|
|
|
void UndoRedoHelper<UndoRedoState>.IUndoRedoHost.OnUndoStackChanged() |
|
|
|
{ |
|
|
|
CanUndo = _undoRedoHelper.CanUndo; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Called from the UndoRedoHelper when the redo stack is modified
|
|
|
|
/// </summary>
|
|
|
|
void UndoRedoHelper<UndoRedoState>.IUndoRedoHost.OnRedoStackChanged() |
|
|
|
{ |
|
|
|
CanRedo = _undoRedoHelper.CanRedo; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|