ฟังการกดปุ่มในแอปคอนโซล NET


224

ฉันจะเรียกใช้แอปพลิเคชั่นคอนโซลของฉันต่อไปได้อย่างไรจนกว่าจะกดปุ่ม (เช่นEscกด)

ฉันสมมติว่ามันวนรอบสักครู่ ฉันไม่ชอบReadKeyที่จะบล็อกการดำเนินการและขอคีย์แทนที่จะทำต่อและฟังการกดปุ่ม

สิ่งนี้สามารถทำได้?

คำตอบ:


343

ใช้Console.KeyAvailableเพื่อให้คุณโทรReadKeyเมื่อคุณรู้ว่ามันจะไม่ปิดกั้น:

Console.WriteLine("Press ESC to stop");
do {
    while (! Console.KeyAvailable) {
        // Do something
   }       
} while (Console.ReadKey(true).Key != ConsoleKey.Escape);

6
แต่ถ้าคุณทำเช่นนี้แทน readLine () คุณจะสูญเสียคุณสมบัติที่ยอดเยี่ยมในการจำประวัติโดยกดปุ่ม "up"
v.oddou

3
@ v.oddou หลังจากบรรทัดสุดท้ายเพียงเพิ่มของคุณConsole.ReadLine()และคุณตั้งไว้ไม่?
Gaffi

1
การวนซ้ำนี้จะใช้ CPU / RAM จำนวนมากหรือไม่ ถ้าไม่เป็นเช่นนั้นได้อย่างไร
โซเฟีย

78

คุณสามารถเปลี่ยนวิธีการของคุณได้เล็กน้อย - ใช้Console.ReadKey()เพื่อหยุดแอพของคุณ แต่ทำงานในหัวข้อพื้นหลัง:

static void Main(string[] args)
{
    var myWorker = new MyWorker();
    myWorker.DoStuff();
    Console.WriteLine("Press any key to stop...");
    Console.ReadKey();
}

ในmyWorker.DoStuff()ฟังก์ชั่นคุณจะต้องเรียกใช้ฟังก์ชันอื่นบนเธรดพื้นหลัง (การใช้Action<>()หรือFunc<>()เป็นวิธีที่ทำได้ง่าย) จากนั้นส่งคืนทันที


60

วิธีที่สั้นที่สุด:

Console.WriteLine("Press ESC to stop");

while (!(Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape))
{
    // do something
}

Console.ReadKey()เป็นฟังก์ชั่นการบล็อคมันจะหยุดการทำงานของโปรแกรมและรอการกดปุ่ม แต่ต้องขอบคุณการตรวจสอบConsole.KeyAvailableครั้งแรกwhileลูปจะไม่ถูกบล็อก แต่จะทำงานจนกว่าEscจะกด


ตัวอย่างที่ง่ายที่สุดจนถึงตอนนี้ ขอบคุณ.
joelc

18

จากวิดีโอแช่งการสร้างแอพพลิเคชันคอนโซล NET ใน C # โดย Jason Roberts ที่http://www.pluralsight.com

เราสามารถทำตามเพื่อให้มีกระบวนการทำงานหลายขั้นตอน

  static void Main(string[] args)
    {
        Console.CancelKeyPress += (sender, e) =>
        {

            Console.WriteLine("Exiting...");
            Environment.Exit(0);
        };

        Console.WriteLine("Press ESC to Exit");

        var taskKeys = new Task(ReadKeys);
        var taskProcessFiles = new Task(ProcessFiles);

        taskKeys.Start();
        taskProcessFiles.Start();

        var tasks = new[] { taskKeys };
        Task.WaitAll(tasks);
    }

    private static void ProcessFiles()
    {
        var files = Enumerable.Range(1, 100).Select(n => "File" + n + ".txt");

        var taskBusy = new Task(BusyIndicator);
        taskBusy.Start();

        foreach (var file in files)
        {
            Thread.Sleep(1000);
            Console.WriteLine("Procesing file {0}", file);
        }
    }

    private static void BusyIndicator()
    {
        var busy = new ConsoleBusyIndicator();
        busy.UpdateProgress();
    }

    private static void ReadKeys()
    {
        ConsoleKeyInfo key = new ConsoleKeyInfo();

        while (!Console.KeyAvailable && key.Key != ConsoleKey.Escape)
        {

            key = Console.ReadKey(true);

            switch (key.Key)
            {
                case ConsoleKey.UpArrow:
                    Console.WriteLine("UpArrow was pressed");
                    break;
                case ConsoleKey.DownArrow:
                    Console.WriteLine("DownArrow was pressed");
                    break;

                case ConsoleKey.RightArrow:
                    Console.WriteLine("RightArrow was pressed");
                    break;

                case ConsoleKey.LeftArrow:
                    Console.WriteLine("LeftArrow was pressed");
                    break;

                case ConsoleKey.Escape:
                    break;

                default:
                    if (Console.CapsLock && Console.NumberLock)
                    {
                        Console.WriteLine(key.KeyChar);
                    }
                    break;
            }
        }
    }
}

internal class ConsoleBusyIndicator
{
    int _currentBusySymbol;

    public char[] BusySymbols { get; set; }

    public ConsoleBusyIndicator()
    {
        BusySymbols = new[] { '|', '/', '-', '\\' };
    }
    public void UpdateProgress()
    {
        while (true)
        {
            Thread.Sleep(100);
            var originalX = Console.CursorLeft;
            var originalY = Console.CursorTop;

            Console.Write(BusySymbols[_currentBusySymbol]);

            _currentBusySymbol++;

            if (_currentBusySymbol == BusySymbols.Length)
            {
                _currentBusySymbol = 0;
            }

            Console.SetCursorPosition(originalX, originalY);
        }
    }

2
คุณช่วยอธิบายเล็กน้อยเกี่ยวกับวิธีการที่คุณมีฉันพยายามทำเช่นเดียวกันกับโปรแกรมคอนโซลของฉันคำอธิบายสำหรับรหัสจะดีมาก
nayef harb

@ nayefharb ตั้งค่างานต่างๆเริ่มต้นแล้ว WaitAll จะรอจนกว่าพวกเขาจะกลับมาทั้งหมด ดังนั้นแต่ละงานจะไม่กลับมาและจะดำเนินต่อไปเรื่อย ๆ จนกว่าจะมีการกดปุ่ม CancelKeyPress
wonea

Console.CursorVisible = false;จะดีที่สุดเพื่อหลีกเลี่ยงเคอร์เซอร์ที่กะพริบบกพร่อง เพิ่มบรรทัดนี้เป็นบรรทัดแรกในstatic void Main(string[] args)บล็อก
Hitesh

12

นี่คือวิธีการที่คุณจะทำบางสิ่งบนเธรดอื่นและเริ่มฟังคีย์ที่กดในเธรดอื่น และคอนโซลจะหยุดการประมวลผลเมื่อสิ้นสุดกระบวนการที่แท้จริงของคุณหรือผู้ใช้กระบวนการยุติโดยการกดEscคีย์

class SplitAnalyser
{
    public static bool stopProcessor = false;
    public static bool Terminate = false;

    static void Main(string[] args)
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("Split Analyser starts");
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("Press Esc to quit.....");
        Thread MainThread = new Thread(new ThreadStart(startProcess));
        Thread ConsoleKeyListener = new Thread(new ThreadStart(ListerKeyBoardEvent));
        MainThread.Name = "Processor";
        ConsoleKeyListener.Name = "KeyListener";
        MainThread.Start();
        ConsoleKeyListener.Start();

        while (true) 
        {
            if (Terminate)
            {
                Console.WriteLine("Terminating Process...");
                MainThread.Abort();
                ConsoleKeyListener.Abort();
                Thread.Sleep(2000);
                Thread.CurrentThread.Abort();
                return;
            }

            if (stopProcessor)
            {
                Console.WriteLine("Ending Process...");
                MainThread.Abort();
                ConsoleKeyListener.Abort();
                Thread.Sleep(2000);
                Thread.CurrentThread.Abort();
                return;
            }
        } 
    }

    public static void ListerKeyBoardEvent()
    {
        do
        {
            if (Console.ReadKey(true).Key == ConsoleKey.Escape)
            {
                Terminate = true;
            }
        } while (true); 
    }

    public static void startProcess()
    {
        int i = 0;
        while (true)
        {
            if (!stopProcessor && !Terminate)
            {
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("Processing...." + i++);
                Thread.Sleep(3000);
            }
            if(i==10)
                stopProcessor = true;

        }
    }

}

5

หากคุณใช้ Visual Studio คุณสามารถใช้ "เริ่มต้นโดยไม่มีการดีบัก" ในเมนูดีบั๊ก

มันจะเขียน "กดปุ่มใดก็ได้เพื่อดำเนินการต่อ.." ไปยังคอนโซลสำหรับคุณเมื่อเสร็จสิ้นแอปพลิเคชันและจะเปิดคอนโซลให้คุณจนกว่าจะมีการกดปุ่ม


3

การพูดถึงกรณีที่คำตอบอื่น ๆ บางคำตอบไม่ได้ดี:

  • Responsive : การเรียกใช้งานโค้ดการจัดการคีย์โดยตรงโดยตรง หลีกเลี่ยงความวุ่นวายของการลงคะแนนเลือกตั้งหรือการปิดกั้นความล่าช้า
  • optionality : keypress ทั่วโลกเลือกใน ; มิฉะนั้นแอปควรออกตามปกติ
  • การแยกข้อกังวล : รหัสการฟังที่น้อยลง ทำงานเป็นอิสระจากรหัสแอปคอนโซลปกติ

หลายของการแก้ปัญหาในหน้านี้ที่เกี่ยวข้องกับการเลือกตั้งหรือการปิดกั้นConsole.KeyAvailable Console.ReadKeyในขณะที่มันเป็นความจริงที่. NET Consoleไม่ร่วมมือกันมากที่นี่คุณสามารถใช้Task.Runเพื่อไปสู่Asyncโหมดการฟังที่ทันสมัยยิ่งขึ้น

ปัญหาหลักที่ต้องระวังคือโดยปกติแล้วเธรดคอนโซลของคุณจะไม่ได้รับการตั้งค่าสำหรับAsyncการดำเนินการ - ซึ่งหมายความว่าเมื่อคุณหลุดออกมาจากด้านล่างของmainฟังก์ชั่นแทนที่จะรอความAsyncสำเร็จ AppDoman ของคุณและกระบวนการจะสิ้นสุดลง . วิธีที่เหมาะสมในการจัดการเรื่องนี้คือการใช้AsyncContextของ Stephen Cleary เพื่อสร้างAsyncการสนับสนุนอย่างเต็มที่ในโปรแกรมคอนโซลแบบเธรดเดี่ยวของคุณ แต่สำหรับกรณีที่ง่ายกว่าเช่นรอปุ่มกดการติดตั้งแทรมโพลีนแบบเต็มอาจเกินความจำเป็น

ตัวอย่างด้านล่างนี้สำหรับโปรแกรมคอนโซลที่ใช้ในไฟล์ชุดซ้ำบางประเภท ในกรณีนี้เมื่อโปรแกรมทำงานกับโปรแกรมปกติแล้วควรออกโดยไม่ต้องใช้ปุ่มกดจากนั้นเราอนุญาตให้กดปุ่มเสริมเพื่อป้องกันไม่ให้แอปออกจากโปรแกรม เราสามารถหยุดวงจรชั่วคราวเพื่อตรวจสอบสิ่งต่าง ๆ อาจกลับมาทำงานต่อหรือใช้การหยุดชั่วคราวเป็น 'จุดควบคุม' ที่เป็นที่รู้จักซึ่งจะแบ่งไฟล์แบตช์ออกอย่างหมดจด

static void Main(String[] args)
{
    Console.WriteLine("Press any key to prevent exit...");
    var tHold = Task.Run(() => Console.ReadKey(true));

    // ... do your console app activity ...

    if (tHold.IsCompleted)
    {
#if false   // For the 'hold' state, you can simply halt forever...
        Console.WriteLine("Holding.");
        Thread.Sleep(Timeout.Infinite);
#else                            // ...or allow continuing to exit
        while (Console.KeyAvailable)
            Console.ReadKey(true);     // flush/consume any extras
        Console.WriteLine("Holding. Press 'Esc' to exit.");
        while (Console.ReadKey(true).Key != ConsoleKey.Escape)
            ;
#endif
    }
}

1

ด้วยโค้ดด้านล่างคุณสามารถฟังการกด SpaceBar ในระหว่างการดำเนินการของคอนโซลและหยุดชั่วคราวจนกว่าจะมีการกดคีย์อื่นและฟัง EscapeKey สำหรับการวนลูปหลัก

static ConsoleKeyInfo cki = new ConsoleKeyInfo();


while(true) {
      if (WaitOrBreak()) break;
      //your main code
}

 private static bool WaitOrBreak(){
        if (Console.KeyAvailable) cki = Console.ReadKey(true);
        if (cki.Key == ConsoleKey.Spacebar)
        {
            Console.Write("waiting..");
            while (Console.KeyAvailable == false)
            {
                Thread.Sleep(250);Console.Write(".");
            }
            Console.WriteLine();
            Console.ReadKey(true);
            cki = new ConsoleKeyInfo();
        }
        if (cki.Key == ConsoleKey.Escape) return true;
        return false;
    }

-1

จากประสบการณ์ของฉันในแอปคอนโซลวิธีที่ง่ายที่สุดในการอ่านคีย์สุดท้ายที่กดมีดังนี้ (ตัวอย่างด้วยปุ่มลูกศร):

ConsoleKey readKey = Console.ReadKey ().Key;
if (readKey == ConsoleKey.LeftArrow) {
    <Method1> ();  //Do something
} else if (readKey == ConsoleKey.RightArrow) {
    <Method2> ();  //Do something
}

ฉันใช้เพื่อหลีกเลี่ยงการวนซ้ำแทนฉันเขียนรหัสข้างต้นภายในวิธีการและฉันเรียกมันในตอนท้ายของทั้ง "วิธีที่ 1" และ "วิธีที่ 2" ดังนั้นหลังจากดำเนินการ "Method1" หรือ "Method2", Console.ReadKey () .Key พร้อมที่จะอ่านกุญแจอีกครั้ง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.