369 строки
11 KiB
C#
369 строки
11 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using Windows.UI.Xaml;
|
|
using Windows.UI.Xaml.Controls;
|
|
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 EditorRenderer : ViewRenderer<Editor, FormsTextBox>
|
|
{
|
|
private static FormsTextBox _copyOfTextBox;
|
|
static Windows.Foundation.Size _zeroSize = new Windows.Foundation.Size(0, 0);
|
|
bool _fontApplied;
|
|
Brush _backgroundColorFocusedDefaultBrush;
|
|
Brush _textDefaultBrush;
|
|
Brush _defaultTextColorFocusBrush;
|
|
Brush _defaultPlaceholderColorFocusBrush;
|
|
Brush _placeholderDefaultBrush;
|
|
|
|
IEditorController ElementController => Element;
|
|
|
|
|
|
FormsTextBox CreateTextBox()
|
|
{
|
|
return new FormsTextBox
|
|
{
|
|
AcceptsReturn = true,
|
|
TextWrapping = TextWrapping.Wrap,
|
|
Style = Windows.UI.Xaml.Application.Current.Resources["FormsTextBoxStyle"] as Windows.UI.Xaml.Style
|
|
};
|
|
}
|
|
|
|
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
|
|
{
|
|
if (e.NewElement != null)
|
|
{
|
|
if (Control == null)
|
|
{
|
|
var textBox = CreateTextBox();
|
|
|
|
SetNativeControl(textBox);
|
|
|
|
textBox.TextChanged += OnNativeTextChanged;
|
|
textBox.LostFocus += OnLostFocus;
|
|
|
|
// 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();
|
|
}
|
|
|
|
UpdateText();
|
|
UpdateInputScope();
|
|
UpdateTextColor();
|
|
UpdateFont();
|
|
UpdateTextAlignment();
|
|
UpdateFlowDirection();
|
|
UpdateMaxLength();
|
|
UpdateDetectReadingOrderFromContent();
|
|
UpdatePlaceholderText();
|
|
UpdatePlaceholderColor();
|
|
UpdateIsReadOnly();
|
|
}
|
|
|
|
base.OnElementChanged(e);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing && Control != null)
|
|
{
|
|
Control.TextChanged -= OnNativeTextChanged;
|
|
Control.LostFocus -= OnLostFocus;
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
base.OnElementPropertyChanged(sender, e);
|
|
|
|
if (e.PropertyName == Editor.TextColorProperty.PropertyName)
|
|
{
|
|
UpdateTextColor();
|
|
}
|
|
else if (e.PropertyName == InputView.KeyboardProperty.PropertyName)
|
|
{
|
|
UpdateInputScope();
|
|
}
|
|
else if (e.PropertyName == InputView.IsSpellCheckEnabledProperty.PropertyName)
|
|
{
|
|
UpdateInputScope();
|
|
}
|
|
else if (e.PropertyName == Editor.IsTextPredictionEnabledProperty.PropertyName)
|
|
{
|
|
UpdateInputScope();
|
|
}
|
|
else if (e.PropertyName == Editor.FontAttributesProperty.PropertyName)
|
|
{
|
|
UpdateFont();
|
|
}
|
|
else if (e.PropertyName == Editor.FontFamilyProperty.PropertyName)
|
|
{
|
|
UpdateFont();
|
|
}
|
|
else if (e.PropertyName == Editor.FontSizeProperty.PropertyName)
|
|
{
|
|
UpdateFont();
|
|
}
|
|
else if (e.PropertyName == Editor.TextProperty.PropertyName)
|
|
{
|
|
UpdateText();
|
|
}
|
|
else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
|
|
{
|
|
UpdateTextAlignment();
|
|
UpdateFlowDirection();
|
|
}
|
|
else if (e.PropertyName == InputView.MaxLengthProperty.PropertyName)
|
|
UpdateMaxLength();
|
|
else if (e.PropertyName == Specifics.DetectReadingOrderFromContentProperty.PropertyName)
|
|
UpdateDetectReadingOrderFromContent();
|
|
else if (e.PropertyName == Editor.PlaceholderProperty.PropertyName)
|
|
UpdatePlaceholderText();
|
|
else if (e.PropertyName == Editor.PlaceholderColorProperty.PropertyName)
|
|
UpdatePlaceholderColor();
|
|
else if (e.PropertyName == InputView.IsReadOnlyProperty.PropertyName)
|
|
UpdateIsReadOnly();
|
|
}
|
|
|
|
void OnLostFocus(object sender, RoutedEventArgs e)
|
|
{
|
|
ElementController.SendCompleted();
|
|
}
|
|
|
|
void UpdatePlaceholderText()
|
|
{
|
|
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);
|
|
}
|
|
|
|
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(Editor.TextProperty, Control.Text);
|
|
}
|
|
|
|
/*
|
|
* Purely invalidating the layout as text is added to the TextBox will not cause it to expand.
|
|
* If the TextBox is set to WordWrap and it is part of the layout it will refuse to Measure itself beyond its established width.
|
|
* Even giving it infinite constraints will cause it to always set its DesiredSize to the same width but with a vertical growth.
|
|
* The only way I was able to grow it was by setting layout renderers width explicitly to some value but then it just set its own Width to that Width which is not helpful.
|
|
* Even vertically it would measure oddly in cases of rapid text changes.
|
|
* Holding down the backspace key or enter key would cause the final result to be not quite right.
|
|
* Both of these issues were fixed by just creating a static TextBox that is not part of the layout which let me just measure
|
|
* the size of the text as it would fit into the TextBox unconstrained and then just return that Size from the GetDesiredSize call.
|
|
* */
|
|
Size GetCopyOfSize(FormsTextBox control, Windows.Foundation.Size constraint)
|
|
{
|
|
if (_copyOfTextBox == null)
|
|
{
|
|
_copyOfTextBox = CreateTextBox();
|
|
|
|
// This causes the copy to be initially setup correctly.
|
|
// I found that if the first measure of this copy occurs with Text then it will just keep defaulting to a measure with no text.
|
|
_copyOfTextBox.Measure(_zeroSize);
|
|
}
|
|
|
|
_copyOfTextBox.Text = control.Text;
|
|
_copyOfTextBox.FontSize = control.FontSize;
|
|
_copyOfTextBox.FontFamily = control.FontFamily;
|
|
_copyOfTextBox.FontStretch = control.FontStretch;
|
|
_copyOfTextBox.FontStyle = control.FontStyle;
|
|
_copyOfTextBox.FontWeight = control.FontWeight;
|
|
_copyOfTextBox.Margin = control.Margin;
|
|
_copyOfTextBox.Padding = control.Padding;
|
|
|
|
// have to reset the measure to zero before it will re-measure itself
|
|
_copyOfTextBox.Measure(_zeroSize);
|
|
_copyOfTextBox.Measure(constraint);
|
|
|
|
Size result = new Size
|
|
(
|
|
Math.Ceiling(_copyOfTextBox.DesiredSize.Width),
|
|
Math.Ceiling(_copyOfTextBox.DesiredSize.Height)
|
|
);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
SizeRequest CalculateDesiredSizes(FormsTextBox control, Windows.Foundation.Size constraint, EditorAutoSizeOption sizeOption)
|
|
{
|
|
if (sizeOption == EditorAutoSizeOption.TextChanges)
|
|
{
|
|
Size result = GetCopyOfSize(control, constraint);
|
|
control.Measure(constraint);
|
|
return new SizeRequest(result);
|
|
}
|
|
else
|
|
{
|
|
control.Measure(constraint);
|
|
Size result = new Size(Math.Ceiling(control.DesiredSize.Width), Math.Ceiling(control.DesiredSize.Height));
|
|
return new SizeRequest(result);
|
|
}
|
|
}
|
|
|
|
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
|
|
{
|
|
FormsTextBox child = Control;
|
|
|
|
if (Children.Count == 0 || child == null)
|
|
return new SizeRequest();
|
|
|
|
return CalculateDesiredSizes(child, new Windows.Foundation.Size(widthConstraint, heightConstraint), Element.AutoSize);
|
|
}
|
|
|
|
void UpdateFont()
|
|
{
|
|
if (Control == null)
|
|
return;
|
|
|
|
Editor editor = Element;
|
|
|
|
if (editor == null)
|
|
return;
|
|
|
|
bool editorIsDefault = editor.FontFamily == null &&
|
|
editor.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(Editor), true) &&
|
|
editor.FontAttributes == FontAttributes.None;
|
|
|
|
if (editorIsDefault && !_fontApplied)
|
|
return;
|
|
|
|
if (editorIsDefault)
|
|
{
|
|
// ReSharper disable AccessToStaticMemberViaDerivedType
|
|
// Resharper wants to simplify 'TextBox' to 'Control', but then it'll conflict with the property 'Control'
|
|
Control.ClearValue(TextBox.FontStyleProperty);
|
|
Control.ClearValue(TextBox.FontSizeProperty);
|
|
Control.ClearValue(TextBox.FontFamilyProperty);
|
|
Control.ClearValue(TextBox.FontWeightProperty);
|
|
Control.ClearValue(TextBox.FontStretchProperty);
|
|
// ReSharper restore AccessToStaticMemberViaDerivedType
|
|
}
|
|
else
|
|
{
|
|
Control.ApplyFont(editor);
|
|
}
|
|
|
|
_fontApplied = true;
|
|
}
|
|
|
|
void UpdateInputScope()
|
|
{
|
|
Editor editor = Element;
|
|
var custom = editor.Keyboard as CustomKeyboard;
|
|
if (custom != null)
|
|
{
|
|
Control.IsTextPredictionEnabled = (custom.Flags & KeyboardFlags.Suggestions) != 0;
|
|
Control.IsSpellCheckEnabled = (custom.Flags & KeyboardFlags.Spellcheck) != 0;
|
|
}
|
|
else
|
|
{
|
|
if (editor.IsSet(Editor.IsTextPredictionEnabledProperty))
|
|
Control.IsTextPredictionEnabled = editor.IsTextPredictionEnabled;
|
|
else
|
|
Control.ClearValue(TextBox.IsTextPredictionEnabledProperty);
|
|
if (editor.IsSet(InputView.IsSpellCheckEnabledProperty))
|
|
Control.IsSpellCheckEnabled = editor.IsSpellCheckEnabled;
|
|
else
|
|
Control.ClearValue(TextBox.IsSpellCheckEnabledProperty);
|
|
}
|
|
|
|
Control.InputScope = editor.Keyboard.ToInputScope();
|
|
}
|
|
|
|
void UpdateText()
|
|
{
|
|
string newText = Element.Text ?? "";
|
|
|
|
if (Control.Text == newText)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Control.Text = newText;
|
|
Control.SelectionStart = Control.Text.Length;
|
|
}
|
|
|
|
void UpdateTextAlignment()
|
|
{
|
|
Control.UpdateTextAlignment(Element);
|
|
}
|
|
|
|
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 UpdateFlowDirection()
|
|
{
|
|
Control.UpdateFlowDirection(Element);
|
|
}
|
|
|
|
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 UpdateIsReadOnly()
|
|
{
|
|
Control.IsReadOnly = Element.IsReadOnly;
|
|
}
|
|
}
|
|
} |