Updated completed unit tests for wincon.h and winbase.h console functions

pull/83/head
David Hall 2019-07-30 10:29:40 -06:00
parent 74e52ff4df
commit 99db4ab7eb
3 changed files with 587 additions and 51 deletions

View File

@ -1307,28 +1307,22 @@ namespace Vanara.PInvoke
// DWORD WINAPI GetConsoleProcessList( _Out_ LPDWORD lpdwProcessList, _In_ DWORD dwProcessCount );
[DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)]
[PInvokeData("Wincon.h", MSDNShortId = "")]
public static extern uint GetConsoleProcessList(SafeHeapBlock lpdwProcessList, uint dwProcessCount);
public static extern uint GetConsoleProcessList([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] uint[] lpdwProcessList, uint dwProcessCount);
/// <summary>Retrieves a list of the processes attached to the current console.</summary>
/// <returns>An array of process identifiers upon success.</returns>
public static uint[] GetConsoleProcessList()
{
var cnt = 8U;
using (var h = new SafeHeapBlock(Bytes()))
uint c = 8U, cnt;
uint[] output;
do
{
var c = GetConsoleProcessList(h, cnt);
while (c > cnt)
{
cnt = c;
h.Size = Bytes();
c = GetConsoleProcessList(h, cnt);
}
if (c == 0)
Win32Error.ThrowLastError();
return h.ToArray<uint>((int)c);
}
int Bytes() => Marshal.SizeOf(typeof(uint)) * (int)cnt;
cnt = c;
output = new uint[cnt];
c = GetConsoleProcessList(output, cnt);
} while (c > cnt);
Array.Resize(ref output, (int)c);
return output;
}
/// <summary>Retrieves information about the specified console screen buffer.</summary>
@ -2775,7 +2769,7 @@ namespace Vanara.PInvoke
public string FaceName;
/// <summary>Gets an empty structure value with the <see cref="cbSize"/> field initialized to the correct value.</summary>
public static readonly CONSOLE_FONT_INFOEX InitializedValue = new CONSOLE_FONT_INFOEX { cbSize = (uint)Marshal.SizeOf(typeof(CONSOLE_FONT_INFOEX)) };
public static readonly CONSOLE_FONT_INFOEX Default = new CONSOLE_FONT_INFOEX { cbSize = (uint)Marshal.SizeOf(typeof(CONSOLE_FONT_INFOEX)) };
}
/// <summary>Contains information about the console history.</summary>
@ -2798,7 +2792,7 @@ namespace Vanara.PInvoke
public uint dwFlags;
/// <summary>Gets an empty structure value with the <see cref="cbSize"/> field initialized to the correct value.</summary>
public static readonly CONSOLE_HISTORY_INFO InitializedValue = new CONSOLE_HISTORY_INFO { cbSize = (uint)Marshal.SizeOf(typeof(CONSOLE_HISTORY_INFO)) };
public static readonly CONSOLE_HISTORY_INFO Default = new CONSOLE_HISTORY_INFO { cbSize = (uint)Marshal.SizeOf(typeof(CONSOLE_HISTORY_INFO)) };
}
/// <summary>Contains information for a console read operation.</summary>
@ -2824,7 +2818,7 @@ namespace Vanara.PInvoke
public CONTROL_KEY_STATE dwControlKeyState;
/// <summary>Gets an empty structure value with the <see cref="nLength"/> field initialized to the correct value.</summary>
public static readonly CONSOLE_READCONSOLE_CONTROL InitializedValue = new CONSOLE_READCONSOLE_CONTROL { nLength = (uint)Marshal.SizeOf(typeof(CONSOLE_READCONSOLE_CONTROL)) };
public static readonly CONSOLE_READCONSOLE_CONTROL Default = new CONSOLE_READCONSOLE_CONTROL { nLength = (uint)Marshal.SizeOf(typeof(CONSOLE_READCONSOLE_CONTROL)) };
}
/// <summary>Contains information about a console screen buffer.</summary>
@ -2906,7 +2900,7 @@ namespace Vanara.PInvoke
public COLORREF[] ColorTable;
/// <summary>Gets an empty structure value with the <see cref="cbSize"/> field initialized to the correct value.</summary>
public static readonly CONSOLE_SCREEN_BUFFER_INFOEX InitializedValue = new CONSOLE_SCREEN_BUFFER_INFOEX { cbSize = (uint)Marshal.SizeOf(typeof(CONSOLE_SCREEN_BUFFER_INFOEX)) };
public static readonly CONSOLE_SCREEN_BUFFER_INFOEX Default = new CONSOLE_SCREEN_BUFFER_INFOEX { cbSize = (uint)Marshal.SizeOf(typeof(CONSOLE_SCREEN_BUFFER_INFOEX)) };
}
/// <summary>Contains information for a console selection.</summary>
@ -2973,15 +2967,18 @@ namespace Vanara.PInvoke
/// <summary>Initializes a new instance of the <see cref="COORD"/> struct.</summary>
/// <param name="x">The horizontal coordinate or column value.</param>
/// <param name="y">The vertical coordinate or row value.</param>
public COORD(short x, short y)
public COORD(int x, int y)
{
X = x;
Y = y;
X = (short)x;
Y = (short)y;
}
/// <summary>Converts to string.</summary>
/// <returns>A <see cref="string"/> that represents this instance.</returns>
public override string ToString() => $"X={X},Y={Y}";
/// <summary>Represents an empty instance of COORD with both X and Y values set to 0.</summary>
public static readonly COORD Empty = default(COORD);
}
/// <summary>

View File

@ -1,5 +1,8 @@
using NUnit.Framework;
using System;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using static Vanara.PInvoke.Kernel32;
namespace Vanara.PInvoke.Tests
@ -7,87 +10,622 @@ namespace Vanara.PInvoke.Tests
[TestFixture]
public class ConsoleTests
{
// TODO: Make ConsoleXXX functions work
// [Test]
[Test]
public void ConsoleTest()
{
// Taken from https://docs.microsoft.com/en-us/windows/console/reading-input-buffer-events
var hStdin = GetStdHandle(StdHandleType.STD_INPUT_HANDLE);
if (hStdin.IsInvalid) Win32Error.ThrowLastError();
var p = CSharpRunner.RunProcess(typeof(ConsoleTestProcess));
p.WaitForExit();
Assert.That(p.ExitCode, Is.Zero);
}
}
if (!GetConsoleMode(hStdin, out CONSOLE_INPUT_MODE fewSaveOldMode))
Win32Error.ThrowLastError();
public static class ConsoleTestProcess
{
public static HFILE hStdin, hStdout;
public static int Main()
{
// Get handles to STDIN and STDOUT.
hStdin = GetStdHandle(StdHandleType.STD_INPUT_HANDLE);
hStdout = GetStdHandle(StdHandleType.STD_OUTPUT_HANDLE);
if (hStdin == HFILE.INVALID_HANDLE_VALUE || hStdout == HFILE.INVALID_HANDLE_VALUE)
return ShowErr("GetStdHandle");
Aliases();
Attach();
ClearScreen();
GetSetWindowInfo();
IO();
Others();
ReadInputEvents();
RegCtrlHandler();
RWBlocks();
ScrollContent();
ScrollWindow();
return 0;
}
private static int Aliases()
{
if (!AddConsoleAlias("test", "expansion string", Assembly.GetEntryAssembly().Location))
return ShowErr("AddConsoleAlias");
var sb = new StringBuilder(256);
if (!GetConsoleAlias("test", sb, (uint)sb.Capacity, Assembly.GetEntryAssembly().Location))
return ShowErr("GetConsoleAlias");
foreach (var exe in GetConsoleAliasExes())
{
foreach (var alias in GetConsoleAliases(exe))
{
Console.WriteLine($"{exe} => {alias}");
}
}
return 0;
}
private static int Attach()
{
var pids = GetConsoleProcessList();
AttachConsole(pids[0]);
FreeConsole();
if (!AllocConsole())
return ShowErr("AllocConsole");
else
FreeConsole();
if (!AttachConsole(25412))
return ShowErr("AttachConsole");
else
FreeConsole();
AttachConsole(uint.MaxValue);
return 0;
}
// Adapted from https://docs.microsoft.com/en-us/windows/console/clearing-the-screen
private static int ClearScreen()
{
// Write some lines to the screen
byte[] lines = Encoding.ASCII.GetBytes("Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah\nBlah Blah Blah Blah Blah Blah Blah Blah Blah Blah\nBlah Blah Blah Blah");
if (!WriteFile(hStdout, lines, (uint)lines.Length, out _, default))
return ShowErr("WriteFile");
Sleep(1000);
// Get the number of character cells in the current buffer.
if (!GetConsoleScreenBufferInfo(hStdout, out var csbi))
return ShowErr("GetConsoleScreenBufferInfo");
// Fill the entire screen with blanks.
var dwConSize = (uint)(csbi.dwSize.X * csbi.dwSize.Y);
if (!FillConsoleOutputCharacter(hStdout, ' ', dwConSize, default, out _))
return ShowErr("FillConsoleOutputCharacter");
// Get the current text attribute.
if (!GetConsoleScreenBufferInfo(hStdout, out csbi))
return ShowErr("GetConsoleScreenBufferInfo");
// Set the buffer's attributes accordingly.
if (!FillConsoleOutputAttribute(hStdout, csbi.wAttributes, dwConSize, default, out _))
return ShowErr("FillConsoleOutputAttribute");
// Put the cursor at its home coordinates.
SetConsoleCursorPosition(hStdout, default);
Sleep(1000);
return 0;
}
private static int GetSetWindowInfo()
{
var sb = new StringBuilder(256);
if (GetConsoleTitle(sb, (uint)sb.Capacity) == 0)
return ShowErr("GetConsoleTitle");
if (!SetConsoleTitle("Test"))
return ShowErr("SetConsoleTitle");
if (GetConsoleOriginalTitle(sb, (uint)sb.Capacity) == 0)
return ShowErr("GetConsoleOriginalTitle");
if (!GetConsoleDisplayMode(out var mode))
return ShowErr("GetConsoleDisplayMode");
Console.WriteLine($"Display mode = {mode}");
var cp = GetConsoleCP();
if (!SetConsoleCP(cp))
return ShowErr("SetConsoleCP");
cp = GetConsoleOutputCP();
if (!SetConsoleOutputCP(cp))
return ShowErr("SetConsoleOutputCP");
if (!GetConsoleCursorInfo(hStdout, out var cursor))
return ShowErr("GetConsoleCursorInfo");
if (!SetConsoleCursorInfo(hStdout, cursor))
return ShowErr("SetConsoleCursorInfo");
if (!GetCurrentConsoleFont(hStdout, false, out var curFont))
return ShowErr("GetCurrentConsoleFont");
var curFontEx = CONSOLE_FONT_INFOEX.Default;
if (!GetCurrentConsoleFontEx(hStdout, false, ref curFontEx))
return ShowErr("GetCurrentConsoleFontEx");
var fsz = GetConsoleFontSize(hStdout, curFont.nFont);
if (fsz.X == 0 && fsz.Y == 0)
return ShowErr("GetConsoleFontSize");
if (!SetCurrentConsoleFontEx(hStdout, false, curFontEx))
return ShowErr("SetCurrentConsoleFontEx");
var hist = CONSOLE_HISTORY_INFO.Default;
if (!GetConsoleHistoryInfo(ref hist) || hist.NumberOfHistoryBuffers <= 0)
return ShowErr("GetConsoleHistoryInfo");
//hist.NumberOfHistoryBuffers++;
//if (!SetConsoleHistoryInfo(hist))
// return ShowErr("SetConsoleHistoryInfo");
var winSz = GetLargestConsoleWindowSize(hStdout);
if (winSz.X == 0 && winSz.Y == 0)
return ShowErr("GetConsoleHistoryInfo");
var hwnd = GetConsoleWindow();
if (hwnd.IsNull)
return ShowErr("GetConsoleWindow");
return 0;
}
// From https://docs.microsoft.com/en-us/windows/console/using-the-high-level-input-and-output-functions
private static int IO()
{
byte[] lpszPrompt1 = Encoding.ASCII.GetBytes("Type a line and press Enter, or q to quit: ");
byte[] lpszPrompt2 = Encoding.ASCII.GetBytes("Type any key, or q to quit: ");
var chBuffer = new byte[256];
// Save the current text colors.
if (!GetConsoleScreenBufferInfo(hStdout, out var csbiInfo))
return ShowErr("GetConsoleScreenBufferInfo");
// Get current info
var csbiInfoEx = CONSOLE_SCREEN_BUFFER_INFOEX.Default;
if (!GetConsoleScreenBufferInfoEx(hStdout, ref csbiInfoEx))
return ShowErr("GetConsoleScreenBufferInfo");
// Set the text attributes to draw red text on black background.
if (!SetConsoleTextAttribute(hStdout, CHARACTER_ATTRIBUTE.FOREGROUND_RED | CHARACTER_ATTRIBUTE.FOREGROUND_INTENSITY))
return ShowErr("SetConsoleTextAttribute");
// Increase the screen buffer size
if (!SetConsoleScreenBufferSize(hStdout, new COORD(csbiInfo.dwSize.X, (short)(csbiInfo.dwSize.Y + 10))))
return ShowErr("SetConsoleScreenBufferSize");
// Write to STDOUT and read from STDIN by using the default modes. Input is echoed automatically, and ReadFile does not return
// until a carriage return is typed.
//
// The default input modes are line, processed, and echo. The default output modes are processed and wrap at EOL.
while (true)
{
if (!WriteFile(hStdout, lpszPrompt1, (uint)lpszPrompt1.Length, out _, default))
return ShowErr("WriteFile");
if (!ReadFile(hStdin, chBuffer, (uint)chBuffer.Length, out _, default))
break;
if (chBuffer[0] == 'q')
break;
}
// Turn off the line input and echo input modes
if (!GetConsoleMode(hStdin, out CONSOLE_INPUT_MODE fdwOldMode))
return ShowErr("GetConsoleMode");
var fdwMode = fdwOldMode & ~(CONSOLE_INPUT_MODE.ENABLE_LINE_INPUT | CONSOLE_INPUT_MODE.ENABLE_ECHO_INPUT);
if (!SetConsoleMode(hStdin, fdwMode))
return ShowErr("SetConsoleMode");
// ReadFile returns when any input is available. WriteFile is used to echo input.
NewLine();
while (true)
{
if (!WriteFile(hStdout, lpszPrompt2, (uint)lpszPrompt2.Length, out _, default))
return ShowErr("WriteFile");
if (!ReadFile(hStdin, chBuffer, (uint)chBuffer.Length, out var cRead, default))
break;
if (chBuffer[0] == '\r')
NewLine();
else if (!WriteFile(hStdout, chBuffer, cRead, out _, default))
break;
else
NewLine();
if (chBuffer[0] == 'q') break;
}
// Restore the original settings.
SetConsoleScreenBufferInfoEx(hStdin, csbiInfoEx);
return 0;
// The NewLine function handles carriage returns when the processed input mode is disabled. It gets the current cursor position
// and resets it to the first cell of the next row.
void NewLine()
{
if (!GetConsoleScreenBufferInfo(hStdout, out csbiInfo))
{
ShowErr("GetConsoleScreenBufferInfo");
return;
}
// If it is the last line in the screen buffer, scroll the buffer up.
csbiInfo.dwCursorPosition.X = 0;
if (csbiInfo.dwSize.Y - 1 == csbiInfo.dwCursorPosition.Y)
ScrollScreenBuffer(hStdout, 1);
// Otherwise, advance the cursor to the next line.
else
csbiInfo.dwCursorPosition.Y += 1;
if (!SetConsoleCursorPosition(hStdout, csbiInfo.dwCursorPosition))
ShowErr("SetConsoleCursorPosition");
}
void ScrollScreenBuffer(HFILE h, short x)
{
var srctScrollRect = new SMALL_RECT(0, 1, (short)(csbiInfo.dwSize.X - x), (short)(csbiInfo.dwSize.Y - x));
// The destination for the scroll rectangle is one row up.
var coordDest = default(COORD);
// The clipping rectangle is the same as the scrolling rectangle. The destination row is left unchanged.
var srctClipRect = srctScrollRect;
// Set the fill character and attributes.
var chiFill = new CHAR_INFO(' ', CHARACTER_ATTRIBUTE.FOREGROUND_RED | CHARACTER_ATTRIBUTE.FOREGROUND_INTENSITY);
// Scroll up one line.
ScrollConsoleScreenBuffer(h, srctScrollRect, srctClipRect, coordDest, chiFill);
}
}
private static int Others()
{
FlushConsoleInputBuffer(hStdin);
var sb = new StringBuilder(256);
ReadConsole(hStdin, sb, 1, out var nRead); //, CONSOLE_READCONSOLE_CONTROL.Default);
var atts = new CHARACTER_ATTRIBUTE[256];
ReadConsoleOutputAttribute(hStdout, atts, (uint)atts.Length, default, out var ar);
ReadConsoleOutputCharacter(hStdout, sb, (uint)sb.Capacity, default, out var r);
WriteConsole(hStdout, "Fred", 4, out var wr);
WriteConsoleInput(hStdin, new[] { INPUT_RECORD.CreateKeyEventRecord(true, 9, 9, '\t') }, 1, out wr);
WriteConsoleOutputAttribute(hStdout, atts, ar, default, out wr);
WriteConsoleOutputCharacter(hStdout, "\b", 1, default, out wr);
return 0;
}
// Adapted from https://docs.microsoft.com/en-us/windows/console/reading-input-buffer-events
private static int ReadInputEvents()
{
if (!GetConsoleMode(hStdin, out CONSOLE_INPUT_MODE fdwSaveOldMode))
return ShowErr("GetConsoleMode");
try
{
if (!SetConsoleMode(hStdin, CONSOLE_INPUT_MODE.ENABLE_WINDOW_INPUT | CONSOLE_INPUT_MODE.ENABLE_MOUSE_INPUT))
Win32Error.ThrowLastError();
if (!SetConsoleMode(hStdin, CONSOLE_INPUT_MODE.ENABLE_WINDOW_INPUT | CONSOLE_INPUT_MODE.ENABLE_MOUSE_INPUT | CONSOLE_INPUT_MODE.ENABLE_EXTENDED_FLAGS))
return ShowErr("SetConsoleMode");
var counter = 0;
const int recCnt = 128;
while (counter++ <= 100)
if (!GetNumberOfConsoleMouseButtons(out var mouseBtn))
return ShowErr("GetNumberOfConsoleMouseButtons");
if (!GetConsoleSelectionInfo(out var selInfo))
return ShowErr("GetConsoleSelectionInfo");
const uint recCnt = 128;
var irInBuf = new INPUT_RECORD[recCnt];
while (true)
{
var irInBuf = new INPUT_RECORD[recCnt];
if (!GetNumberOfConsoleInputEvents(hStdin, out var evtNum))
return ShowErr("GetNumberOfConsoleInputEvents");
if (evtNum > 0)
{
PeekConsoleInput(hStdin, irInBuf, recCnt, out var pkread);
}
if (!ReadConsoleInput(hStdin, irInBuf, recCnt, out var cNumRead))
Win32Error.ThrowLastError();
return ShowErr("ReadConsoleInput");
for (var i = 0; i < cNumRead; i++)
{
Debug.WriteLine($"Seeing event {irInBuf[i].EventType}");
switch (irInBuf[i].EventType)
{
case EVENT_TYPE.KEY_EVENT:
TestContext.WriteLine($"Key event: {(irInBuf[i].Event.KeyEvent.bKeyDown ? "Pressed" : "Released")}");
Debug.WriteLine($"Key event: {(irInBuf[i].Event.KeyEvent.bKeyDown ? "Pressed" : "Released")} Key: {irInBuf[i].Event.KeyEvent.uChar} ({irInBuf[i].Event.KeyEvent.wVirtualKeyCode})");
if (irInBuf[i].Event.KeyEvent.uChar == 'q')
return 0;
break;
case EVENT_TYPE.MOUSE_EVENT:
MouseEventProc(irInBuf[i].Event.MouseEvent);
break;
case EVENT_TYPE.WINDOW_BUFFER_SIZE_EVENT:
TestContext.WriteLine($"Screen buffer is {irInBuf[i].Event.WindowBufferSizeEvent.dwSize.X} x {irInBuf[i].Event.WindowBufferSizeEvent.dwSize.Y}");
Debug.WriteLine($"Screen buffer is {irInBuf[i].Event.WindowBufferSizeEvent.dwSize}");
break;
case EVENT_TYPE.MENU_EVENT:
case EVENT_TYPE.FOCUS_EVENT:
break;
default:
throw new InvalidOperationException("Unknown event type.");
return ShowErr("Unknown event type.");
}
}
}
}
finally
{
SetConsoleMode(hStdin, fewSaveOldMode);
SetConsoleMode(hStdin, fdwSaveOldMode);
}
void MouseEventProc(MOUSE_EVENT_RECORD mouseEvent)
void MouseEventProc(in MOUSE_EVENT_RECORD mouseEvent)
{
TestContext.Write("Mouse event: ");
Debug.Write("Mouse event: ");
switch (mouseEvent.dwEventFlags)
{
case MOUSE_EVENT_FLAG.NONE:
if (mouseEvent.dwButtonState == MOUSE_BUTTON_STATE.FROM_LEFT_1ST_BUTTON_PRESSED)
TestContext.WriteLine("Left btn press");
Debug.WriteLine("Left btn press");
else if (mouseEvent.dwButtonState == MOUSE_BUTTON_STATE.RIGHTMOST_BUTTON_PRESSED)
TestContext.WriteLine("Right btn press");
Debug.WriteLine("Right btn press");
else
TestContext.WriteLine("Btn press");
Debug.WriteLine("Btn press");
break;
case MOUSE_EVENT_FLAG.DOUBLE_CLICK:
TestContext.WriteLine("Double click");
Debug.WriteLine("Double click");
break;
case MOUSE_EVENT_FLAG.MOUSE_HWHEELED:
TestContext.WriteLine("Horz mouse wheeled");
Debug.WriteLine("Horz mouse wheeled");
break;
case MOUSE_EVENT_FLAG.MOUSE_MOVED:
TestContext.WriteLine("Mouse moved");
Debug.WriteLine("Mouse moved");
break;
case MOUSE_EVENT_FLAG.MOUSE_WHEELED:
TestContext.WriteLine("Vert mouse wheeled");
Debug.WriteLine("Vert mouse wheeled");
break;
default:
TestContext.WriteLine("Unknown");
Debug.WriteLine("Unknown");
break;
}
}
}
// From https://docs.microsoft.com/en-us/windows/console/registering-a-control-handler-function
private static int RegCtrlHandler()
{
if (SetConsoleCtrlHandler(CtrlHandler, true))
{
Console.Write("\nThe Control Handler is installed.\n");
Console.Write("\n -- Now try pressing Ctrl+C or Ctrl+Break, or");
Console.Write("\n try logging off or closing the console...\n");
Console.Write("\n(...waiting in a loop for events...)\n\n");
if (!GenerateConsoleCtrlEvent(CTRL_EVENT.CTRL_BREAK_EVENT, 0))
return ShowErr("GenerateConsoleCtrlEvent failed");
while (true) Sleep(100);
}
else
{
Console.Write("\nERROR: Could not set control handler");
return 1;
}
bool CtrlHandler(CTRL_EVENT CtrlType)
{
switch (CtrlType)
{
// Handle the CTRL-C signal.
case CTRL_EVENT.CTRL_C_EVENT:
Console.Write("Ctrl-C event\n\n");
Beep(750, 300);
return true;
// CTRL-CLOSE: confirm that the user wants to exit.
case CTRL_EVENT.CTRL_CLOSE_EVENT:
Beep(600, 200);
Console.Write("Ctrl-Close event\n\n");
return true;
// Pass other signals to the next handler.
case CTRL_EVENT.CTRL_BREAK_EVENT:
Beep(900, 200);
Console.Write("Ctrl-Break event\n\n");
return false;
case CTRL_EVENT.CTRL_LOGOFF_EVENT:
Beep(1000, 200);
Console.Write("Ctrl-Logoff event\n\n");
return false;
case CTRL_EVENT.CTRL_SHUTDOWN_EVENT:
Beep(750, 500);
Console.Write("Ctrl-Shutdown event\n\n");
return false;
default:
return false;
}
}
}
// From https://docs.microsoft.com/en-us/windows/console/reading-and-writing-blocks-of-characters-and-attributes
private static int RWBlocks()
{
// Get a handle to the STDOUT screen buffer to copy from and create a new screen buffer to copy to.
var hNewScreenBuffer = CreateConsoleScreenBuffer(ACCESS_MASK.GENERIC_READ | ACCESS_MASK.GENERIC_WRITE, System.IO.FileShare.ReadWrite);
if (hNewScreenBuffer == HFILE.INVALID_HANDLE_VALUE)
return ShowErr("CreateConsoleScreenBuffer failed");
// Make the new screen buffer the active screen buffer.
if (!SetConsoleActiveScreenBuffer(hNewScreenBuffer))
return ShowErr("SetConsoleActiveScreenBuffer failed");
// Set the source rectangle.
var srctReadRect = new SMALL_RECT(0, 0, 79, 1);
// The temporary buffer size is 2 rows x 80 columns.
var coordBufSize = new COORD(2, 80);
// The top left destination cell of the temporary buffer is row 0, col 0.
var coordBufCoord = new COORD();
// Copy the block from the screen buffer to the temp. buffer.
var chiBuffer = new CHAR_INFO[coordBufSize.X * coordBufSize.Y];
if (!ReadConsoleOutput(hStdout, chiBuffer, coordBufSize, coordBufCoord, ref srctReadRect))
return ShowErr("ReadConsoleOutput failed");
// Set the destination rectangle.
var srctWriteRect = new SMALL_RECT(0, 10, 79, 11);
// Copy from the temporary buffer to the new screen buffer.
if (!WriteConsoleOutput(hNewScreenBuffer, chiBuffer, coordBufSize, coordBufCoord, ref srctWriteRect))
return ShowErr("WriteConsoleOutput failed");
Sleep(5000);
// Restore the original active screen buffer.
if (!SetConsoleActiveScreenBuffer(hStdout))
return ShowErr("SetConsoleActiveScreenBuffer failed");
return 0;
}
// From https://docs.microsoft.com/en-us/windows/console/scrolling-a-screen-buffer-s-contents
private static int ScrollContent()
{
Console.Write("\nPrinting 20 lines for reference. Notice that line 6 is discarded during scrolling.\n");
for (var i = 0; i <= 20; i++)
Console.Write($"{i}\n");
Sleep(1000);
// Get the screen buffer size.
if (!GetConsoleScreenBufferInfo(hStdout, out var csbiInfo))
return ShowErr("GetConsoleScreenBufferInfo");
// The scrolling rectangle is the bottom 15 rows of the screen buffer.
var srctScrollRect = new SMALL_RECT(0, (short)(csbiInfo.dwSize.Y - 16), (short)(csbiInfo.dwSize.X - 1), (short)(csbiInfo.dwSize.Y - 1));
// The destination for the scroll rectangle is one row up.
var coordDest = new COORD(0, (short)(csbiInfo.dwSize.Y - 17));
// The clipping rectangle is the same as the scrolling rectangle. The destination row is left unchanged.
var srctClipRect = srctScrollRect;
// Fill the bottom row with green blanks.
var chiFill = new CHAR_INFO(' ', CHARACTER_ATTRIBUTE.BACKGROUND_GREEN | CHARACTER_ATTRIBUTE.FOREGROUND_RED);
// Scroll up one line.
if (!ScrollConsoleScreenBuffer(hStdout, srctScrollRect, srctClipRect, coordDest, chiFill))
return ShowErr("ScrollConsoleScreenBuffer");
Sleep(1000);
return 0;
}
// From https://docs.microsoft.com/en-us/windows/console/scrolling-a-screen-buffer-s-window
private static int ScrollWindow()
{
Console.Write("\nPrinting twenty lines, then scrolling up five lines.\n");
Console.Write("Press any key to scroll up ten lines; then press another key to stop the demo.\n");
for (var i = 0; i <= 20; i++)
Console.Write($"{i}\n");
if (ScrollByAbsoluteCoord(5) > 0)
Console.ReadKey();
else
return ShowErr("ScrollByAbsoluteCoord");
if (ScrollByRelativeCoord(10) > 0)
Console.ReadKey();
else
return ShowErr("ScrollByRelativeCoord");
return 0;
int ScrollByAbsoluteCoord(short iRows)
{
// Get the current screen buffer size and window position.
if (!GetConsoleScreenBufferInfo(hStdout, out var csbiInfo))
{
ShowErr("GetConsoleScreenBufferInfo");
return 0;
}
// Set srctWindow to the current window size and location.
var srctWindow = csbiInfo.srWindow;
// Check whether the window is too close to the screen buffer top
if (srctWindow.Top >= iRows)
{
srctWindow.Top -= iRows; // move top up
srctWindow.Bottom -= iRows; // move bottom up
if (!SetConsoleWindowInfo(hStdout, true, srctWindow))
{
ShowErr("SetConsoleWindowInfo");
return 0;
}
return iRows;
}
else
{
ShowMsg("\nCannot scroll; the window is too close to the top.\n");
return 0;
}
}
int ScrollByRelativeCoord(short iRows)
{
// Get the current screen buffer window position.
if (!GetConsoleScreenBufferInfo(hStdout, out var csbiInfo))
{
ShowErr("GetConsoleScreenBufferInfo");
return 0;
}
// Check whether the window is too close to the screen buffer top
if (csbiInfo.srWindow.Top >= iRows)
{
var srctWindow = new SMALL_RECT(0, (short)-iRows, 0, (short)-iRows);
if (!SetConsoleWindowInfo(hStdout, false, srctWindow))
{
ShowErr("SetConsoleWindowInfo");
return 0;
}
return iRows;
}
else
{
ShowMsg("\nCannot scroll; the window is too close to the top.\n");
return 0;
}
}
}
private static int ShowErr(string msg, Win32Error? err = null)
{
if (!err.HasValue) err = Win32Error.GetLastError();
ShowMsg(msg + "\r\n" + err.ToString());
return (int)err;
}
private static void ShowMsg(string msg)
{
msg = "======================================================\r\n" + msg + "\r\n======================================================\r\n";
var bmsg = Encoding.ASCII.GetBytes(msg);
WriteFile(hStdout, bmsg, (uint)bmsg.Length, out _, default);
var inBuf = new byte[2];
ReadFile(hStdin, inBuf, (uint)inBuf.Length, out _, default);
}
}
}

View File

@ -48,6 +48,7 @@
<Compile Include="AppModelTests.cs" />
<Compile Include="InterlockedApiTests.cs" />
<Compile Include="InteropServices\SafeLocalHandleTests.cs" />
<Compile Include="WinNlsTests.cs" />
<Compile Include="WinBase.UmsTests.cs" />
<Compile Include="WinBase.TxFTests.cs" />
<Compile Include="WinBase.TimeTests.cs" />