maui-linux/Xamarin.Forms.Platform.UAP/EntryRenderer.cs

436 строки
13 KiB
C#

using System;
using System.ComponentModel;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Xamarin.Forms.Internals;
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;
using Specifics = Xamarin.Forms.PlatformConfiguration.WindowsSpecific.InputView;
namespace Xamarin.Forms.Platform.UWP
{
public class EntryRenderer : ViewRenderer<Entry, FormsTextBox>
{
bool _fontApplied;
Brush _backgroundColorFocusedDefaultBrush;
Brush _placeholderDefaultBrush;
Brush _textDefaultBrush;
Brush _defaultTextColorFocusBrush;
Brush _defaultPlaceholderColorFocusBrush;
bool _cursorPositionChangePending;
bool _selectionLengthChangePending;
bool _nativeSelectionIsUpdating;
IElementController ElementController => Element as IElementController;
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
if (Control == null)
{
var textBox = new FormsTextBox { Style = Windows.UI.Xaml.Application.Current.Resources["FormsTextBoxStyle"] as Windows.UI.Xaml.Style };
SetNativeControl(textBox);
textBox.TextChanged += OnNativeTextChanged;
textBox.KeyUp += TextBoxOnKeyUp;
textBox.SelectionChanged += SelectionChanged;
textBox.GotFocus += TextBoxGotFocus;
// If the Forms VisualStateManager is in play or the user wants to disable the Forms legacy
// color stuff, then the underlying textbox should just use the Forms VSM states
textBox.UseFormsVsm = e.NewElement.HasVisualStateGroups()
|| !e.NewElement.OnThisPlatform().GetIsLegacyColorModeEnabled();
}
// When we set the control text, it triggers the SelectionChanged event, which updates CursorPosition and SelectionLength;
// These one-time-use variables will let us initialize a CursorPosition and SelectionLength via ctor/xaml/etc.
_cursorPositionChangePending = Element.IsSet(Entry.CursorPositionProperty);
_selectionLengthChangePending = Element.IsSet(Entry.SelectionLengthProperty);
UpdateIsPassword();
UpdateText();
UpdatePlaceholder();
UpdateTextColor();
UpdateFont();
UpdateAlignment();
UpdatePlaceholderColor();
UpdateMaxLength();
UpdateDetectReadingOrderFromContent();
UpdateReturnType();
UpdateIsReadOnly();
UpdateInputScope();
if (_cursorPositionChangePending)
UpdateCursorPosition();
if (_selectionLengthChangePending)
UpdateSelectionLength();
}
}
void TextBoxGotFocus(object sender, RoutedEventArgs e)
{
if (_cursorPositionChangePending)
UpdateCursorPosition();
if (_selectionLengthChangePending)
UpdateSelectionLength();
SetCursorPositionFromRenderer(Control.SelectionStart);
SetSelectionLengthFromRenderer(Control.SelectionLength);
}
protected override void Dispose(bool disposing)
{
if (disposing && Control != null)
{
Control.TextChanged -= OnNativeTextChanged;
Control.KeyUp -= TextBoxOnKeyUp;
Control.SelectionChanged -= SelectionChanged;
Control.GotFocus -= TextBoxGotFocus;
}
base.Dispose(disposing);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Entry.TextProperty.PropertyName)
UpdateText();
else if (e.PropertyName == Entry.IsPasswordProperty.PropertyName)
UpdateIsPassword();
else if (e.PropertyName == Entry.PlaceholderProperty.PropertyName)
UpdatePlaceholder();
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
UpdateTextColor();
else if (e.PropertyName == InputView.KeyboardProperty.PropertyName)
UpdateInputScope();
else if (e.PropertyName == InputView.IsSpellCheckEnabledProperty.PropertyName)
UpdateInputScope();
else if (e.PropertyName == Entry.IsTextPredictionEnabledProperty.PropertyName)
UpdateInputScope();
else if (e.PropertyName == Entry.FontAttributesProperty.PropertyName)
UpdateFont();
else if (e.PropertyName == Entry.FontFamilyProperty.PropertyName)
UpdateFont();
else if (e.PropertyName == Entry.FontSizeProperty.PropertyName)
UpdateFont();
else if (e.PropertyName == Entry.HorizontalTextAlignmentProperty.PropertyName)
UpdateAlignment();
else if (e.PropertyName == Entry.PlaceholderColorProperty.PropertyName)
UpdatePlaceholderColor();
else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
UpdateAlignment();
else if (e.PropertyName == InputView.MaxLengthProperty.PropertyName)
UpdateMaxLength();
else if (e.PropertyName == Specifics.DetectReadingOrderFromContentProperty.PropertyName)
UpdateDetectReadingOrderFromContent();
else if (e.PropertyName == Entry.ReturnTypeProperty.PropertyName)
UpdateReturnType();
else if (e.PropertyName == Entry.CursorPositionProperty.PropertyName)
UpdateCursorPosition();
else if (e.PropertyName == Entry.SelectionLengthProperty.PropertyName)
UpdateSelectionLength();
else if (e.PropertyName == InputView.IsReadOnlyProperty.PropertyName)
UpdateIsReadOnly();
}
protected override void UpdateBackgroundColor()
{
base.UpdateBackgroundColor();
if (Control == null)
{
return;
}
// By default some platforms have alternate default background colors when focused
BrushHelpers.UpdateColor(Element.BackgroundColor, ref _backgroundColorFocusedDefaultBrush,
() => Control.BackgroundFocusBrush, brush => Control.BackgroundFocusBrush = brush);
}
void OnNativeTextChanged(object sender, Windows.UI.Xaml.Controls.TextChangedEventArgs args)
{
Element.SetValueCore(Entry.TextProperty, Control.Text);
}
void TextBoxOnKeyUp(object sender, KeyRoutedEventArgs args)
{
if (args?.Key != VirtualKey.Enter)
return;
// Hide the soft keyboard; this matches the behavior of Forms on Android/iOS
Windows.UI.ViewManagement.InputPane.GetForCurrentView().TryHide();
((IEntryController)Element).SendCompleted();
}
void UpdateAlignment()
{
Control.TextAlignment = Element.HorizontalTextAlignment.ToNativeTextAlignment(((IVisualElementController)Element).EffectiveFlowDirection);
}
void UpdateFont()
{
if (Control == null)
return;
Entry entry = Element;
if (entry == null)
return;
bool entryIsDefault = entry.FontFamily == null && entry.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(Entry), true) && entry.FontAttributes == FontAttributes.None;
if (entryIsDefault && !_fontApplied)
return;
if (entryIsDefault)
{
// ReSharper disable AccessToStaticMemberViaDerivedType
// Resharper wants to simplify 'FormsTextBox' to 'Control', but then it'll conflict with the property 'Control'
Control.ClearValue(FormsTextBox.FontStyleProperty);
Control.ClearValue(FormsTextBox.FontSizeProperty);
Control.ClearValue(FormsTextBox.FontFamilyProperty);
Control.ClearValue(FormsTextBox.FontWeightProperty);
Control.ClearValue(FormsTextBox.FontStretchProperty);
// ReSharper restore AccessToStaticMemberViaDerivedType
}
else
{
Control.ApplyFont(entry);
}
_fontApplied = true;
}
void UpdateInputScope()
{
Entry entry = Element;
if (entry.Keyboard is CustomKeyboard custom)
{
Control.IsTextPredictionEnabled = (custom.Flags & KeyboardFlags.Suggestions) != 0;
Control.IsSpellCheckEnabled = (custom.Flags & KeyboardFlags.Spellcheck) != 0;
}
else
{
if (entry.IsSet(Entry.IsTextPredictionEnabledProperty))
Control.IsTextPredictionEnabled = entry.IsTextPredictionEnabled;
else
Control.ClearValue(TextBox.IsTextPredictionEnabledProperty);
if (entry.IsSet(InputView.IsSpellCheckEnabledProperty))
Control.IsSpellCheckEnabled = entry.IsSpellCheckEnabled;
else
Control.ClearValue(TextBox.IsSpellCheckEnabledProperty);
}
Control.InputScope = entry.Keyboard.ToInputScope();
}
void UpdateIsPassword()
{
Control.IsPassword = Element.IsPassword;
}
void UpdatePlaceholder()
{
Control.PlaceholderText = Element.Placeholder ?? "";
}
void UpdatePlaceholderColor()
{
Color placeholderColor = Element.PlaceholderColor;
BrushHelpers.UpdateColor(placeholderColor, ref _placeholderDefaultBrush,
() => Control.PlaceholderForegroundBrush, brush => Control.PlaceholderForegroundBrush = brush);
BrushHelpers.UpdateColor(placeholderColor, ref _defaultPlaceholderColorFocusBrush,
() => Control.PlaceholderForegroundFocusBrush, brush => Control.PlaceholderForegroundFocusBrush = brush);
}
void UpdateText()
{
Control.Text = Element.Text ?? "";
}
void UpdateTextColor()
{
Color textColor = Element.TextColor;
BrushHelpers.UpdateColor(textColor, ref _textDefaultBrush,
() => Control.Foreground, brush => Control.Foreground = brush);
BrushHelpers.UpdateColor(textColor, ref _defaultTextColorFocusBrush,
() => Control.ForegroundFocusBrush, brush => Control.ForegroundFocusBrush = brush);
}
void UpdateMaxLength()
{
Control.MaxLength = Element.MaxLength;
var currentControlText = Control.Text;
if (currentControlText.Length > Element.MaxLength)
Control.Text = currentControlText.Substring(0, Element.MaxLength);
}
void UpdateDetectReadingOrderFromContent()
{
if (Element.IsSet(Specifics.DetectReadingOrderFromContentProperty))
{
if (Element.OnThisPlatform().GetDetectReadingOrderFromContent())
{
Control.TextReadingOrder = TextReadingOrder.DetectFromContent;
}
else
{
Control.TextReadingOrder = TextReadingOrder.UseFlowDirection;
}
}
}
void UpdateReturnType()
{
if (Control == null || Element == null)
return;
Control.InputScope = Element.ReturnType.ToInputScope();
}
void SelectionChanged(object sender, RoutedEventArgs e)
{
if (_nativeSelectionIsUpdating || Control == null || Element == null)
return;
int cursorPosition = Element.CursorPosition;
if (!_cursorPositionChangePending)
{
var start = cursorPosition;
int selectionStart = Control.SelectionStart;
if (selectionStart != start)
SetCursorPositionFromRenderer(selectionStart);
}
if (!_selectionLengthChangePending)
{
int elementSelectionLength = Math.Min(Control.Text.Length - cursorPosition, Element.SelectionLength);
int controlSelectionLength = Control.SelectionLength;
if (controlSelectionLength != elementSelectionLength)
SetSelectionLengthFromRenderer(controlSelectionLength);
}
}
void UpdateSelectionLength()
{
if (_nativeSelectionIsUpdating || Control == null || Element == null)
return;
if (Control.Focus(FocusState.Programmatic))
{
try
{
int selectionLength = 0;
int elemSelectionLength = Element.SelectionLength;
if (Element.IsSet(Entry.SelectionLengthProperty))
selectionLength = Math.Max(0, Math.Min(Control.Text.Length - Element.CursorPosition, elemSelectionLength));
if (elemSelectionLength != selectionLength)
SetSelectionLengthFromRenderer(selectionLength);
Control.SelectionLength = selectionLength;
}
catch (Exception ex)
{
Log.Warning("Entry", $"Failed to set Control.SelectionLength from SelectionLength: {ex}");
}
finally
{
_selectionLengthChangePending = false;
}
}
}
void UpdateCursorPosition()
{
if (_nativeSelectionIsUpdating || Control == null || Element == null)
return;
if (Control.Focus(FocusState.Programmatic))
{
try
{
int start = Control.Text.Length;
int cursorPosition = Element.CursorPosition;
if (Element.IsSet(Entry.CursorPositionProperty))
start = Math.Min(start, cursorPosition);
if (start != cursorPosition)
SetCursorPositionFromRenderer(start);
Control.SelectionStart = start;
// Length is dependent on start, so we'll need to update it
UpdateSelectionLength();
}
catch (Exception ex)
{
Log.Warning("Entry", $"Failed to set Control.SelectionStart from CursorPosition: {ex}");
}
finally
{
_cursorPositionChangePending = false;
}
}
}
void SetCursorPositionFromRenderer(int start)
{
try
{
_nativeSelectionIsUpdating = true;
ElementController?.SetValueFromRenderer(Entry.CursorPositionProperty, start);
}
catch (Exception ex)
{
Log.Warning("Entry", $"Failed to set CursorPosition from renderer: {ex}");
}
finally
{
_nativeSelectionIsUpdating = false;
}
}
void SetSelectionLengthFromRenderer(int selectionLength)
{
try
{
_nativeSelectionIsUpdating = true;
ElementController?.SetValueFromRenderer(Entry.SelectionLengthProperty, selectionLength);
}
catch (Exception ex)
{
Log.Warning("Entry", $"Failed to set SelectionLength from renderer: {ex}");
}
finally
{
_nativeSelectionIsUpdating = false;
}
}
void UpdateIsReadOnly()
{
Control.IsReadOnly = Element.IsReadOnly;
}
}
}