当前位置: 首页 > news >正文

如何在命令行中监听用户输入文本的改变?

这真是一个诡异的需求。为什么我需要在命令行中得知用户输入文字的改变啊!实际上我希望实现的是:在命令行中输入一段文字,然后不断地将这段文字发往其他地方。

本文将介绍如何监听用户在命令行中输入文本的改变。


在命令行中输入有三种不同的方法:

  • Console.Read()
    • 用户可以一直输入,在用户输入回车之前,此方法都会一直阻塞。而一旦用户输入了回车,你后面的 Console.Read 就不会一直阻塞了,直到把用户在这一行输入的文字全部读完。
  • Console.ReadKey()
    • 用户输入之前此方法会一直阻塞,用户只要按下任何一个键这个方法都会返回并得到用户按下的按键信息。
  • Console.ReadLine()
    • 用户可以一直输入,在用户输入回车之前,此方法都会一直阻塞。当用户输入了回车之后,此方法会返回用户在这一行输入的字符串。

从表面上来说,以上这三个方法都不能满足我们的需求,每一个方法都不能直接监听用户的输入文本改变。尤其是 Console.Read()Console.ReadLine() 方法,在用户输入回车之前,我们都得不到任何信息。看起来我们似乎只能通过 Console.ReadKey() 来完成我们的需求了。

但是,一旦我们使用了 Console.ReadKey(),我们将不能获得另外两个方法中的输入体验。例如,我们按下退格键(BackSpace)可以删除光标的前一个字符,按下删除键(Delete)可以删除光标的后一个字符,按下左右键可以移动光标到合适的文本上。

然而,不幸的是,除了这三个方法,我们还真的没有原生的方法来实现命令行的输入监听了。所以看样子我们需要自己来使用 Console.ReadKey() 实现用户输入文字的监听了。

我在 如何让 .NET Core 命令行程序接受密码的输入而不显示密码明文 - walterlv 一问中有说到如何在命令行中输入密码而不会显示明文。我们用到的就是此博客中所述的方法。

var builder = new StringBuilder();
while (true)
{
    var i = Console.ReadKey(true);

    if (i.Key == ConsoleKey.Enter)
    {
        Console.WriteLine();
        // 用户在这里输入了回车,于是我们需要结束输入了。
    }

    if (i.Key == ConsoleKey.Backspace)
    {
        if (builder.Length > 0)
        {
            Console.Write("\b \b");
            builder.Remove(builder.Length - 1, 1);
        }
    }
    else
    {
        builder.Append(i.KeyChar);
        Console.Write(i.KeyChar);
    }
}

然而实际上在使用此方法的时候并不符合预期,因为退格的时候我们得到了半个字:

我们得到了半个字

额外的,我们还不支持左右键移动光标,而且按住控制键的时候也会输入一个字符;这些都是我还没有处理的。

这就意味着我们使用 "\b \b" 来删除我们输入的字符的时候,有可能在一些字符的情况下我们需要删除两个字符宽度。

然而如何获取一个字的字符宽度呢?还是很复杂的。于是我很暴力地使用 OnChar函数的中文处理问题,退格键时,怎么处理-CSDN论坛 论坛中使用的方法直接通过编码范围判断中文的方式来推测字符宽度。如果你有更正统的方法,非常欢迎指导我。

简单起见,我写了一个类来封装输入文本改变。阅读以下代码,或者访问 Walterlv.CloudKeyboard/ConsoleLineReader.cs 阅读此类型的最新版本的代码。

using System;
using System.Text;

namespace Walterlv.Demo
{
    public sealed class ConsoleLineReader
    {
        public event EventHandler<ConsoleTextChangedEventArgs> TextChanged;

        public string ReadLine()
        {
            var builder = new StringBuilder();
            while (true)
            {
                var i = Console.ReadKey(true);

                if (i.Key == ConsoleKey.Enter)
                {
                    var line = builder.ToString();
                    OnTextChanged(line, i.Key);
                    Console.WriteLine();
                    return line;
                }

                if (i.Key == ConsoleKey.Backspace)
                {
                    if (builder.Length > 0)
                    {
                        var lastChar = builder[builder.Length - 1];
                        Console.Write(lastChar > 0xA0 ? "\b\b  \b\b" : "\b \b");
                        builder.Remove(builder.Length - 1, 1);
                    }
                }
                else
                {
                    builder.Append(i.KeyChar);
                    Console.Write(i.KeyChar);
                }

                OnTextChanged(builder.ToString(), i.Key);
            }
        }

        private void OnTextChanged(string line, ConsoleKey key)
        {
            TextChanged?.Invoke(this, new ConsoleTextChangedEventArgs(line, key));
        }
    }

    public class ConsoleTextChangedEventArgs : EventArgs
    {
        public ConsoleTextChangedEventArgs(string line, ConsoleKey consoleKey)
        {
            Line = line;
            ConsoleKey = consoleKey;
        }

        public string Line { get; }
        public ConsoleKey ConsoleKey { get; }
    }
}

那么使用的时候,则会简单很多:

var reader = new ConsoleLineReader();
reader.TextChanged += (sender, args) =>
{
    // 这里可以在用户每次输入的文本改变的时候执行。
};

while (true)
{
    // 我在这里循环执行,于是即便用户按了回车,也会继续输入。
    reader.ReadLine();
}

参考资料

  • StreamReader.cs
  • windows - How to backspace the characters in the cmd buffer? - Super User
  • Console.KeyAvailable Property (System) - Microsoft Docs
  • OnChar函数的中文处理问题,退格键时,怎么处理-CSDN论坛

我的博客会首发于 https://walterlv.com/,而 CSDN 和博客园仅从其中摘选发布,而且一旦发布了就不再更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://blog.csdn.net/wpwalter),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • 使用 Xamarin 开发 iOS 键盘扩展(含网络访问)
  • 使用 Xamarin 开发 iOS 应用中需要注意的若干个问题
  • .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例
  • 使用 Xamarin 在 iOS 真机上部署应用进行调试
  • 在 Snoop 中使用 PowerShell 脚本进行更高级的 UI 调试
  • WPF 支持的多线程 UI 并不是线程安全的
  • ReSharper 在 C 盘占用了太多空间了,本文告诉你如何安全地删除或转移这些文件
  • WPF 让普通 CLR 属性支持 XAML 绑定(非依赖属性),这样 MarkupExtension 中定义的属性也能使用绑定了
  • git 的合并原理(递归三路合并算法)
  • git 合并策略
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • 使用一句 git 命令将仓库的改动推送到所有的远端
  • 将 svn 仓库迁移到 git 仓库
  • 使用 Visual Studio 调试多进程的程序
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • Angular 4.x 动态创建组件
  • Angular Elements 及其运作原理
  • C# 免费离线人脸识别 2.0 Demo
  • express如何解决request entity too large问题
  • HTTP那些事
  • iOS | NSProxy
  • js操作时间(持续更新)
  • Node 版本管理
  • Octave 入门
  • Quartz初级教程
  • React 快速上手 - 06 容器组件、展示组件、操作组件
  • SQL 难点解决:记录的引用
  • windows下使用nginx调试简介
  • 和 || 运算
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 如何合理的规划jvm性能调优
  • 实战|智能家居行业移动应用性能分析
  • 使用Envoy 作Sidecar Proxy的微服务模式-4.Prometheus的指标收集
  • 源码安装memcached和php memcache扩展
  • 自制字幕遮挡器
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • (C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切
  • (cos^2 X)的定积分,求积分 ∫sin^2(x) dx
  • (Demo分享)利用原生JavaScript-随机数-实现做一个烟花案例
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (转)C#调用WebService 基础
  • (转)c++ std::pair 与 std::make
  • (转)创业的注意事项
  • .FileZilla的使用和主动模式被动模式介绍
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .Net(C#)常用转换byte转uint32、byte转float等
  • @Data注解的作用
  • @media screen 针对不同移动设备