Reputation: 1047
Let me explain, this is what I'm trying to achieve:
Enter name:
F4-Quit
is there a way to display the next line (F4-Quit) without readline waiting for user input before displaying it? the reason is I wanted to, at anytime quit, even though name has not been entered yet (cursor is waiting), this is very useful in many circumstances where someone during the process of entering information changes their mind and wants to quit or go back.
If that's not possible what would be the way around it?
thank you!
Upvotes: 5
Views: 4063
Reputation: 297
So you want to do 2 things:
For the first question it seems like what you're looking for is Console.SetCursorPosition(). With SetCursorPosition you can jump to anywhere in your Console Window. In your case you want to jump to the next line from where you are and Console.Write your "F4-Quit" and then jump back again.
Console.CursorTop //Gets and sets the current Console row position
Console.CursorLeft //Gets and sets the current Console column position
//To get to next row from where you currently are:
Console.SetCursorPosition(Console.CursorLeft, Console.CursorTop + 1);
//Or:
Console.CursorTop++;
So a potential solution for you would be:
Console.Write("Enter Name: ");
var startPos = new int[] { Console.CursorLeft, Console.CursorTop};
Console.CursorLeft = 0;
Console.CursorTop++;
Console.Write("F4-Quit");
Console.SetCursorPosition(startPos[0], startPos[1]);
Console.ReadLine();
As for the cancelling of the Console.ReadLine() you need a bit more code:
As Hans Passant has pointed out you can create your own Console.ReadLine but this can be quite time-consuming. So if there's no other better way here's one I made that seems to do the trick:
Just create a new static class for instance:
public static class XConsole
{
}
and paste the following methods in it:
public static string CancelableReadLine(out bool isCancelled)
{
var cancelChar = ConsoleKey.F4; // You can change the character for cancelling here if you want
var builder = new StringBuilder();
var cki = Console.ReadKey(true);
int index = 0;
(int left, int top) startPosition;
while (cki.Key != ConsoleKey.Enter && cki.Key != cancelChar)
{
if (cki.Key == ConsoleKey.LeftArrow)
{
if (index < 1)
{
cki = Console.ReadKey(true);
continue;
}
LeftArrow(ref index, cki);
}
else if (cki.Key == ConsoleKey.RightArrow)
{
if (index >= builder.Length)
{
cki = Console.ReadKey(true);
continue;
}
RightArrow(ref index, cki, builder);
}
else if (cki.Key == ConsoleKey.Backspace)
{
if (index < 1)
{
cki = Console.ReadKey(true);
continue;
}
BackSpace(ref index, cki, builder);
}
else if (cki.Key == ConsoleKey.Delete)
{
if (index >= builder.Length)
{
cki = Console.ReadKey(true);
continue;
}
Delete(ref index, cki, builder);
}
else if (cki.Key == ConsoleKey.Tab)
{
cki = Console.ReadKey(true);
continue;
}
else
{
if (cki.KeyChar == '\0')
{
cki = Console.ReadKey(true);
continue;
}
Default(ref index, cki, builder);
}
cki = Console.ReadKey(true);
}
if (cki.Key == cancelChar)
{
startPosition = GetStartPosition(index);
ErasePrint(builder, startPosition);
isCancelled = true;
return string.Empty;
}
isCancelled = false;
startPosition = GetStartPosition(index);
var endPosition = GetEndPosition(startPosition.left, builder.Length);
var left = 0;
var top = startPosition.top + endPosition.top + 1;
Console.SetCursorPosition(left, top);
var value = builder.ToString();
return value;
}
private static void LeftArrow(ref int index, ConsoleKeyInfo cki)
{
var previousIndex = index;
index--;
if (cki.Modifiers == ConsoleModifiers.Control)
{
index = 0;
var startPosition = GetStartPosition(previousIndex);
Console.SetCursorPosition(startPosition.left, startPosition.top);
return;
}
if (Console.CursorLeft > 0)
Console.CursorLeft--;
else
{
Console.CursorTop--;
Console.CursorLeft = Console.BufferWidth - 1;
}
}
private static void RightArrow(ref int index, ConsoleKeyInfo cki, StringBuilder builder)
{
var previousIndex = index;
index++;
if (cki.Modifiers == ConsoleModifiers.Control)
{
index = builder.Length;
var startPosition = GetStartPosition(previousIndex);
var endPosition = GetEndPosition(startPosition.left, builder.Length);
var top = startPosition.top + endPosition.top;
var left = endPosition.left;
Console.SetCursorPosition(left, top);
return;
}
if (Console.CursorLeft < Console.BufferWidth - 1)
Console.CursorLeft++;
else
{
Console.CursorTop++;
Console.CursorLeft = 0;
}
}
private static void BackSpace(ref int index, ConsoleKeyInfo cki, StringBuilder builder)
{
var previousIndex = index;
index--;
var startPosition = GetStartPosition(previousIndex);
ErasePrint(builder, startPosition);
builder.Remove(index, 1);
Console.Write(builder.ToString());
GoBackToCurrentPosition(index, startPosition);
}
private static void Delete(ref int index, ConsoleKeyInfo cki, StringBuilder builder)
{
var startPosition = GetStartPosition(index);
ErasePrint(builder, startPosition);
if (cki.Modifiers == ConsoleModifiers.Control)
{
builder.Remove(index, builder.Length - index);
Console.Write(builder.ToString());
GoBackToCurrentPosition(index, startPosition);
return;
}
builder.Remove(index, 1);
Console.Write(builder.ToString());
GoBackToCurrentPosition(index, startPosition);
}
private static void Default(ref int index, ConsoleKeyInfo cki, StringBuilder builder)
{
var previousIndex = index;
index++;
builder.Insert(previousIndex, cki.KeyChar);
var startPosition = GetStartPosition(previousIndex);
Console.SetCursorPosition(startPosition.left, startPosition.top);
Console.Write(builder.ToString());
GoBackToCurrentPosition(index, startPosition);
}
private static (int left, int top) GetStartPosition(int previousIndex)
{
int top;
int left;
if (previousIndex <= Console.CursorLeft)
{
top = Console.CursorTop;
left = Console.CursorLeft - previousIndex;
}
else
{
var decrementValue = previousIndex - Console.CursorLeft;
var rowsFromStart = decrementValue / Console.BufferWidth;
top = Console.CursorTop - rowsFromStart;
left = decrementValue - rowsFromStart * Console.BufferWidth;
if (left != 0)
{
top--;
left = Console.BufferWidth - left;
}
}
return (left, top);
}
private static void GoBackToCurrentPosition(int index, (int left, int top) startPosition)
{
var rowsToGo = (index + startPosition.left) / Console.BufferWidth;
var rowIndex = index - rowsToGo * Console.BufferWidth;
var left = startPosition.left + rowIndex;
var top = startPosition.top + rowsToGo;
Console.SetCursorPosition(left, top);
}
private static (int left, int top) GetEndPosition(int startColumn, int builderLength)
{
var cursorTop = (builderLength + startColumn) / Console.BufferWidth;
var cursorLeft = startColumn + (builderLength - cursorTop * Console.BufferWidth);
return (cursorLeft, cursorTop);
}
private static void ErasePrint(StringBuilder builder, (int left, int top) startPosition)
{
Console.SetCursorPosition(startPosition.left, startPosition.top);
Console.Write(new string(Enumerable.Range(0, builder.Length).Select(o => ' ').ToArray()));
Console.SetCursorPosition(startPosition.left, startPosition.top);
}
Console.WriteLine("Calling at start of screen");
string text = XConsole.CancelableReadLine(out bool isCancelled);
if (isCancelled)
{
//Do what you want in here, for instance:
return;
}
You can also call it after a Console.Write:
Console.WriteLine("Calling after Console.Write");
Console.Write("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
string text = XConsole.CancelableReadLine(out bool isCancelled);
if (isCancelled)
return;
This code handles:
- writing beyond the Console window edge
- calling method after existing text (using Console.Write)
- resizing Console window
- Ctrl modifiers
This code does not handle:
- Tab key (Normal Console.ReadLine has a bug when tabbing further than window size so chose to exclude this as it is seldom needed...)
My code is based off of oleg wx and Chris Dunaway's answers so credit goes to them as well.
I would recommend to try to build your own based off of these answers but sometimes you just need something to work.
Hope this helps!
Reference:
https://stackoverflow.com/a/66495807/15337673
Upvotes: 1
Reputation: 1047
Here's the solution as Jeppe pointed out
public static void Main()
{
ConsoleKeyInfo cki;
Console.Clear();
// Establish an event handler to process key press events.
Console.CancelKeyPress += new ConsoleCancelEventHandler(myHandler);
while (true) {
Console.Write("Press any key, or 'X' to quit, or ");
Console.WriteLine("CTRL+C to interrupt the read operation:");
// Start a console read operation. Do not display the input.
cki = Console.ReadKey(true);
// this process can be skipped
// Console.WriteLine(" Key pressed: {0}\n", cki.Key);
// Exit if the user pressed the 'X' key.
if (cki.Key == ConsoleKey.F4) break;
}
}
protected static void myHandler(object sender, ConsoleCancelEventArgs args)
{
args.Cancel = true;
go_back_to_main();
}
Upvotes: 3
Reputation: 942468
Just write your own version of ReadLine(). Here's a TryReadLine() version in the pattern of .NET's TryParse():
static bool TryReadLine(out string result) {
var buf = new StringBuilder();
for (; ; ) {
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.F4) {
result = "";
return false;
}
else if (key.Key == ConsoleKey.Enter) {
result = buf.ToString();
return true;
}
else if (key.Key == ConsoleKey.Backspace && buf.Length > 0) {
buf.Remove(buf.Length - 1, 1);
Console.Write("\b \b");
}
else if (key.KeyChar != 0) {
buf.Append(key.KeyChar);
Console.Write(key.KeyChar);
}
}
}
Upvotes: 9
Reputation: 62012
If it does not have to be the F4 key, but you can use Ctrl+C or Ctrl+Break, then you might be able to use the CancelKeyPress
event.
Upvotes: 2
Reputation: 564901
If that's not possible what would be the way around it?
The best way around this would be to use a GUI application. This would eliminate the restrictions placed on you by the console.
In general, if you want complex user input control, a UI is going to be better than a console.
If you must use a console application, you likely would need to use Console.ReadKey
in a loop instead of Console.ReadLine
, and check for the "escape" key stroke. As soon as a new line character has been entered, you can process your "text" command/input/etc.
Upvotes: 4