Using C# 4 in a Windows console application that continually reports progress how can I make the "redraw" of the screen more fluid?
I'd like to do one of the following:
- Have it only "redraw" the part of the screen that's changing (the progress portion) and leave the rest as is.
- "Redraw" the whole screen but not have it flicker.
Currently I re-write all the text (application name, etc.). Like this:
Console.Clear();
WriteTitle();
Console.WriteLine();
Console.WriteLine("Deleting:\t{0} of {1} ({2})".FormatString(count.ToString("N0"), total.ToString("N0"), (count / (decimal)total).ToString("P2")));
Which causes a lot of flickering.
Try Console.SetCursorPosition. More details here: How can I update the current line in a C# Windows Console App?
static void Main(string[] args)
{
Console.SetCursorPosition(0, 0);
Console.Write("################################");
for (int row = 1; row < 10; row++)
{
Console.SetCursorPosition(0, row);
Console.Write("# #");
}
Console.SetCursorPosition(0, 10);
Console.Write("################################");
int data = 1;
System.Diagnostics.Stopwatch clock = new System.Diagnostics.Stopwatch();
clock.Start();
while (true)
{
data++;
Console.SetCursorPosition(1, 2);
Console.Write("Current Value: " + data.ToString());
Console.SetCursorPosition(1, 3);
Console.Write("Running Time: " + clock.Elapsed.TotalSeconds.ToString());
Thread.Sleep(1000);
}
Console.ReadKey();
}
I know this question is a bit old but I found if you set Console.CursorVisible = false then the flickering stops as well.
Here's a simple working demo that shows multi-line usage without flickering. It shows the current time and a random string every second.
private static void StatusUpdate()
{
var whiteSpace = new StringBuilder();
whiteSpace.Append(' ', 10);
var random = new Random();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var randomWord = new string(Enumerable.Repeat(chars, random.Next(10)).Select(s => s[random.Next(s.Length)]).ToArray());
while (true)
{
Console.SetCursorPosition(0, 0);
var sb = new StringBuilder();
sb.AppendLine($"Program Status:{whiteSpace}");
sb.AppendLine("-------------------------------");
sb.AppendLine($"Last Updated: {DateTime.Now}{whiteSpace}");
sb.AppendLine($"Random Word: {randomWord}{whiteSpace}");
sb.AppendLine("-------------------------------");
Console.Write(sb);
Thread.Sleep(1000);
}
}
The above example assumes your console window is blank to start. If not, make sure to use Console.Clear() first.
Technical Note:
SetCursorPosition(0,0) places the cursor back to the top (0,0) so the next call to Console.Write will start from line 0, char 0. Note, it doesn't delete the previous content before writing. As an example, if you write "asdf" over a previous line such as "0123456", you'll end up with something like "asdf456" on that line. For that reason, we use a whiteSpace variable to ensure any lingering characters from the previous line are overwritten with blank spaces. Adjust the length of the whiteSpace variable to meet your needs. You only need the whiteSpace variable for lines that change.
Personal Note:
For my purposes, I wanted to show the applications current status (once a second) along with a bunch of other status information and I wanted to avoid any annoying flickering that can happen when you use Console.Clear(). In my application, I run my status updates behind a separate thread so it constantly provides updates even though I have numerous other threads and long running tasks going at the same time.
Credits:
Thanks to previous posters and dtb for the random string generator used in the demo.
How can I generate random alphanumeric strings in C#?
You could try to hack something together using the core libraries.
Rather than waste your time for sub-standard results, I would check out this C# port of the ncurses library (which is a library used for formatting console output):
Curses Sharp
I think you can use \r in Windows console to return the beginning of a line.
You could also use SetCursorPosition.
I would recommend the following extension methods. They allow you to use a StringBuilder to refresh the console view without any flicker, and also tidies up any residual characters on each line
The Problem: The following demo demonstrates using a standard StringBuilder, where updating lines that are shorter than the previously written line get jumbled up. It does this by writing a short string, then a long string on a loop:
public static void Main(string[] args)
{
var switchTextLength = false;
while(true)
{
var sb = new StringBuilder();
if (switchTextLength)
sb.AppendLine("Short msg");
else
sb.AppendLine("Longer message");
sb.UpdateConsole();
switchTextLength = !switchTextLength;
Thread.Sleep(500);
}
}
Result:
The Solution: By using the extension method provided below, the issue is resolved
public static void Main(string[] args)
{
var switchTextLength = false;
while(true)
{
var sb = new StringBuilder();
if (switchTextLength)
sb.AppendLineEx("Short msg");
else
sb.AppendLineEx("Longer message");
sb.UpdateConsole();
switchTextLength = !switchTextLength;
Thread.Sleep(500);
}
}
Result:
Extension Methods:
public static class StringBuilderExtensions
{
/// <summary>
/// Allows StrinbBuilder callers to append a line and blank out the remaining characters for the length of the console buffer width
/// </summary>
public static void AppendLineEx(this StringBuilder c, string msg)
{
// Append the actual line
c.Append(msg);
// Add blanking chars for the rest of the buffer
c.Append(' ', Console.BufferWidth - msg.Length - 1);
// Finish the line
c.Append(Environment.NewLine);
}
/// <summary>
/// Combines two StringBuilders using AppendLineEx
/// </summary>
public static void AppendEx(this StringBuilder c, StringBuilder toAdd)
{
foreach (var line in toAdd.ReadLines())
{
c.AppendLineEx(line);
}
}
/// <summary>
/// Hides the console cursor, resets its position and writes out the string builder
/// </summary>
public static void UpdateConsole(this StringBuilder c)
{
// Ensure the cursor is hidden
if (Console.CursorVisible) Console.CursorVisible = false;
// Reset the cursor position to the top of the console and write out the string builder
Console.SetCursorPosition(0, 0);
Console.WriteLine(c);
}
}
I actually had this issue so I made a quick simple method to try and eliminate this.
static void Clear(string text, int x, int y)
{
char[] textChars = text.ToCharArray();
string newText = "";
//Converts the string you just wrote into a blank string
foreach(char c in textChars)
{
text = text.Replace(c, ' ');
}
newText = text;
//Sets the cursor position
Console.SetCursorPosition(x, y);
//Writes the blank string over the old string
Console.WriteLine(newText);
//Resets cursor position
Console.SetCursorPosition(0, 0);
}
It actually worked surprisingly well and I hope it may work for you!
Naive approach but for simple applications is working:
protected string clearBuffer = null; // Clear this if window size changes
protected void ClearConsole()
{
if (clearBuffer == null)
{
var line = "".PadLeft(Console.WindowWidth, ' ');
var lines = new StringBuilder();
for (var i = 0; i < Console.WindowHeight; i++)
{
lines.AppendLine(line);
}
clearBuffer = lines.ToString();
}
Console.SetCursorPosition(0, 0);
Console.Write(clearBuffer);
Console.SetCursorPosition(0, 0);
}
Console.SetCursorPosition(0, 0); //Instead of Console.Clear();
WriteTitle();
Console.WriteLine();
Console.WriteLine("Deleting:\t{0} of {1} ({2})".FormatString(count.ToString("N0")
Related
I am working on a custom texteditor control and encountered this problem.
I need a function that gets the character indexes for every newline "\n" in the text.
I already have two ways to accomplish this:
private List<int> GetNewLineLocations()
{
var list = new List<int>();
int ix = 0;
foreach (var c in this.Text)
{
if (c == '\n') list.Add(ix);
ix++;
}
Debug.WriteLine(ix);
return list;
}
And:
private List<int> GetNewLineLocations()
{
var list = new List<int>();
int ix = -1;
for (int i = 0; i < this.Lines.Length; i++)
{
ix += Lines[i].Length;
ix += 1;
list.Add(ix);
}
return list;
}
The first solution does work but slows down the more text is entered in the richtextbox that is around 40000 characters but that can be spread out among a lot of rows like 20000.
The second one seems to be faster because it loops less and does more or less the same but is slows down dramatically at 1000 rows no mater how much text they contain.
The code of course needs to run fast and not use a lot of resources that is why I thought the second solution would be better.
My question is:
Which solution is better and why?
Why is the second solution so much slower?
Is there an even better solution?
I tried both of your examples and Felix's and a solution of my own using a rich text box and 40k lines. The result was this was the fastest, and I saw no slow down. Can you try passing the array of lines as a paramater and let us know the result?
public static List<int> GetNewLineLocations(this string[] lines)
{
var list = new List<int>();
int ix = -1;
for (int i = 0; i < lines.Length; i++)
{
ix += lines[i].Length+1;
list.Add(ix);
}
return list;
}
When working with strings Regular Expressions are very nice to use. But they are not the fastest. If you need faster processing you should do it on lower levels and in parallel. And make sure to use long as index because int only allow you to process up to 2^31 chars, and long up to 2^63 chars.
I agree with #Nyerguds
who sayed in the comments:
The problem is that the standard function to fetch the text in a rich text box is actually a processing function that has to filter out the RTF markup. The actual function to fetch the text is the bottleneck, not what comes after it.
So your data should be held somewhere in the code and not in the userinterface. Sooner or later when processing long texts that will cause trouble anyway, like stuttering when scrolling or further bottlenecks. And I would only represent the lines that could be displayed in the control anyway. So you should overthink your application design. Check your Front/Backend seperation. Storing your data in a backend will allow you to access your data directly without depending in your Textbox methods or other userinterface stuff.
Here is a sample how to easy process data with the Parallel Class of the .net framework:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
internal class Program
{
public static byte[] _globalDataStore { get; set; }
private static void Main(string[] args)
{
DoStuff();
ShowDone();
}
private static void ShowDone()
{
Console.WriteLine("done...");
Console.ReadKey();
}
private static void DoStuff()
{
var tempData = GetData();
StoreData(ref tempData);
tempData = null; //free some ram
var dataIdentifier = (byte)'\n';
GetAndPromptDataPositions(_globalDataStore, dataIdentifier);
}
private static void GetAndPromptDataPositions<T>(T[] data, T dataIdentifier)
{
var dataPositionList = GetDataPositions<T>(data, dataIdentifier);
PromptDataPostions(dataPositionList);
}
private static void PromptDataPostions(IEnumerable<long> positionList)
{
foreach (var position in positionList)
{
Console.WriteLine($"Position '{position}'");
}
}
private static string GetData()
{
return "aasdlj\naksdlkajsdlkasldj\nasld\njkalskdjasldjlasd";
}
private static void StoreData(ref string tempData)
{
_globalDataStore = Encoding.ASCII.GetBytes(tempData);
}
private static List<long> GetDataPositions<T>(T[] data, T dataToFind)
{
lock (data) //prevent data from being changed while processing, important when have other threaded could write data
{
var postitonList = new List<long>();
Parallel.For(0, data.LongLength, (position) =>
{
if (data[position].Equals(dataToFind))
{
lock (postitonList) //lock list because of multithreaded access to prevent data corruption
{
postitonList.Add(position);
}
}
});
return postitonList;
}
}
}
}
I am building winforms .net Application , I have a E-Pos printer on Network ,
using this Code below :
On Form Loading Printer initializing :
explorer = new PosExplorer(this);
DeviceInfo receiptPrinterDevice = explorer.GetDevice("PosPrinter", Properties.Settings.Default.KitchenPrinter); //May need to change this if you don't use a logicial name or use a different one.
kitchenPrinter = (PosPrinter)explorer.CreateInstance(receiptPrinterDevice);
ConnectToPrinter();
private void ConnectToPrinter()
{
kitchenPrinter.Open();
kitchenPrinter.Claim(10000);
kitchenPrinter.DeviceEnabled = true;
}
Function Call on Print Button :
private void PrintReceipt()
{
try
{ kitchenPrinter.PrintNormal(PrinterStation.Receipt, "test");
}
finally
{
}
}
When I want to Switch to Other Form I call Disconnect Function
DisconnectFromPrinter(kitchenPrinter);
Reporting frm = new Reporting(curuser);
frm.Show();
this.Hide();
private void DisconnectFromPrinter(PosPrinter kitchenPrinter)
{
try
{
kitchenPrinter.Release();
kitchenPrinter.Close();
}
catch { }
}
It prints successful one time ,when pressing to print next time it throws and exception
Method ClaimDevice threw an exception. Attempt was made to perform an illegal or unsupported operation with the device, or an invalid parameter value was used.
any suggestion ?
Since the Release command is not being effective and may Claim command is throwing an error every time I am loading my form because it is being Claimed before.
So I have Create a separate Class Called "createPOS"
class createPOS
{
public static PosExplorer explorer;
public static PosPrinter kitchenPrinter;
public static void createPos()
{
explorer = new PosExplorer();
DeviceInfo receiptPrinterDevice = explorer.GetDevice("PosPrinter", Properties.Settings.Default.KitchenPrinter); //May need to change this if you don't use a logicial name or use a different one.
kitchenPrinter = (PosPrinter)explorer.CreateInstance(receiptPrinterDevice);
kitchenPrinter.Open();
kitchenPrinter.Claim(10000);
kitchenPrinter.DeviceEnabled = true;
}
public static void Print(string text){
if (kitchenPrinter.Claimed)
PrintTextLine(kitchenPrinter, text); // kitchenPrinter.PrintNormal(PrinterStation.Receipt, text ); //Print text, then a new line character.
}
private static void PrintTextLine(PosPrinter printer, string text)
{
if (text.Length < printer.RecLineChars)
printer.PrintNormal(PrinterStation.Receipt, text + Environment.NewLine); //Print text, then a new line character.
else if (text.Length > printer.RecLineChars)
printer.PrintNormal(PrinterStation.Receipt, TruncateAt(text, printer.RecLineChars)); //Print exactly as many characters as the printer allows, truncating the rest, no new line character (printer will probably auto-feed for us)
else if (text.Length == printer.RecLineChars)
printer.PrintNormal(PrinterStation.Receipt, text + Environment.NewLine); //Print text, no new line character, printer will probably auto-feed for us.
}
private static string TruncateAt(string text, int maxWidth)
{
string retVal = text;
if (text.Length > maxWidth)
retVal = text.Substring(0, maxWidth);
return retVal;
}
}
and On the Login form that it will only be accessed once I have initialized my printer
createPOS.createPos();
and on MainForm I have called the Printing Method :
createPOS.Print("This allows me to Print Several times");
on that way I am able to print several times and even I navigate to other forms and come back that works fine.
Thank you guys.
So Here is the Program Again
As u can see i have Created 2 methods with a Working Loop
And Created 2 threads in main pointing towards these methods and they are started
What gets out as output is Both Loops work like 1 and then space and in new line 1 and so on
But what i want is to make them appear in the same row line side by side As we divide a page in 2 parts and write things in lines
I do not want To make them Work Seperately but at a time and in the same line but Different columns
I know it can be acheived by Writing Both Objects in same Console .wl but i want to acheive it this way by these 2 threads
Please provide valuable solutions that would work
Thanks
using System;
using System.Threading;
class Program
{
static void Main(string [] args)
{
Thread t1 = new Thread(code1);
Thread t2= new Thread (code2);
t1.Start();
t2.Start();
}
static void code1()
{
for(int i=0;i<50;i++)
{
Console.WriteLine(i);
Thread.Sleep(1000);
}
}
static void code2()
{
for(int i=0;i<50;i++)
{
Console.WriteLine("/t/t"+i);
Thread.Sleep(1000);
}
}}
You have to use the Console.SetCursorPosition(int left, int top) method, so you can write on the Console starting from any position you want, also back in the previous rows.
Obviously, you have to keep trace of the position for each Thread. That is, the current row of that Thread, and its first column.
In my example I made 2 threads, one with the first column in position 0, and the second with the first column in position 50. Be careful about the width of the strings that you need to write, or they will overflow their own space on the Console.
Also, because you are doing it in a multithreading app, you need a lock on the Console. Otherwise, suppose this: a Thread sets the CursorPosition, then another Thread sets it, then the scheduler returns to the first Thread... the first Thread writes on the second Thread's position!
This is a very simple Console Program that gets the point:
using System;
using System.Threading;
namespace StackOverflow_3_multithread_on_console
{
class Program
{
static Random _random = new Random();
static void Main(string[] args)
{
var t1 = new Thread(Run1);
var t2 = new Thread(Run2);
t1.Start();
t2.Start();
}
static void Run1()
{
for(int i = 0; i < 30; i++)
{
Thread.Sleep(_random.Next(2000)); //for test
ConsoleLocker.Write("t1:" + i.ToString(), 0, i);
}
}
static void Run2()
{
for (int i = 0; i < 30; i++)
{
Thread.Sleep(_random.Next(2000)); //for test
ConsoleLocker.Write("t2:" + i.ToString(), 30, i);
}
}
}
static class ConsoleLocker
{
private static object _lock = new object();
public static void Write(string s, int left, int top)
{
lock (_lock)
{
Console.SetCursorPosition(left, top);
Thread.Sleep(100); //for test
Console.Write(s);
}
}
}
}
All the Thread.Sleep are there just to demonstrate that the lock works well. You can remove all them, especially the one in the ConsoleLocker.
I know there are MANY similiar questions, but I can't seem to get to the bottom of this.
In my program I execute a verification method which should compare two ascii HEX files with eachother (one is local, the other is read from a USB device). Some code:
private void buttonVerify_Click(object sender, EventArgs e)
{
onlyVerifying = true;
Thread t = new Thread(verifyProgram);
}
private void verifyProgram()
{
verifying = true;
externalFlashFile.Clear();
// After this method is finished, the returned data will end up in
// this.externalFlashFile since this listen to the usb's returned data
hexFile.readExternalFlashForVerify(usbDongle, autoEvent);
externalFlashFile.RemoveAt(0);
//externalFlashFile.RemoveAt(externalFlashFile.Count - 1);
hexFile.verifyProgram(externalFlashFile);
}
public void verifyProgram(List<string> externalProgram)
{
byte[] originalFile = null; // Will be modified later with given size
byte[] externalFile = new byte[4096];
int k = 0, errors = 0;
// Remove last line which contains USB command data
externalProgram.RemoveAt(externalProgram.Count - 1);
foreach (String currentLine in externalProgram)
{
for (int i = 0; i < 64; i += 2)
{
string currentDataByte = currentLine.Substring(i, 2);
externalFile[k] = Convert.ToByte(currentDataByte, 16);
k++;
}
progress += steps;
}
//... compare externalFile and originalFile
When executing the readExternalFlashForVerify the USB is responding with requested data. This data is parsed and calls an eventhandler:
public void usbDongle_OnDataParsed(object sender, EventArgs e)
{
if (verifying)
{
usbDongle.receivedBytesString.Trim();
externalFlashFile.Add(usbDongle.receivedBytesString.Substring(2, 32 * 2));
// Allow hexFile continue its thread processing
autoEvent.Set();
}
}
The first run is always completes correctly. The following executions, at the third or fourth iteration of the foreach, I get an extra element in externalProgram. This is not a global variable (argument in function call) and the function is not called anywhere else. This ofcourse throws an exception.
I tried adding .ToList() to externalProgram in the foreach but that didn't do any difference. How can my externalProgram be modified during this execution?
EDIT: I never found the cause of this, but replacing the foreach with a hard-coded for-loop solved the issue at hand. Not an optimal solution, but don't have much time on this.
// The list should never be larger than 128 items
for (int j = 0; j < 0x7f ; j++)
{
string currentLine = externalProgram[j];
// ...
Usually when you receive an exception with a message like that it is caused by multiple accesses from different threads to a list.
What I suggest you is to use a lock when you add and remove items from that list, so you're sure the indexes to that collection are not changing. You have to think what would happen if you try to remove the last element (of index 3, for example) of a collection when someone else removes a previous item (changing the lenght of the collection to 3...).
This example: Properly locking a List<T> in MultiThreaded Scenarios? describes better what I mean.
Probably this line is a problem:
externalProgram.RemoveAt(externalProgram.Count - 1);
If verifyProgram is called multiple times, it will remove more and more lines from externalProgram list passed by reference
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 something will be output to the console while the user is writing. This messes the buffer up. Is there any clean way of doing this?
Thanks.
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.
If you need to allow output to arrive while the user is typing I recommend sending the output to a new window. So, you could have one window that is used to start the application and then it spawns a thread to open a new console for input and then it continues to send any output messages to the original window. I think you will run in to too many resource locking issues if you try to keep everything in the same window.
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 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;
}
}
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.
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.