C# Maui暂时还没有TextBox,因为这个可以通过xaml样式实现,但是为了长期使用,自己写一个TextBox。
定义一个TextEventArgs
public class TextEventArgs : EventArgs { public string Text{ get; set; } public TextEventArgs(string text) { Text = text; } }
PropertyManager和之前的例子一样,这里就不重复了。除非更新了新功能。
我的MauiProgram.cs中添加了自定义字体,这个在第一个MAUI 配置中说了,以后也不再重复了。自己去https://www.iconfont.cn/注册账号,添加自己喜欢的字体icon,然后下载下来覆盖项目中的IconFont.ttf
fonts.AddFont("IconFont.ttf", "IconFont");
TextBox继承Border,以后所有自定义控件都继承于Border,不要用Frame了,因为这个在将来的未来会被淘汰。现在Border可以实现所有功能。
public class TextBox : Border { public static readonly BindableProperty TextProperty = BindableProperty.Create( nameof(Text), typeof(string), typeof(TextBox), null, BindingMode.TwoWay, propertyChanged: OnTextChanged); public static readonly BindableProperty IsPasswordProperty = BindableProperty.Create( nameof(IsPassword), typeof(bool), typeof(TextBox), false); public static readonly BindableProperty IsMultilineProperty = BindableProperty.Create( nameof(IsMultiline), typeof(bool), typeof(TextBox), false); public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create( nameof(Placeholder), typeof(string), typeof(TextBox), null); public static readonly BindableProperty TextColorProperty = BindableProperty.Create( nameof(TextColor), typeof(Color), typeof(TextBox), Colors.Black); public static readonly BindableProperty TextSizeProperty = BindableProperty.Create( nameof(TextSize), typeof(double), typeof(TextBox), 15d); public static readonly BindableProperty IsReadOnlyProperty = BindableProperty.Create( nameof(IsReadOnly), typeof(bool), typeof(TextBox), false, propertyChanged: OnIsReadOnlyChanged); public static readonly BindableProperty CharacterSpacingProperty = BindableProperty.Create( nameof(CharacterSpacing), typeof(double), typeof(TextBox), 0d); public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(double), typeof(TextBox), 0d, propertyChanged: PropertyManager.CornerRadiusProperty); public static readonly BindableProperty IconTextProperty = BindableProperty.Create(nameof(IconText), typeof(string), typeof(TextBox), null); public static readonly BindableProperty IconTextColorProperty = BindableProperty.Create(nameof(IconTextColor), typeof(Color), typeof(TextBox), Colors.Gray); public static readonly BindableProperty IconTextFontSizeProperty = BindableProperty.Create(nameof(IconTextFontSize), typeof(double), typeof(TextBox), 22d); public static readonly BindableProperty IconFontFamilyProperty = BindableProperty.Create(nameof(IconFontFamily), typeof(string), typeof(TextBox), "IconFont"); public event EventHandler<TextEventArgs>? ReturnPressed, EditingFinished; public string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } public bool IsPassword { get => (bool)GetValue(IsPasswordProperty); set => SetValue(IsPasswordProperty, value); } public bool IsMultiline { get => (bool)GetValue(IsMultilineProperty); set => SetValue(IsMultilineProperty, value); } public string Placeholder { get => (string)GetValue(PlaceholderProperty); set => SetValue(PlaceholderProperty, value); } public Color TextColor { get => (Color)GetValue(TextColorProperty); set => SetValue(TextColorProperty, value); } public double TextSize { get => (double)GetValue(TextSizeProperty); set => SetValue(TextSizeProperty, value); } public bool IsReadOnly { get => (bool)GetValue(IsReadOnlyProperty); set => SetValue(IsReadOnlyProperty, value); } public double CharacterSpacing { get => (double)GetValue(CharacterSpacingProperty); set => SetValue(CharacterSpacingProperty, value); } public double CornerRadius { get => (double)GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); } public string IconText { get => (string)GetValue(IconTextProperty); set => SetValue(IconTextProperty, value); } public Color IconTextColor { get => (Color)GetValue(IconTextColorProperty); set => SetValue(IconTextColorProperty, value); } public double IconTextFontSize { get => (double)GetValue(IconTextFontSizeProperty); set => SetValue(IconTextFontSizeProperty, value); } public string IconFontFamily { get => (string)GetValue(IconFontFamilyProperty); set => SetValue(IconFontFamilyProperty, value); } private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue) { var tb = (TextBox)bindable; var newText = newValue as string ?? string.Empty; var oldText = oldValue as string ?? string.Empty; if (newText.Length > oldText.Length && tb.IsMultiline) { var addText = newText.Substring(oldText.Length); if (addText.Contains('r') || addText.Contains('n')) { //如果是回车换行,则触发ReturnPressed事件,安全派发到UI线程 tb.Dispatcher.Dispatch(() => tb.ReturnPressed?.Invoke(tb, new TextEventArgs(newText))); } } } //动态更新IsReadOnly属性 private static void OnIsReadOnlyChanged(BindableObject bindable, object oldValue, object newValue) { var tb = (TextBox)bindable; if (tb.grid.Children.Count == 0) return; if (tb.grid.Children[0] is InputView view) { view.IsReadOnly = (bool)newValue; view.Background = (bool)newValue ? Colors.WhiteSmoke : Colors.Transparent; } } private Grid grid; public TextBox() { grid = new Grid() { ColumnDefinitions = new ColumnDefinitionCollection { new ColumnDefinition { Width = GridLength.Star }, new ColumnDefinition { Width = GridLength.Auto } }, }; // 设置布局 this.Content = grid; this.StrokeThickness = 1; this.Stroke = Colors.LightGray; this.StrokeShape = new RoundRectangle() { CornerRadius = CornerRadius }; this.Padding = new Thickness(0); //重载OnHandlerChanged中也可以初始化,构造函数会先于属性设置执行 //Dispatcher.Dispatch(Init)也可以初始化 this.Loaded += Init; } private void Init(object? sender, EventArgs e) { //凡是设置了propertyChanged的属性,都需要在这里手动初始化,因为Dispatcher.Dispatch/Loaded会在propertyChanged之后执行 InputView edit = IsMultiline ? new Editor() { Margin = new Thickness(0), AutoSize = EditorAutoSizeOption.TextChanges, VerticalTextAlignment = TextAlignment.Start, } : new Entry() { Margin = new Thickness(0), VerticalTextAlignment = TextAlignment.Start, }; edit.IsReadOnly = IsReadOnly; edit.Background = IsReadOnly ? Colors.WhiteSmoke : Colors.Transparent; grid.Children.Add(edit); edit.SetBinding(InputView.TextProperty, new Binding(nameof(Text), mode: BindingMode.TwoWay, source: this)); edit.SetBinding(InputView.PlaceholderProperty, new Binding(nameof(Placeholder), mode: BindingMode.TwoWay, source: this)); edit.SetBinding(InputView.TextColorProperty, new Binding(nameof(TextColor), mode: BindingMode.TwoWay, source: this)); edit.SetBinding(InputView.FontSizeProperty, new Binding(nameof(TextSize), mode: BindingMode.TwoWay, source: this)); edit.SetBinding(InputView.CharacterSpacingProperty, new Binding(nameof(CharacterSpacing), mode: BindingMode.TwoWay, source: this)); if (edit is Entry entry) { edit.SetBinding(Entry.IsPasswordProperty, new Binding(nameof(IsPassword), mode: BindingMode.TwoWay, source: this)); entry.Completed += (s, e) => { ReturnPressed?.Invoke(this, new TextEventArgs(edit.Text)); edit.Unfocus(); }; } edit.Unfocused += (s, e) => { EditingFinished?.Invoke(this, new TextEventArgs(edit.Text)); }; if (IsPassword) { Label icon = new Label() { Text = IconText, FontFamily = IconFontFamily, FontSize = IconTextFontSize, TextColor = IconTextColor, VerticalTextAlignment = TextAlignment.Center, Margin = new Thickness(10, 0), }; grid.Children.Add(icon); Grid.SetColumn(icon, 1); icon.SetBinding(Label.TextProperty, new Binding(nameof(IconText), mode: BindingMode.TwoWay, source: this)); icon.SetBinding(Label.TextColorProperty, new Binding(nameof(IconTextColor), mode: BindingMode.TwoWay, source: this)); icon.SetBinding(Label.FontSizeProperty, new Binding(nameof(IconTextFontSize), mode: BindingMode.TwoWay, source: this)); icon.SetBinding(Label.FontFamilyProperty, new Binding(nameof(IconFontFamily), mode: BindingMode.TwoWay, source: this)); TapGestureRecognizer tapGesture = new TapGestureRecognizer(); tapGesture.Tapped += OnTapped; icon.GestureRecognizers.Add(tapGesture); } } private void OnTapped(object? sender, TappedEventArgs e) { Grid? grid = (sender as Label)?.Parent as Grid; if (grid == null) return; if (grid.Children[0] is Entry entry) { entry.IsPassword = !entry.IsPassword; } } }
TextBox.xaml (前面的例子已经说明了如何把自定义控件,加到默认命名空间,以后也不再重复了)
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MauiViews.MauiDemos.Book._03.TextBox" Title="TextBox" HeightRequest="400" WidthRequest="300"> <Grid RowDefinitions="auto,auto,auto, *"> <TextBox Placeholder="单行输入-只读" CornerRadius="15" IsReadOnly="True"/> <TextBox Grid.Row="1" Placeholder="单行输入" ReturnPressed="TextBox_ReturnPressed" CornerRadius="15"/> <TextBox Grid.Row="2" Placeholder="密码输入" IsPassword="True" ReturnPressed="TextBox_ReturnPressed" IconText=""/> <TextBox Grid.Row="3" Placeholder="多行输入" IsMultiline="True" ReturnPressed="TextBox_ReturnPressed"/> </Grid> </ContentPage>
对应的cs代码
using Shares.Utility; using System.Diagnostics; namespace MauiViews.MauiDemos.Book._03; public partial class TextBox : ContentPage { public TextBox() { InitializeComponent(); } private void TextBox_ReturnPressed(object? sender, TextEventArgs e) { Trace.WriteLine($"TextBox_ReturnPressed: {e.Text}"); } }
运行效果