Is there any way to detect both Readline and ReadKey, so that in most cases it behaves as a readline, except for some special key inputs that should be detected?
I need some "parallel" implementation to introduce simultaneity.
The code below is synchronous and does not meet my need
while ((line = Console.ReadLine()) != "x")
{
if (line == "BLABLA")
{
//Stuff
}
else
{
//Stuff
}
ConsoleKeyInfo ki = Console.ReadKey(true);
if ((ki.Key == ConsoleKey.V) && (ki.Modifiers == ConsoleModifiers.Control))
{
//Stuff
}
}
Here's a function I just made to do this.
Right now it only handles backspace, enter, and Esc, but it could easily be modified to handle other keys if you deem them necessary.
// returns null if user pressed Escape, or the contents of the line if they pressed Enter.
private static string ReadLineOrEsc()
{
string retString = "";
int curIndex = 0;
do
{
ConsoleKeyInfo readKeyResult = Console.ReadKey(true);
// handle Esc
if (readKeyResult.Key == ConsoleKey.Escape)
{
Console.WriteLine();
return null;
}
// handle Enter
if (readKeyResult.Key == ConsoleKey.Enter)
{
Console.WriteLine();
return retString;
}
// handle backspace
if (readKeyResult.Key == ConsoleKey.Backspace)
{
if (curIndex > 0)
{
retString = retString.Remove(retString.Length - 1);
Console.Write(readKeyResult.KeyChar);
Console.Write(' ');
Console.Write(readKeyResult.KeyChar);
curIndex--;
}
}
else
// handle all other keypresses
{
retString += readKeyResult.KeyChar;
Console.Write(readKeyResult.KeyChar);
curIndex++;
}
}
while (true);
}
No, not as such. Both methods block until the user enters something on the console. So even if you would find a way to have both run in parallel, it will not be deterministic which one gets the first shot.
There is a (not obvious) similar problem: how to make Console.ReadLine()
abort/break after a certain amount of time without user input.
There have been multiple attempts for this problem here:
- Console.ReadLine Break
- How to add a Timeout to Console.ReadLine()?
Most are modelled around either creating your own version of a ReadLine
function that adds a timeout (or in your case special handling for certain character (codes)) or the use some sort of threading.
Both ways are either non-trivial or have their own issues (make sure you review the comments, even for the accepted answers).
In short, I think you will need to roll your own version of ReadLine, based on Console.ReadKey
with your special handling included and that much of the genuine Console.ReadLine
behavior that you require. Note that this even include such basic things as RETURN, ARROW KEYS, BACKSPACE handling, etc.
Update: There is the getline.cs Code from the Mono project, which implements a line editing capability like it was provided by some venerable UNIX shells (EMACS mode, if you care). For that, I believe it will need to implement some sort of ReadLine replacement, although I haven't checked. Maybe you can use this as a starting point.
In response to @Overlord Zurd, I improved the code provided by the user.
public class ConsoleOutput
{
private ConsoleOutputType OutputType { get; set; }
private object MyObject { get; }
private static bool IsInserting { get; set; }
public string KeyName => IsKey() && (ConsoleKeyInfo)MyObject != null ? ((ConsoleKeyInfo)MyObject).Key.ToString() : "Null";
public string OutputString => !IsKey() && MyObject != null ? (string)MyObject : string.Empty;
public static event Action<string> ReadInput = delegate { };
public static event Action<ConsoleKeyInfo> ReadKey = delegate { };
private ConsoleOutput()
{
}
public ConsoleOutput(object obj)
{
MyObject = obj;
OutputType = obj is ConsoleKeyInfo ? ConsoleOutputType.Key : ConsoleOutputType.Value;
}
public bool IsKey()
{
return OutputType == ConsoleOutputType.Key;
}
public bool IsExitKey()
{
if (!IsKey())
return false;
var info = ((ConsoleKeyInfo)MyObject);
return (info.Modifiers & ConsoleModifiers.Control) != 0 && info.Key == ConsoleKey.B;
}
public string GetValue()
{
return (string)MyObject;
}
// returns null if user pressed Escape, or the contents of the line if they pressed Enter.
public static ConsoleOutput ReadLineOrKey()
{
string retString = "";
int curIndex = 0;
do
{
ConsoleKeyInfo readKeyResult = Console.ReadKey(true);
// handle Enter
if (readKeyResult.Key == ConsoleKey.Enter)
{
ReadInput?.Invoke(retString);
Console.WriteLine();
return new ConsoleOutput(retString);
}
// handle backspace
if (readKeyResult.Key == ConsoleKey.Backspace)
{
if (curIndex > 0)
{
retString = retString.Remove(retString.Length - 1);
Console.Write(readKeyResult.KeyChar);
Console.Write(' ');
Console.Write(readKeyResult.KeyChar);
--curIndex;
}
}
else if (readKeyResult.Key == ConsoleKey.Delete)
{
if (retString.Length - curIndex > 0)
{
// Store current position
int curLeftPos = Console.CursorLeft;
// Redraw string
for (int i = curIndex + 1; i < retString.Length; ++i)
Console.Write(retString[i]);
// Remove last repeated char
Console.Write(' ');
// Restore position
Console.SetCursorPosition(curLeftPos, Console.CursorTop);
// Remove string
retString = retString.Remove(curIndex, 1);
}
}
else if (readKeyResult.Key == ConsoleKey.RightArrow)
{
if (curIndex < retString.Length)
{
++Console.CursorLeft;
++curIndex;
}
}
else if (readKeyResult.Key == ConsoleKey.LeftArrow)
{
if (curIndex > 0)
{
--Console.CursorLeft;
--curIndex;
}
}
else if (readKeyResult.Key == ConsoleKey.Insert)
{
IsInserting = !IsInserting;
}
#if DEBUG
else if (readKeyResult.Key == ConsoleKey.UpArrow)
{
if (Console.CursorTop > 0)
--Console.CursorTop;
}
else if (readKeyResult.Key == ConsoleKey.DownArrow)
{
if (Console.CursorTop < Console.BufferHeight - 1)
++Console.CursorTop;
}
#endif
else
// handle all other keypresses
{
if (IsInserting || curIndex == retString.Length)
{
retString += readKeyResult.KeyChar;
Console.Write(readKeyResult.KeyChar);
++curIndex;
}
else
{
// Store char
char c = readKeyResult.KeyChar;
// Write char at position
Console.Write(c);
// Store cursor position
int curLeftPos = Console.CursorLeft;
// Clear console from curIndex to end
for (int i = curIndex; i < retString.Length; ++i)
Console.Write(' ');
// Go back
Console.SetCursorPosition(curLeftPos, Console.CursorTop);
// Write the chars from curIndex to end (with the new appended char)
for (int i = curIndex; i < retString.Length; ++i)
Console.Write(retString[i]);
// Restore again
Console.SetCursorPosition(curLeftPos, Console.CursorTop);
// Store in the string
retString = retString.Insert(curIndex, new string(c, 1));
// Sum one to the cur index (we appended one char)
++curIndex;
}
}
if (char.IsControl(readKeyResult.KeyChar) &&
readKeyResult.Key != ConsoleKey.Enter &&
readKeyResult.Key != ConsoleKey.Backspace &&
readKeyResult.Key != ConsoleKey.Tab &&
readKeyResult.Key != ConsoleKey.Delete &&
readKeyResult.Key != ConsoleKey.RightArrow &&
readKeyResult.Key != ConsoleKey.LeftArrow &&
readKeyResult.Key != ConsoleKey.Insert)
{
#if DEBUG
if (readKeyResult.Key == ConsoleKey.UpArrow || readKeyResult.Key == ConsoleKey.DownArrow)
continue;
#endif
ReadKey?.Invoke(readKeyResult);
Console.WriteLine();
return new ConsoleOutput(readKeyResult);
}
}
while (true);
}
}
As you can see, I implemented Insert, Arrow control, Delete, etc etc... (Insert was an important thing because if you write any text with this code you will see behavior as Insert key provides).
And example of use:
internal class Program
{
private static void Main(string[] args)
{
Console.Write("Write test string: ");
var test = ConsoleOutput.ReadLineOrKey();
if (test.IsKey())
Console.WriteLine(test.KeyName);
else
Console.WriteLine($"Output string: {test.OutputString}");
Console.Read();
}
}
You can keep updated on this link (is a link to my lib I'm currently working at).