I am writing a server app and I want it to be console based. I need the user to be able to input different commands, but at the same time there is a possibility that somethi
There's no perfect way of accomplishing this, I think. What telnet does (at least the last version I used) was not print any input (just read the keystrokes) and simply print the output as it arrives. The alternative is to store any data that needs to be output to the console in a buffer, and only print it once the user has finished entering their command. (You could even timestamp the output, to make it more obvious.) I really can't see any better alternative here - you're inevitably going to run into problems using a synchronous I/O interface (i.e. the command line) together with asynchronous operations in the backend.
This sort of thing becomes a somewhat simpler problem if you treat the server as a client/server application. Let the server have "n" connections to client admin applications that send commands and receive output. The client application could completely separate input and output, having one thread to handle input, and one to handle output.
The output thread could block if the input thread is in the middle of entering a line, and unblock when the line is either cancelled or committed.
I started work on a test program to show how you could divide the console into an output area and an input area, where the input area is moved down as the output area expands with more output. It's not perfect yet, but you may be able to develop it into the answer you're looking for:
static int outCol, outRow, outHeight = 10;
static void Main(string[] args)
{
bool quit = false;
System.DateTime dt = DateTime.Now;
do
{
if (Console.KeyAvailable)
{
if (Console.ReadKey(false).Key == ConsoleKey.Escape)
quit = true;
}
System.Threading.Thread.Sleep(0);
if (DateTime.Now.Subtract(dt).TotalSeconds > .1)
{
dt = DateTime.Now;
WriteOut(dt.ToString(" ss.ff"), false);
}
} while (!quit);
}
static void WriteOut(string msg, bool appendNewLine)
{
int inCol, inRow;
inCol = Console.CursorLeft;
inRow = Console.CursorTop;
int outLines = getMsgRowCount(outCol, msg) + (appendNewLine?1:0);
int outBottom = outRow + outLines;
if (outBottom > outHeight)
outBottom = outHeight;
if (inRow <= outBottom)
{
int scrollCount = outBottom - inRow + 1;
Console.MoveBufferArea(0, inRow, Console.BufferWidth, 1, 0, inRow + scrollCount);
inRow += scrollCount;
}
if (outRow + outLines > outHeight)
{
int scrollCount = outRow + outLines - outHeight;
Console.MoveBufferArea(0, scrollCount, Console.BufferWidth, outHeight - scrollCount, 0, 0);
outRow -= scrollCount;
Console.SetCursorPosition(outCol, outRow);
}
Console.SetCursorPosition(outCol, outRow);
if (appendNewLine)
Console.WriteLine(msg);
else
Console.Write(msg);
outCol = Console.CursorLeft;
outRow = Console.CursorTop;
Console.SetCursorPosition(inCol, inRow);
}
static int getMsgRowCount(int startCol, string msg)
{
string[] lines = msg.Split('\n');
int result = 0;
foreach (string line in lines)
{
result += (startCol + line.Length) / Console.BufferWidth;
startCol = 0;
}
return result + lines.Length - 1;
}
Personally i would use event handlers to managed a console that handles both input and outup at the same time, create a class ScreenManager or whatever, inside that class add a void RunProgram() mthod, create an event with handler and required variables for reading the input key "Console.ReadKey(bool).key".
static Consolekey newKey;
on your main program, creat an instance of your class "whatev you called it", then create a thread of that instances internal method, Thread coreThread = new Thread(delegate() {myinstance.myProgramMrthod()});
loop in your main until the threads up and running. while (!Thread.IsAlive) ;
then create the main program loop.
while (true)
{
}
then for safty, join your custom thread so the main program doesnt continue until the custom thread is closed/disposed.
customThread.Join();
you now have two threads running seperatly.
back to your class, create a switch inside your event handler method.
switch (newkey)
{
case Consolekey.enter
Console.WriteLine("enter pressed");
break;
ect, ect.
default:
Console.write(newkey); // writes character key that dont match above conditions to the screen.
break;
}
stick allyour logic inhere with how you want to handle keys. How to use multiple modifier keys in C# might be of some help.
inside your instance's method RunProgram() or whatev you choose to call it, after you've done whatever code you need to, create an infinite loop to check for key change.
while (true)
{
newKey = Console.ReadKey(true).Key;
if (newKey != oldKey)
{
KeyChange.Invoke();
}
}
this loop stores any key pressed and then checks to see if theres a new key, if true fires the event.
you now have the core of what your looking for, one string that loops askng for a new key, whilst the main loop is free to display whatever text you wish to display.
two fixable bugs with this that i can think of, one is "default" inside switch will print to console in caps or strings. and the other is any text added to the console is added at the cursor point so it adds to the text the user has just input.
hwoever i will, since i've just made it, how you have to manager the text been added to the console. again im using an event. i could use methods and functions throughout but events add move flexability to the program, i think.
okay so we want to be able to add text to the console, without it upsetting the input we enter. keeping the input at the bottom;
create a new delegate that has a signiture with a string argument, void delegate myDelegate(string Arg). then create an event with this delegate, call it newline, newinput, whatev you like.
the events handler will take a string argument (repersenting the console update text: what you want to insert into the console above the users input) it will grab the text the user has been entering into the console, store it, then print out the paramiter string onto the console, then print out the users input underneith.
personally i chose to create a static string at the top outside the method, initialise it to empty, cos its going to be frequently used and you dont want to be creating a new identifyer and then initialising the variable everytime the method is called, then dispose of it at the end of the method, only to recreate a new one again, and again.
call the string "input" or whatever.
in the default area of the keychange event handle add input +=newkey.
in the Consolekey.enter section console writline input then input = string.empty Or string = "".
in the event handler add some logic.
public void OnInsert(string Argument)
{
Console.CursorTop -= 1;
// moves the cursor to far left so new input overwrites the old.
// if arg string is longer, then print arg string then print input // string.
if (Argument.Length > input.Length)
{
Console.WriteLine(Argument);
Console.WriteLine(input);
}
else
{
// if the users input if longer than the argument text then print
// out the argument text, then print white spaces to overwrite the
// remaining input characters still displayed on screen.
for (int i = 0; i < input.Length;i++ )
{
if (i < Argument.Length)
{
Console.Write(Argument[i]);
}
else
{
Console.Write(' ');
}
}
Console.Write(Environment.NewLine);
Console.WriteLine(input);
}
}
hope this helps some of you, its not perfect, a quick put together test that works enough to be built on.
Have you tried calling OpenStandardInput, reading any input and resetting it's position, then writing to the output stream. Afterwards, you can call OpenStandardInput again and fill the data back into the stream.
I got my example working using Console.MoveBufferArea()
, but note that this won't work on platforms other than Windows because the method is not implemented on those platforms.
With this example you would use Read()
instead of Console.ReadLine()
and Log(...)
instead of Console.WriteLine(...)
in your code.
class Program
{
static void Main(string[] args)
{
// Reader
new Thread(() =>
{
string line;
while ((line = Read()) != null)
{
//...
}
Environment.Exit(0);
}).Start();
// Writer
new Thread(() =>
{
while (true)
{
Thread.Sleep(1000);
Log("----------");
}
}).Start();
}
static int lastWriteCursorTop = 0;
static void Log(string message)
{
int messageLines = message.Length / Console.BufferWidth + 1;
int inputBufferLines = Console.CursorTop - lastWriteCursorTop + 1;
Console.MoveBufferArea(sourceLeft: 0, sourceTop: lastWriteCursorTop,
targetLeft: 0, targetTop: lastWriteCursorTop + messageLines,
sourceWidth: Console.BufferWidth, sourceHeight: inputBufferLines);
int cursorLeft = Console.CursorLeft;
Console.CursorLeft = 0;
Console.CursorTop -= inputBufferLines - 1;
Console.WriteLine(message);
lastWriteCursorTop = Console.CursorTop;
Console.CursorLeft = cursorLeft;
Console.CursorTop += inputBufferLines - 1;
}
static string Read()
{
Console.Write(">"); // optional
string line = Console.ReadLine();
lastWriteCursorTop = Console.CursorTop;
return line;
}
}