03 - LayoutPanels例子 - TextBox

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,
                    IsReadOnly = IsReadOnly,
                    Background = IsReadOnly ? Colors.WhiteSmoke : Colors.Transparent,
                } :
                new Entry()
                {
                    Margin = new Thickness(0),
                    VerticalTextAlignment = TextAlignment.Start,
                    IsReadOnly = IsReadOnly,
                    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="&#xe615;"/>
        <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}");
    }
}

运行效果

相关推荐
专注VB编程开发20年10 天前
java/.net跨平台UI浏览器SDK,浏览器控件开发包分析
linux·ui·跨平台·浏览器·cef·miniblink
Areslee10 天前
一种通用跨平台实现SEH的解决方案
linux·macos·内核·跨平台·seh
攻城狮7号10 天前
【AI时代速通QT】第二节:Qt SDK 的目录介绍和第一个Qt Creator项目
c语言·c++·qt·跨平台
dalgleish14 天前
03 - LayoutPanels例子 - SimpleInkCanvas
跨平台·mvvm·c# maui
南岩亦凛汀15 天前
在Linux下使用wxWidgets进行跨平台GUI开发(三)
c++·跨平台·gui·开源框架·工程实战教程
南岩亦凛汀20 天前
使用wxWidgets进行跨平台GUI开发(附1)
跨平台·gui·开源框架·工程实战教程
南岩亦凛汀23 天前
在Linux下使用wxWidgets进行跨平台GUI开发(二)
跨平台·gui·开源框架
南岩亦凛汀1 个月前
在Linux下使用wxWidgets进行跨平台GUI开发
c++·跨平台·gui·开源框架·工程实战教程
诗仙&李白1 个月前
lnnovationHubTool,用prism+WPF编写的MVVM模式的快速上位机软件开发框架平台
wpf·mvvm·prism·上位机软件开发框架平台