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.
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: