在表单中模拟阻止控制台键盘输入

时间:2015-03-31 13:30:47

标签: c# winforms keyboard console emulation

我需要在C#中编写一个控制台模拟器。我想用可以全屏工作的东西替换System.Console。我的方法是制作一个没有边框的最大化表格。

我面临的一个挑战是如何将事件驱动的非阻塞键盘输入转换为阻止键盘输入,或者更具体地说,如何在Windows窗体中实现等效的Console.ReadKey()

1 个答案:

答案 0 :(得分:1)

我不认为你真的想要完全阻止控制台窗口。您需要的更像是PowerShell或标准控制台窗口。我的意思是,您是否反对能够移动窗口,从控制台复制数据等等是值得怀疑的。


因此,进一步描述的控制台基于TextBox。为什么不在Form本身? - 因为多行,只读TextBox已经提供了很多设施,可以简化我们的ConsoleAppendText,文本选择,复制......)。

1。想法和界面:

正如您在问题中已经提到的,WinForms与控制台应用程序的模型完全不同,具有消息队列和基于事件的处理。因此,要从事件中获取数据并防止形式变得无法响应,我们无法阻止此事件循环。为了实现这样的行为,我们将在Task.Run的不同线程上运行基于控制台的代码,并取消阻止依赖于输入的" console"从主窗体线程中的事件调用。应用程序的基石将是接下来的两个接口:

public interface IConsole
{
    ConsoleKeyInfo ReadKey();

    void WriteLine(String line);
}

public interface IConsoleContext
{
    void Run(Action<IConsole> main);
}

首先是Console本身,其方法反映了标准控制台功能。第二个将在另一个线程上运行我们的控制台相关代码(由action表示),为此操作提供一些控制台对象。

我们的IConsoleContext实施将TextBoxConsoleContext,其私有嵌套类TextBoxConsoleIConsole

2。主表单代码

以下是我们将在表单中用于演示控制台的代码。假设我们有textBox textBox_Console

private void Form1_Load(object sender, EventArgs e)
{
    var consoleContext = new TextBoxConsoleContext(this.textBox_Console);

    consoleContext.Run((console) =>
    {
        console.WriteLine("Welcome to the TextBox console");
        console.WriteLine("Press any key:");
        console.ReadKey();

        ConsoleKeyInfo keyInfo = new ConsoleKeyInfo();


        console.WriteLine("Press y to continue: ");
        do
        {
            keyInfo = console.ReadKey();

            if (keyInfo.KeyChar == 'y')
                break;

            console.WriteLine("You have entered another key, please enter y to continue:");
        }
        while (true);

        console.WriteLine("Thank you for your cooperation.");
    });
}

3。 TextBoxConsoleContext本身:

public class TextBoxConsoleContext : IConsoleContext
{
    #region Nested types

    private class TextBoxConsole : IConsole
    {
        #region Fields

        private TextBoxConsoleContext parent;

        #endregion


        #region Constructors

        public TextBoxConsole(TextBoxConsoleContext parent)
        {
            if (parent == null)
                throw new ArgumentNullException("parent");

            this.parent = parent;
        }

        #endregion


        #region IConsole implementation

        public ConsoleKeyInfo ReadKey()
        {
            var key = this.parent.m_Queue.Dequeue();
            this.WriteLine(key.KeyChar.ToString());
            return key;
        }

        public void WriteLine(string line)
        {
            Action writeLine = () =>
                {
                    var textToAppend = String.Format("{0}{1}",
                        line,
                        Environment.NewLine);

                    this.parent.m_TextBox.AppendText(textToAppend);
                };

            this.parent.m_TextBox.Invoke(writeLine);
        }

        #endregion
    }

    #endregion


    #region Fields

    private TextBox m_TextBox;
    private OnRequestProducerConsumerQueue<ConsoleKeyInfo> m_Queue = new OnRequestProducerConsumerQueue<ConsoleKeyInfo>();

    private Boolean m_Shift;
    private Boolean m_Alt;
    private Boolean m_Ctrl;
    private ConsoleKey m_KeyInfo;

    #endregion


    #region Constructors

    public TextBoxConsoleContext(TextBox textBox)
    {
        if (textBox == null)
            throw new ArgumentNullException("textBox");


        this.m_TextBox = textBox;
        this.m_TextBox.ReadOnly = true;
        // Event handler that will read key down data before key press
        this.m_TextBox.KeyDown += (obj, e) =>
            {
                this.m_Shift = e.Modifiers.HasFlag(Keys.Shift);
                this.m_Alt = e.Modifiers.HasFlag(Keys.Alt);
                this.m_Ctrl = e.Modifiers.HasFlag(Keys.Control);

                if (!Enum.TryParse<ConsoleKey>(e.KeyCode.ToString(), out this.m_KeyInfo))
                {
                    this.m_KeyInfo = ConsoleKey.Escape;
                }                   
            };

        this.m_TextBox.KeyPress += (obj, e) =>
            {
                this.m_Queue.EnqueueIfRequired(new ConsoleKeyInfo(e.KeyChar, 
                    this.m_KeyInfo, 
                    this.m_Shift, 
                    this.m_Alt,
                    this.m_Ctrl));
            };
    }

    #endregion


    #region IConsoleContext implementation

    public void Run(Action<IConsole> main)
    {
        if (main == null)
            throw new ArgumentNullException("main");

        var console = new TextBoxConsole(this);

        Task.Run(() =>
            main(console));
    }

    #endregion
}

4。 OnRequestProducerConsumerQueue

编辑:我已经用这个更简单的类替换了初始队列类,该类使用具有Monitor调用的单个锁对象。此代码基于条件变量模式。

备注:我只是想替换我之前发布的以前滥用的同步结构。此版本不会改变行为中的任何内容,如果您需要实现EnqueueIfRequired方法,则此实现中的ReadLine将无用(但您可以添加一些谓词逻辑,允许使用后直到线终止符出现。)

public class OnRequestProducerConsumerQueue<T>
{
    private Queue<T> m_Items = new Queue<T>();
    private object m_lock = new Object();
    private Int32 m_NeedItems = 0;

    public void EnqueueIfRequired(T value)
    {
        lock (this.m_lock)
        {
            if (this.m_NeedItems == 0)
                return;

            this.m_Items.Enqueue(value);
            this.m_NeedItems--;

            Monitor.PulseAll(this.m_lock);
        }
    }

    public T Dequeue()
    {
        lock (this.m_lock)
        {
            this.m_NeedItems++;

            while (this.m_Items.Count < 1)
            {
                Monitor.Wait(this.m_lock);
            }

            return this.m_Items.Dequeue();
        }
    }
}