Here's the code for EditText:
class EditText : FrameworkElement
{
TextBlock hintBlock, errorBlock, lengthBlock;
TextBox inputBox;
Separator separator;
Path leftIcon, invalidIcon, validIcon;
Grid container, infoContainer;
TranslateTransform translateHint;
ScaleTransform scaleHint;
DoubleAnimation translateHintAnim, scaleHintAnim;
ColorAnimation brushAnim;
SolidColorBrush brush;
bool isHintMoved;
public string Icon { get; set; }
public string Hint { get; set; }
public bool IsRequired { get; set; }
public bool IsMultiline { get; set; }
public int MaxLength { get; set; }
public bool IsTrimBottomRequested { get; set; }
public EditText() {
Margin = new Thickness(5);
brush = new SolidColorBrush(Colors.SkyBlue);
initializeControls();
container = new Grid {
Margin = new Thickness(0, 5, 0, 0),
RowDefinitions = {
new RowDefinition(),
new RowDefinition(){Height = GridLength.Auto},
new RowDefinition(){Height = GridLength.Auto}
},
ColumnDefinitions = {
new ColumnDefinition(){Width = GridLength.Auto},
new ColumnDefinition(),
new ColumnDefinition(){Width = GridLength.Auto}
},
Children = { leftIcon, inputBox, hintBlock, invalidIcon, validIcon, separator, infoContainer }
};
AddVisualChild(container);
initializeAnimations();
inputBox.TextChanged += onInputTextChanged;
Loaded += onLoaded;
}
void onLoaded(object sender, RoutedEventArgs e) {
hintBlock.Text = Hint;
if (Icon != null) {
leftIcon.Data = Geometry.Parse(Icon);
leftIcon.Visibility = Visibility.Visible;
}
if (IsRequired) {
Grid.SetColumnSpan(inputBox, 1);
errorBlock.Visibility = Visibility.Visible;
invalidIcon.Visibility = Visibility.Visible;
}
if (MaxLength > 0) {
inputBox.MaxLength = MaxLength;
lengthBlock.Text = "0/" + MaxLength;
lengthBlock.Visibility = Visibility.Visible;
}
if (IsMultiline) {
inputBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
inputBox.Resources.Add(typeof(ScrollBar), App.multilineTextScrollStyle);
inputBox.AcceptsReturn = true;
inputBox.TextWrapping = TextWrapping.Wrap;
inputBox.VerticalAlignment = VerticalAlignment.Top;
leftIcon.VerticalAlignment = VerticalAlignment.Top;
invalidIcon.VerticalAlignment = VerticalAlignment.Top;
validIcon.VerticalAlignment = VerticalAlignment.Top;
hintBlock.VerticalAlignment = VerticalAlignment.Top;
}
inputBox.SetBinding(TextBox.TextProperty, new Binding() {
Path = new PropertyPath(nameof(Text)),
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(EditText), 1),
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
errorBlock.SetBinding(TextBlock.TextProperty, new Binding() {
Path = new PropertyPath(nameof(Error)),
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(EditText), 1)
});
if (IsTrimBottomRequested) {
infoContainer.Visibility = Visibility.Collapsed;
}
}
void initializeControls() {
translateHint = new TranslateTransform();
scaleHint = new ScaleTransform();
hintBlock = new TextBlock {
Padding = new Thickness(5, 0, 5, 0),
Foreground = Brushes.Gray,
Background = Brushes.White,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
RenderTransform = new TransformGroup {
Children = { scaleHint, translateHint }
}
};
inputBox = new TextBox {
BorderThickness = new Thickness(0),
Margin = new Thickness(5, 0, 5, 0),
VerticalAlignment = VerticalAlignment.Bottom
};
separator = new Separator { Background = brush };
leftIcon = new Path {
Fill = brush,
Width = 18,
Height = 18,
Stretch = Stretch.Uniform,
HorizontalAlignment = HorizontalAlignment.Left,
Visibility = Visibility.Collapsed
};
invalidIcon = new Path {
Data = Geometry.Parse(Icons.Info),
Fill = Brushes.Coral,
Width = 16,
Height = 16,
Stretch = Stretch.Uniform,
Visibility = Visibility.Collapsed,
HorizontalAlignment = HorizontalAlignment.Right
};
validIcon = new Path {
Data = Geometry.Parse(Icons.Checked),
Fill = Brushes.Green,
Width = 16,
Height = 16,
Stretch = Stretch.Uniform,
Visibility = Visibility.Collapsed
};
Grid.SetRow(separator, 1);
Grid.SetColumnSpan(separator, 3);
Grid.SetColumn(inputBox, 1);
Grid.SetColumnSpan(inputBox, 2);
Grid.SetColumn(hintBlock, 1);
Grid.SetColumn(invalidIcon, 2);
Grid.SetColumn(validIcon, 2);
errorBlock = new TextBlock {
Foreground = Brushes.Gray,
Margin = new Thickness(5, 0, 0, 0),
TextWrapping = TextWrapping.Wrap,
Visibility = Visibility.Hidden,
FontSize = 11,
FontStyle = FontStyles.Italic
};
lengthBlock = new TextBlock() {
Foreground = Brushes.Gray,
Visibility = Visibility.Collapsed,
HorizontalAlignment = HorizontalAlignment.Right,
FontSize = 11,
FontStyle = FontStyles.Italic
};
Grid.SetColumn(lengthBlock, 1);
infoContainer = new Grid() {
ColumnDefinitions = {
new ColumnDefinition(),
new ColumnDefinition(){Width = GridLength.Auto}
},
Children = { errorBlock, lengthBlock }
};
Grid.SetRow(infoContainer, 2);
Grid.SetColumnSpan(infoContainer, 3);
}
void initializeAnimations() {
var duration = TimeSpan.FromMilliseconds(250);
var ease = new CubicEase { EasingMode = EasingMode.EaseInOut };
scaleHintAnim = new DoubleAnimation {
Duration = duration,
EasingFunction = ease
};
translateHintAnim = new DoubleAnimation {
Duration = duration,
EasingFunction = ease
};
brushAnim = new ColorAnimation {
Duration = duration,
EasingFunction = ease
};
}
void onInputTextChanged(object sender, TextChangedEventArgs e) {
lengthBlock.Text = inputBox.Text.Length + "/" + MaxLength;
}
void animateHint() {
translateHint.BeginAnimation(TranslateTransform.YProperty, translateHintAnim);
scaleHint.BeginAnimation(ScaleTransform.ScaleYProperty, scaleHintAnim);
scaleHint.BeginAnimation(ScaleTransform.ScaleXProperty, scaleHintAnim);
}
void moveHintUp() {
isHintMoved = true;
scaleHintAnim.To = 0.9;
translateHintAnim.To = -15;
animateHint();
}
void moveHintDown() {
isHintMoved = false;
scaleHintAnim.To = 1;
translateHintAnim.To = 0;
animateHint();
}
#region Overrides
protected override void OnMouseEnter(MouseEventArgs e) {
if (string.IsNullOrWhiteSpace(inputBox.Text)) {
inputBox.Focus();
brushAnim.To = Colors.CornflowerBlue;
brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);
}
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) {
if (inputBox.Text.Length == 0 && !isHintMoved) moveHintUp();
brushAnim.To = Colors.CornflowerBlue;
brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e) {
if (string.IsNullOrWhiteSpace(Text) && isHintMoved) moveHintDown();
brushAnim.To = Colors.SkyBlue;
brush.BeginAnimation(SolidColorBrush.ColorProperty, brushAnim);
}
protected override Size MeasureOverride(Size availableSize) {
if (IsMultiline) {
hintBlock.Measure(availableSize);
separator.Measure(availableSize);
leftIcon.Measure(availableSize);
double height = 0d;
if (double.IsInfinity(availableSize.Height)) height = 100;
else {
height = availableSize.Height - hintBlock.DesiredSize.Height - separator.DesiredSize.Height - Margin.Top;
height = height > 0 ? height : 0;
}
inputBox.Height = height;
}
container.Width = availableSize.Width;
container.Measure(availableSize);
return container.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize) {
container.Arrange(new Rect(container.DesiredSize));
return finalSize;
}
protected override Visual GetVisualChild(int index) => container;
protected override int VisualChildrenCount => 1;
#endregion
#region DependencyProperty
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public string Error {
get { return (string)GetValue(ErrorProperty); }
set { SetValue(ErrorProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(EditText), new FrameworkPropertyMetadata() {
DefaultValue = null,
BindsTwoWayByDefault = true,
PropertyChangedCallback = onTextChanged
});
static void onTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var o = d as EditText;
if (e.NewValue == null && o.Error == string.Empty ||
e.NewValue == null && o.Error == null)
o.OnPreviewLostKeyboardFocus(null);
else {
o.moveHintUp();
}
}
public static readonly DependencyProperty ErrorProperty =
DependencyProperty.Register("Error", typeof(string), typeof(EditText), new PropertyMetadata(null, onErrorChanged));
static void onErrorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var o = d as EditText;
var err = e.NewValue == null ? null : e.NewValue.ToString();
if (err == string.Empty) {
o.invalidIcon.Visibility = Visibility.Hidden;
o.validIcon.Visibility = Visibility.Visible;
o.errorBlock.Visibility = Visibility.Hidden;
}
else {
o.invalidIcon.Visibility = Visibility.Visible;
o.validIcon.Visibility = Visibility.Hidden;
o.errorBlock.Visibility = Visibility.Visible;
}
}
#endregion
}