Search code examples
c#.net-coreconsole

Sleep a function without sleeping entire console


So I'm trying to add a clock widget to my C# Console program. Except I'm too dumb to understand how to update it while being able to interact with the app.

Here is the code:

using System.Text;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using Spectre.Console;


namespace Awesome_C__Project
{
    internal class Program
    {
        public static List<Option> options;
        private static int lastSelection = -1; // Where we write the ">".
        static void Main(string[] args)
        {
            
            Encoding UTF8 = Encoding.UTF8;
            Console.OutputEncoding = UTF8;

            // Create options that you want your menu to have

            options = new List<Option>
            {
                new Option("  ๐Ÿก Home      ", () => HomePageLink()),
                new Option("  ๐Ÿ“ฉ Request   ", () => RequestPageLink()),      
                new Option("  โŒจ๏ธ Typing    ", () => TypingGamePageLink()),
                new Option("  โš™๏ธ Configure ", () => ConfigurePageLink()),
                new Option("  ๐Ÿ’ซ Updates   ", () => UpdatePageLink()),
                new Option("  โŒ Exit      ", () => Environment.Exit(0)),

            };

            // Set the default index of the selected item to be the first
            int index = 0;

            // Write the menu out
            WriteMenu(options, options[index]);


            // Store key info in here
            ConsoleKeyInfo keyinfo;
            do
            {
                keyinfo = Console.ReadKey();

                // Handle each key input (down arrow will write the menu again with a different selected item)
                switch (keyinfo.Key)
                {
                    case ConsoleKey.DownArrow:
                        if (index + 1 < options.Count)
                        {
                            index++;
                        }
                        break;
                    case ConsoleKey.UpArrow:
                        if (index > 0)
                        {
                            index--;
                        }
                        break;
                    case ConsoleKey.Enter:
                        Console.Clear(); // The only place where we clear.
                        options[index].Selected.Invoke();
                        lastSelection = index;
                        break;
                }
                WriteMenu(options, options[index]); // Write the menu in all the cases.
            }
            while (keyinfo.Key != ConsoleKey.X);

            Console.ReadKey();
        }
        // Default action of all the options. You can create more methods

        static void WriteAt(int left, int top, string text)
        {
            Console.CursorLeft = left;
            Console.CursorTop = top;
            Console.Write(text);
        }

        static void WriteMenu(List<Option> options, Option selectedOption)
        {
            // Don't clear here since we want to preserve the output the selected menu item.
            // Instead, let's use our new method WriteAt to write at a specific place.
            for (int i = 0; i < options.Count; i++)
            {
                Option option = options[i];
                if (option == selectedOption)
                {
                    Console.Write("\u001b[48;2;50;50;50m");
                }
                else
                {
                    Console.Write("\u001b[48;2;30;30;30m");
                }
                WriteAt(1, i, i == lastSelection ? " >" : "  ");
                Console.Write(option.Name);
            }
        }

        static void HomePageLink()
        {
            WriteAt(20, 0, "\u001b[48;2;12;12;12mHome");
            WriteAt(20, 1, "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
            WriteAt(20, 2, "");
            WriteAt(20, 25, "Monsteraยฎ Development Studio - 2023 Copyrights Reserved");
            
        }

        static void RenderClock()
        {
            DateTime DateNow = DateTime.Now;
            Console.CursorLeft = 20;
            Console.CursorTop = 3;

            Console.WriteLine("Today is " + DateNow.ToString("MMMM dd") + " of " + DateNow.ToString("yyyy") + " at " + DateTime.Now.ToShortTimeString().ToString() + ".");
            System.Threading.Thread.Sleep(1000);
            RenderClock();
        }
        static void RequestPageLink()
        {
            WriteAt(20, 0, "\u001b[48;2;12;12;12mRequest");
            WriteAt(20, 1, "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
            WriteAt(20, 3, "Nothing here yet, sorry.");
        }

        static void TypingGamePageLink()
        {
            WriteAt(20, 0, "\u001b[48;2;12;12;12mTyping Competition");
            WriteAt(20, 1, "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
            WriteAt(20, 3, "Nothing here yet, sorry.");
        }

        static void UpdatePageLink()
        {
            WriteAt(20, 0, "\u001b[48;2;12;12;12mUpdates");
            WriteAt(20, 1, "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
            WriteAt(20, 3, "Nothing here yet, sorry.");
        }

        static void ConfigurePageLink()
        {
            WriteAt(20, 0, "\u001b[48;2;12;12;12mConfigure");
            WriteAt(20, 1, "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
            WriteAt(20, 3, "Nothing here yet, sorry.");
        }
    }

    public class Option
    {
        public string Name { get; }
        public Action Selected { get; }

        public Option(string name, Action selected)
        {
            Name = name;
            Selected = selected;
        }
    }
   
}

Basically, I want to update the clock while being still able to interact with the program. Technically you could pause the clock function and let the other functions do their job, but I'm very new to C# and don't quite know how to do that.


Solution

  • You might want to run it in parallel, for example with Task.Run:

    static void RenderClock()
    {
        while (true)
        {
            DateTime DateNow = DateTime.Now;
            Console.CursorLeft = 20;
            Console.CursorTop = 3;
    
            Console.WriteLine("Today is " + DateNow.ToString("MMMM dd") + " of " + DateNow.ToString("yyyy") + " at " + DateTime.Now.ToShortTimeString().ToString() + ".");
            System.Threading.Thread.Sleep(1000);
        }
    }
    

    And somewhere at app start:

    Task.Run(RenderClock);
    

    Note that you might need to syncronize writes to the console (i.e. depending on what an how is written to console you might need to perform this operation "atomically"), for example using lock's.

    Better approach would be switching to timer.

    Read more: