带定时器的C#控制台基本用户界面

带定时器的C#控制台基本用户界面,c#,timer,console-application,C#,Timer,Console Application,我正在制作一个基本的控制台应用程序(不幸的是,这是必须的),它显示时间和一个用户可以输入选项的非常基本的界面。当他们这样做时,UI会更新相关的新数据以显示,并允许进一步输入等等。一直以来,顶部都显示有秒的实时时钟 我所做的基本上是每秒重新绘制整个UI,并尝试倾听用户输入,指导下次如何绘制UI。如何在重新绘制UI时管理用户输入?有更好的办法吗 以下是整个(非常精简的)程序: 目前我有Console.ReadLine()在定时器启动之后,如果我没有启动,那么应用程序会立即打开和关闭。另外两个Cons

我正在制作一个基本的控制台应用程序(不幸的是,这是必须的),它显示时间和一个用户可以输入选项的非常基本的界面。当他们这样做时,UI会更新相关的新数据以显示,并允许进一步输入等等。一直以来,顶部都显示有秒的实时时钟

我所做的基本上是每秒重新绘制整个UI,并尝试倾听用户输入,指导下次如何绘制UI。如何在重新绘制UI时管理用户输入?有更好的办法吗

以下是整个(非常精简的)程序:


目前我有
Console.ReadLine()
在定时器启动之后,如果我没有启动,那么应用程序会立即打开和关闭。另外两个
Console.ReadLine()被完全忽略了,但在这些点上,我希望记录用户输入。

似乎我考虑过了,@elgonzo为我指出了正确的方向。我现在只在一行上显示计时器,并在需要时清除其他行。我有一个基本的工作示例,我将扩展我的主程序。它似乎完全符合我的需要(显然缺少验证和功能),请评论是否可以改进:

using System;
using System.Diagnostics;
using System.Timers;

namespace TimerTest
{
    class Program
    {
        static bool appOpen = true;
        static bool useStartUI = false;
        static string currentInput = "";
        static Timer aTimer = new System.Timers.Timer();
        static void Main(string[] args)
        {
            Console.WriteLine("Main - " + DateTime.Now.ToString("HH:mm:ss tt"));
            aTimer.Elapsed += new ElapsedEventHandler(DrawClock);
            aTimer.Interval = 1000;
            aTimer.Enabled = true;

            while (appOpen) // to keep the application running and give a possible exit - will expand this
            {
                DrawUI();
            }
        }
        #region UI region
        static void DrawClock(object source, ElapsedEventArgs e)
        {
            Console.SetCursorPosition(0, 0);
            Console.WriteLine("DrawClock - " + DateTime.Now.ToString("HH:mm:ss     tt"));
            Console.SetCursorPosition(0, 7);
        }
        static void DrawUI()
        {
            Debug.WriteLine("currentInput = " + currentInput);
            if (currentInput == "A" || currentInput == "a")
            {
                useStartUI = true;
            }
            else if (currentInput == "B" || currentInput == "b")
            {
                useStartUI = false;
            }
            if (useStartUI)
            {
                DisplayStartUI();
            }
            else
            {
                DisplayCurrentUI();
            }
        }
        private static void DisplayStartUI()
        {
            CleanUI();
            Console.WriteLine("DisplayStartUI");
            Console.WriteLine("Press 'B' to switch to CurrentUI");

            ConsoleKeyInfo keyPressed = Console.ReadKey();
            Console.SetCursorPosition(0, 7);
            Console.WriteLine("You pressed {0}", keyPressed.KeyChar);
            currentInput = keyPressed.KeyChar.ToString();
            if (keyPressed.KeyChar.ToString() == "b")
            {
                DrawUI();
            }
        }
        private static void DisplayCurrentUI()
        {
            CleanUI();
            Console.WriteLine("DisplayCurrentUI");
            Console.WriteLine("Press 'A' to switch to StartUI");
            ConsoleKeyInfo keyPressed = Console.ReadKey();
            Console.SetCursorPosition(0, 7);
            Console.WriteLine("You pressed {0}", keyPressed.KeyChar);
            currentInput = keyPressed.KeyChar.ToString();
            if (keyPressed.KeyChar.ToString() == "a")
            {
                DrawUI();
            }
        }
        #endregion
        #region functional methods
        public static void CleanUI()
        {
            Console.SetCursorPosition(0, 5);
            ClearCurrentConsoleLine();
            Console.SetCursorPosition(0, 6);
            ClearCurrentConsoleLine();
            Console.SetCursorPosition(0, 7);
            ClearCurrentConsoleLine();
            Console.SetCursorPosition(0, 5);
        }
        public static void ClearCurrentConsoleLine()
        {
            int currentLineCursor = Console.CursorTop;
            Console.SetCursorPosition(0, Console.CursorTop);
            Console.Write(new string(' ', Console.WindowWidth));
            Console.SetCursorPosition(0, currentLineCursor);
        }
        #endregion
    }
}

看来我想得太多了,@elgonzo为我指明了正确的方向。我现在只在一行上显示计时器,并在需要时清除其他行。我有一个基本的工作示例,我将扩展我的主程序。它似乎完全符合我的需要(显然缺少验证和功能),请评论是否可以改进:

using System;
using System.Diagnostics;
using System.Timers;

namespace TimerTest
{
    class Program
    {
        static bool appOpen = true;
        static bool useStartUI = false;
        static string currentInput = "";
        static Timer aTimer = new System.Timers.Timer();
        static void Main(string[] args)
        {
            Console.WriteLine("Main - " + DateTime.Now.ToString("HH:mm:ss tt"));
            aTimer.Elapsed += new ElapsedEventHandler(DrawClock);
            aTimer.Interval = 1000;
            aTimer.Enabled = true;

            while (appOpen) // to keep the application running and give a possible exit - will expand this
            {
                DrawUI();
            }
        }
        #region UI region
        static void DrawClock(object source, ElapsedEventArgs e)
        {
            Console.SetCursorPosition(0, 0);
            Console.WriteLine("DrawClock - " + DateTime.Now.ToString("HH:mm:ss     tt"));
            Console.SetCursorPosition(0, 7);
        }
        static void DrawUI()
        {
            Debug.WriteLine("currentInput = " + currentInput);
            if (currentInput == "A" || currentInput == "a")
            {
                useStartUI = true;
            }
            else if (currentInput == "B" || currentInput == "b")
            {
                useStartUI = false;
            }
            if (useStartUI)
            {
                DisplayStartUI();
            }
            else
            {
                DisplayCurrentUI();
            }
        }
        private static void DisplayStartUI()
        {
            CleanUI();
            Console.WriteLine("DisplayStartUI");
            Console.WriteLine("Press 'B' to switch to CurrentUI");

            ConsoleKeyInfo keyPressed = Console.ReadKey();
            Console.SetCursorPosition(0, 7);
            Console.WriteLine("You pressed {0}", keyPressed.KeyChar);
            currentInput = keyPressed.KeyChar.ToString();
            if (keyPressed.KeyChar.ToString() == "b")
            {
                DrawUI();
            }
        }
        private static void DisplayCurrentUI()
        {
            CleanUI();
            Console.WriteLine("DisplayCurrentUI");
            Console.WriteLine("Press 'A' to switch to StartUI");
            ConsoleKeyInfo keyPressed = Console.ReadKey();
            Console.SetCursorPosition(0, 7);
            Console.WriteLine("You pressed {0}", keyPressed.KeyChar);
            currentInput = keyPressed.KeyChar.ToString();
            if (keyPressed.KeyChar.ToString() == "a")
            {
                DrawUI();
            }
        }
        #endregion
        #region functional methods
        public static void CleanUI()
        {
            Console.SetCursorPosition(0, 5);
            ClearCurrentConsoleLine();
            Console.SetCursorPosition(0, 6);
            ClearCurrentConsoleLine();
            Console.SetCursorPosition(0, 7);
            ClearCurrentConsoleLine();
            Console.SetCursorPosition(0, 5);
        }
        public static void ClearCurrentConsoleLine()
        {
            int currentLineCursor = Console.CursorTop;
            Console.SetCursorPosition(0, Console.CursorTop);
            Console.Write(new string(' ', Console.WindowWidth));
            Console.SetCursorPosition(0, currentLineCursor);
        }
        #endregion
    }
}

查看
控制台
类的文档。它有属性和方法来获取和设置光标位置,获取控制台窗口的(当前)大小等。利用这些功能和信息,您只需编写控制台UI中需要更新的区域,应该会变得很容易。此外,如果不希望程序退出,则需要防止主方法退出(退出主方法意味着程序将结束)。如何阻止主方法退出是您的选择,最终取决于应用程序逻辑的要求(旁注:退出main方法并不总是意味着程序将退出。更准确地说,程序将在最后一个前台线程结束时退出。现在,在这里的控制台应用程序中,您只有一个前台线程-运行main方法的线程。因此,退出main方法将结束程序中唯一的前台线程如果您要编写一个使用多个前台线程的程序,则在每个前台线程结束之前,程序不会退出。不,我不是说您应该使用多个前台线程…)好的,我想我明白了。所以可能允许时钟的计时器存在于另一个线程上,并让它只更新控制台窗口的顶部(比如前3行)。对UI的任何其他更新都是“静态”完成的。唯一的问题是当console.Clear()被触发,时钟用户界面将需要一段时间来更新,这意味着用户界面可能看起来有点“笨重”?如果您只想更新控制台的一小部分,为什么要清除整个控制台?此外,这种方法可能会受到不期望的副作用:如果用户正在键入某些文本/数据/任何内容,并且就在那一刻呢计时器启动,您的程序开始移动光标或清除控制台,试图在用户键入时更新UI?您必须彻底思考应用程序场景和逻辑,确定冲突场景,如我在此处概述的场景,并验证和测试它们…最后,取决于您想要的/need当用户可以输入数据/文本/决定您是否可以使用Console.ReadLine的任何内容时,您的应用程序将执行此操作。在最坏的情况下,您可能必须编写自己的输入例程,该例程将仅依赖Console.ReadKey来实现用户输入的健壮行为,同时允许用户界面更新而不中断/affecting用户输入…查看
控制台
类的文档。它有属性和方法来获取和设置光标位置,以获取(当前)控制台窗口的大小等。利用这些功能和信息,您应该可以很容易地只写入控制台UI中需要更新的区域。此外,如果您不希望程序退出,则需要防止主方法退出(退出主方法意味着程序将结束)。您将/能够如何阻止主方法退出是您的选择,最终取决于您的应用程序逻辑的要求(旁注:退出main方法并不总是意味着程序将退出。更准确地说,程序将在最后一个前台线程结束时退出。现在,在这里的控制台应用程序中,您只有一个前台线程-运行main方法的线程。因此,退出main方法将结束程序中唯一的前台线程如果您要编写一个使用多个前台线程的程序,则在每个前台线程结束之前,程序不会退出。不,我不是说您应该使用多个前台线程…)好的,我想我明白了。所以可能允许时钟的计时器存在于另一个线程上,并让它只更新控制台窗口的顶部(比如前3行)。对UI的任何其他更新都是“静态”完成的。唯一的问题是当console.Clear()被触发,时钟用户界面将需要一段时间来更新,这意味着用户界面可能看起来有点“笨重”?如果只想更新控制台的一小部分,为什么要清除整个控制台?此外,这种方法可能会受到不必要的影响