Console replication of old CHOICE DOS command - how to timeout? - choice.vb (0/1)

  • Thread starter Thread starter Mark A. Nadig
  • Start date Start date
M

Mark A. Nadig

I've got a console application in vb.net to replicate the behavior of
the old DOS command CHOICE. I've got a timer event successfully
firing, however the main thread is stuck on the console.read(). How
can I interrupt that and have it return the default errorlevel?

Any suggestions? I appreciate your thoughts.

On another note: If someone knows how to easily read a single key from
the console instead of relying on the user to hit enter, that would be
most helpful as well. I understand whidbey has some enh. in this area.

Best,
Mark Nadig
/\/\/
 
Use the _getch() function in MSVCRT.DLL. Here's an example:

Public Class MSVCRT
Declare Auto Function _getch Lib "msvcrt.dll" () As Char
Public Shared Function ReadKey() As Char
Return (_getch())
End Function
End Class

Module Module1
Sub Main()
Console.WriteLine("Press a key: ")
Dim p As Char = MSVCRT._getch()
Console.WriteLine(String.Format("You pressed {0}", p))
Console.WriteLine("Press ENTER to end")
p = " "
While (p <> ControlChars.Cr)
p = MSVCRT._getch()
End While
End Sub
End Module
 
I've got a console application in vb.net to replicate the behavior of
the old DOS command CHOICE. I've got a timer event successfully
firing, however the main thread is stuck on the console.read(). How
can I interrupt that and have it return the default errorlevel?

Any suggestions? I appreciate your thoughts.

On another note: If someone knows how to easily read a single key from
the console instead of relying on the user to hit enter, that would be
most helpful as well. I understand whidbey has some enh. in this area.

Best,
Mark Nadig
/\/\/

This is C# code - but it's a program I wrote and use from a batch file:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace GetKey
{
class KeyGrabber
{
// constant values for use with the console api
private const uint STD_INPUT_HANDLE = unchecked((uint) -10);
private const uint ENABLE_LINE_INPUT = 0x0002;
private const uint ENABLE_ECHO_INPUT = 0x0004;
private static readonly IntPtr INVALID_HANDLE_VALUE = new
IntPtr(-1);

// console functions
[DllImport("kernel32", ExactSpelling=true, SetLastError=true)]
private static extern IntPtr GetStdHandle (
uint nStdHandle);

[DllImport("kernel32", ExactSpelling=true, SetLastError=true)]
private static extern bool GetConsoleMode (
IntPtr hConsoleHandle,
out uint lpMode);

[DllImport("kernel32", ExactSpelling=true, SetLastError=true)]
private static extern bool SetConsoleMode(
IntPtr hConsoleHandle,
uint dwMode);

[STAThread]
static int Main(string[] args)
{
int ret = -1; // assume failure to simplify the code...
string prompt = (args.Length != 0 ? args[0] : string.Empty);

IntPtr stdin = GetStdHandle(STD_INPUT_HANDLE);
if (stdin != INVALID_HANDLE_VALUE)
{
uint oldMode;

if (GetConsoleMode(stdin, out oldMode))
{
uint newMode = (oldMode & (~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)));
if (SetConsoleMode(stdin, newMode))
{
Console.Write(prompt);
ret = Console.Read();
Console.WriteLine();
SetConsoleMode(stdin, oldMode)
}
}
}

return ret;
}
}
}

Should be fairly simple to convert to vb :) But the important part is
the setconsolemode api call.

Bye the way, I call the program from the bat file like:

@ECHO OFF

:MENU
CD c:\program files\slrn
CLS
ECHO ----------------------------
ECHO [1] news.microsoft.com
ECHO [2] news.uswest.net
ECHO [3] privatenews.microsoft.com
ECHO [4] betanews.microsoft.com
ECHO [*] Any other to exit
ECHO ----------------------------
GETKEY "Enter Your Selection: "

IF %ERRORLEVEL%==49 GOTO MSNEWS
IF %ERRORLEVEL%==50 GOTO USWEST
IF %ERRORLEVEL%==51 GOTO PMSNEWS
IF %ERRORLEVEL%==52 GOTO BETANEWS
GOTO END

In case you haven't guessed - I use this bat file to control my
newsreader :)

HTH
 
Hi Michael,

Thank you for the reply. Any suggestions on how to timeout that call?
Thanks,

Mark
/\/\/
 
Hi Tom,

Thank you for the reply. Any suggestions on how to timeout the
console.read() call and return a default? Thanks,

Mark

This is C# code - but it's a program I wrote and use from a batch file:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace GetKey
{
class KeyGrabber
{
// constant values for use with the console api
private const uint STD_INPUT_HANDLE = unchecked((uint) -10);
private const uint ENABLE_LINE_INPUT = 0x0002;
private const uint ENABLE_ECHO_INPUT = 0x0004;
private static readonly IntPtr INVALID_HANDLE_VALUE = new
IntPtr(-1);

// console functions
[DllImport("kernel32", ExactSpelling=true, SetLastError=true)]
private static extern IntPtr GetStdHandle (
uint nStdHandle);

[DllImport("kernel32", ExactSpelling=true, SetLastError=true)]
private static extern bool GetConsoleMode (
IntPtr hConsoleHandle,
out uint lpMode);

[DllImport("kernel32", ExactSpelling=true, SetLastError=true)]
private static extern bool SetConsoleMode(
IntPtr hConsoleHandle,
uint dwMode);

[STAThread]
static int Main(string[] args)
{
int ret = -1; // assume failure to simplify the code...
string prompt = (args.Length != 0 ? args[0] : string.Empty);

IntPtr stdin = GetStdHandle(STD_INPUT_HANDLE);
if (stdin != INVALID_HANDLE_VALUE)
{
uint oldMode;

if (GetConsoleMode(stdin, out oldMode))
{
uint newMode = (oldMode & (~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)));
if (SetConsoleMode(stdin, newMode))
{
Console.Write(prompt);
ret = Console.Read();
Console.WriteLine();
SetConsoleMode(stdin, oldMode)
}
}
}

return ret;
}
}
}

Should be fairly simple to convert to vb :) But the important part is
the setconsolemode api call.

/\/\/
 
The console class in .Net does not support timeouts or single key
reading. The read method waits for the Enter key to be pressed.

I believe that this has been enhanced in .Net 2.0 in that you can now
print anywhere on the console screen and wait for single key presses.

Chris
 
Hi Tom,

Thank you for the reply. Any suggestions on how to timeout the
console.read() call and return a default? Thanks,

Mark

sure... Start a timer (use the system.timers.timer or the
system.threading.timer) just before your call to console.read... If the
event fires return a default.
 
Mmmm... Not really. Sounds like you need to dig even deeper into the DLLs
and find the routine to scan the keyboard, put this function in a loop and
drop out of the loop when a key is pressed or your countdown timer reaches
zero. I've actually done things similar to this using OS and BIOS level
Interrupt calls in x86 Assembler, but it's been a long, long time... Sorry
about that :(
 
Hi Tom,

The sample code I posted already has a timer. However, since it just
invokes a delegate, how can the delegate method cause the thread Sub
Main is running on to stop and return some valu? Returning a value
from the delegate method doesn't have an affect on Sub Main().

Thanks,
Mark
sure... Start a timer (use the system.timers.timer or the
system.threading.timer) just before your call to console.read... If the
event fires return a default.

/\/\/
 
Hi Tom,

The sample code I posted already has a timer. However, since it just
invokes a delegate, how can the delegate method cause the thread Sub
Main is running on to stop and return some valu? Returning a value
from the delegate method doesn't have an affect on Sub Main().

Thanks,
Mark

Oopps... I didn't think about it. Well, I have another idea - in the
delegate method, call the WriteConsoleInput API function to write your
default value to the console input buffer. This should trigger the read
to return just as if you pressed the key.

Unfortunately, I'm working on my Linux box tonight, so I won't be able
to put together a sample until tommorow.
 
Hi Tom,

I think you may have already answered this question on another thread
from 1-28-04 http://www.mcse.ms/archive107-2004-1-337551.html

However, I'm having a heck of a time converting the c# code for the
INPUT_RECORD struct and so I can't write the DllImport stmt. Here's
the c# code you wrote a year ago :)

[StructLayout(LayoutKind.Sequential)]
public struct INPUT_RECORD
{
public InputEventType EventType;
public EVENT_UNION Event;
}

But, I can't translate to vb.net.

Anyway, thanks for all your help.

Mark
Oopps... I didn't think about it. Well, I have another idea - in the
delegate method, call the WriteConsoleInput API function to write your
default value to the console input buffer.
/\/\/
 
Hi Tom,

I think you may have already answered this question on another thread
from 1-28-04 http://www.mcse.ms/archive107-2004-1-337551.html

However, I'm having a heck of a time converting the c# code for the
INPUT_RECORD struct and so I can't write the DllImport stmt. Here's
the c# code you wrote a year ago :)

[StructLayout(LayoutKind.Sequential)]
public struct INPUT_RECORD
{
public InputEventType EventType;
public EVENT_UNION Event;
}

But, I can't translate to vb.net.

Anyway, thanks for all your help.

Mark

Mark - I'll try and translate this for you a little later on.
 
Hi Tom,

I think you may have already answered this question on another thread
from 1-28-04 http://www.mcse.ms/archive107-2004-1-337551.html

However, I'm having a heck of a time converting the c# code for the
INPUT_RECORD struct and so I can't write the DllImport stmt. Here's
the c# code you wrote a year ago :)

[StructLayout(LayoutKind.Sequential)]
public struct INPUT_RECORD
{
public InputEventType EventType;
public EVENT_UNION Event;
}

But, I can't translate to vb.net.

Anyway, thanks for all your help.

Mark
Oopps... I didn't think about it. Well, I have another idea - in the
delegate method, call the WriteConsoleInput API function to write your
default value to the console input buffer.
/\/\/

Ok... I'm working on my Linux box again tonight - so I can't actually
test this code, but here is a translation of this structure :)

Imports System.Runtime.InteropServices

....
<Flags ()> _
Public Enum ControlKeyState
RightAltPressed = &H1
LeftAltPressed = &H2
RightCtrlPressed = &H4
LeftCtrlPressed = &H8
ShiftPressed = &H10
NumLockOn = &H20
ScrollLockOn = &H40
CapsLockOn = &H80
EnhancedKey = &H100
End Enum

<Flags ()> _
Public Enum MouseButtonState
FromLeft1stButtonPressed = &H1
RightMostButtonPressed = &H2
FromLeft2ndButtonPressed = &H4,
FromLeft3rdButtonPressed = &H8,
FromLeft4thButtonPressed = &H10,
End Enum

<Flags ()> _
Public Enum MouseEventFlags
MouseMoved = &H1
DoubleClick = &H2
MouseWheeled = &H4
End Enum

Public Enum InputEventType As Short
KeyEvent = &H1
MouseEvent = &H2
WindowBufferSizeEvent = &H4
MenuEvent = &H8
FocusEvent = &H10
End Enum

<StructLayout (LayoutKind.Sequential)> _
Public Structure COORD
Public X As Short
Public Y As Short
End Structure

<StructLayout (LayoutKind.Sequential)> _
Public Structure SMALL_RECT
Public Left As Short
Public Top As Short
Public Right As Short
Public Bottom As Short
End Structure

<StructLayout (LayoutKind.Explicit)> _
Public Structure CONSOLE_CHAR
<FieldOffset (0)> Public AsciiChar As Byte
<FieldOffset (0)> Public UnicodeChar As Short
End Structure

<StructLayout (LayoutKind.Sequential)> _
Public Structure KEY_EVENT_RECORD
Public bKeyDown As Boolean
Public wRepeatCount As Short
Public wVirtualKeyCode As Short
Public wVirtualScanCode As Short
Public uChar As CONSOLE_CHAR
Public dwControlKeyState As ControlKeyState
End Structure

<StructLayout(LayoutKind.Sequential)> _
Public Structure MOUSE_EVENT_RECORD
Public dwMousePosition As COORD
Public dwButtonState As MouseButtonState
Public dwControlKeyState As ControlKeyState
Public dwEventFlags As MouseEventFlags
End Structure

<StructLayout(LayoutKind.Sequential)> _
Public Structure WINDOW_BUFFER_SIZE_RECORD
Public dwSize As COORD
End Structure

<StructLayout(LayoutKind.Sequential)> _
Public Structure MENU_EVENT_RECORD
Public dwCommandId As Integer
End Structure

<StructLayout(LayoutKind.Sequential)> _
Public Structure FOCUS_EVENT_RECORD
Public bSetFocus As Boolean
End Structure

<StructLayout(LayoutKind.Explicit)> _
Public Structure EVENT_UNION
<FieldOffset(0)> Public KeyEvent As KEY_EVENT_RECORD
<FieldOffset(0)> Public MouseEvent As MOUSE_EVENT_RECORD
<FieldOffset(0)> Public WindowBufferSizeEvent As WINDOW_BUFFER_SIZE_RECORD
<FieldOffset(0)> Public MenuEvent As MENU_EVENT_RECORD
<FieldOffset(0)> Public FocusEvent As FOCUS_EVENT_RECORD
End Structure

<StructLayout(LayoutKind.Sequential)> _
Public Structure INPUT_RECORD
Public EventType As InputEventType
Public [Event] As EVENT_UNION
End Structure

Whoooh! Anyway, I think this should be pretty close. Let me know if
you have any problems.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top