7
\$\begingroup\$

This is based on a library called Consoledefender V 3.3.

I made a minimal class for personal usage based on this library by using C++11. It actually takes user input and prints it back on the console screen and acts like an echo. I would like this class reviewed.

Console.h

#include <windows.h>
#include <conio.h>
#include <sstream>
#include <vector>

class Console
{
public:

    Console();
    ~Console();

    template<typename T>
    bool getinput(T& rNum, const short maxlen) const;

    int wait(const std::string& rStrMessage = "Press any key to continue . . . ", const unsigned timeout = INFINITE) const;

    bool setfontsize(short& rWidth, short& rHeight) const;
    bool title(const std::string& rStrTitle) const;
    bool resize(const short width, const short height) const;
    bool move(const int left, const int top) const;

private:
    static unsigned __stdcall key(void *ch);
    DWORD getmode() const;
    CONSOLE_SCREEN_BUFFER_INFO getscreenbufferinfo() const;
    COORD getfontsize() const;
    WINDOWPLACEMENT getplacement() const;

    const HWND mHwndCon;
    const HANDLE mOut, mIn;
    const DWORD mMode;
    const CONSOLE_SCREEN_BUFFER_INFO mConsoleSceenBufferInfo;
    const COORD mFontsSize;
    const WINDOWPLACEMENT mWindowPlacemet;
};

template<typename T>
inline
bool Console::getinput(T& rStr, const short maxlen) const
{
    return false;
}

template <>
inline
bool Console::getinput(int& rNum, const short maxlen) const
{
    short len = 0, i = 0;
    int numtmp = 0;
    int ch = 0;
    std::string str;
    CONSOLE_SCREEN_BUFFER_INFO csbi = { 0 };
    COORD coordBufSize = { maxlen, 1 };
    COORD coordBufCoord = { 0, 0 };

    if (maxlen < 1 || !GetConsoleScreenBufferInfo(mOut, &csbi))
    {
        return false;
    }

    std::vector<CHAR_INFO> chiVec(maxlen);

    SMALL_RECT srctRect = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, static_cast<short>(csbi.dwCursorPosition.X + maxlen), csbi.dwCursorPosition.Y };
    COORD crdCur = csbi.dwCursorPosition;

    if (!ReadConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect))
    {
        return false;
    }

    for (auto& i : chiVec)
    {
        i.Char.AsciiChar = 0;
    }

    if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect))
    {
        return false;
    }

    while (ch != 13)
    {
        ch = _getch();
        if (ch == 0 || ch == 224)
        {
            ch = 256 + _getch();
        }

        if (len < maxlen)
        {
            if ((ch == static_cast<int>('-') && len == 0)
                || (ch == static_cast<int>('+') && len == 0)
                || (ch >= static_cast<int>('0') && ch <= static_cast<int>('9')))
            {
                str += static_cast<char>(ch);
                chiVec.at(len).Char.AsciiChar = ch;
                ++len;
                ++crdCur.X;
                if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect)
                    || !SetConsoleCursorPosition(mOut, crdCur))
                {
                    return false;
                }
            }
        }
        if (ch == static_cast<int>('\b') && len > 0)
        {
            str = str.substr(0, str.size() - 1);
            --len;
            --crdCur.X;
            chiVec.at(len).Char.AsciiChar = 0;

            if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect)
                || !SetConsoleCursorPosition(mOut, crdCur))
            {
                return false;
            }
        }
    }
    if (!(std::istringstream(str) >> numtmp).fail())
    {
        rNum = numtmp;
        return true;
    }
    return false;
}

template <>
inline
bool Console::getinput(unsigned& rNum, const short maxlen) const
{
    short len = 0, i = 0;
    unsigned numtmp = 0;
    int ch = 0;
    std::string str;
    CONSOLE_SCREEN_BUFFER_INFO csbi = { 0 };
    COORD coordBufSize = { maxlen, 1 };
    COORD coordBufCoord = { 0, 0 };

    if (maxlen < 1 || !GetConsoleScreenBufferInfo(mOut, &csbi))
    {
        return false;
    }

    std::vector<CHAR_INFO> chiVec(maxlen);
    SMALL_RECT srctRect = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, static_cast<short>(csbi.dwCursorPosition.X + maxlen), csbi.dwCursorPosition.Y };
    COORD crdCur = csbi.dwCursorPosition;

    if (!ReadConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect))
    {
        return false;
    }

    for (auto& i : chiVec)
    {
        i.Char.AsciiChar = 0;
    }

    if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect))
    {
        return false;
    }

    while (ch != 13)
    {
        ch = _getch();
        if (ch == 0 || ch == 224)
        {
            ch = 256 + _getch();
        }

        if (len < maxlen)
        {
            if ((ch == static_cast<int>('+') && len == 0)
                || (ch >= static_cast<int>('0') && ch <= static_cast<int>('9')))
            {
                str += static_cast<char>(ch);
                chiVec.at(len).Char.AsciiChar = ch;
                ++len;
                ++crdCur.X;

                if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect)
                    || !SetConsoleCursorPosition(mOut, crdCur))
                {
                    return false;
                }
            }
        }

        if (ch == static_cast<int>('\b') && len > 0)
        {
            str = str.substr(0, str.size() - 1);
            --len;
            --crdCur.X;
            chiVec.at(len).Char.AsciiChar = 0;

            if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect)
                || !SetConsoleCursorPosition(mOut, crdCur))
            {
                return false;
            }
        }
    }
    if (!(std::istringstream(str) >> numtmp).fail())
    {
        rNum = numtmp;
        return true;
    }
    return false;
}

template <>
inline
bool Console::getinput(float& rNum, const short maxlen) const
{
    short len = 0, i = 0, sign = -1, fpoint = -1, exp = -1;
    float numtmp = 0;
    int ch = 0;
    std::string str;
    CONSOLE_SCREEN_BUFFER_INFO csbi = { 0 };
    COORD coordBufSize = { maxlen, 1 };
    COORD coordBufCoord = { 0, 0 };

    if (maxlen < 1 || !GetConsoleScreenBufferInfo(mOut, &csbi))
    {
        return false;
    }

    std::vector<CHAR_INFO> chiVec(maxlen);
    SMALL_RECT srctRect = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, static_cast<short>(csbi.dwCursorPosition.X + maxlen), csbi.dwCursorPosition.Y };
    COORD crdCur = csbi.dwCursorPosition;

    if (!ReadConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect))
    {
        return false;
    }

    for (auto& i : chiVec)
    {
        i.Char.AsciiChar = 0;
    }

    if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect))
    {
        return false;
    }

    while (ch != 13)
    {
        ch = _getch();
        if (ch == 0 || ch == 224)
        {
            ch = 256 + _getch();
        }

        if (len < maxlen)
        {
            if ((ch == static_cast<int>('-') && len - 1 == exp)
                || (ch == static_cast<int>('+') && len - 1 == exp)
                || (ch >= static_cast<int>('0') && ch <= static_cast<int>('9'))
                || (ch == static_cast<int>('.') && fpoint == -1 && exp == -1)
                || ((ch == static_cast<int>('e') || ch == static_cast<int>('E')) && exp == -1 && len > 0 && !(len == 1 && !sign)))
            {
                str += static_cast<char>(ch);
                chiVec.at(len).Char.AsciiChar = ch;
                if (ch == static_cast<int>('-') && !len)
                {
                    sign = 0;
                }
                else if (ch == static_cast<int>('+') && !len)
                {
                    sign = 0;
                }
                else if (ch == static_cast<int>('.'))
                {
                    fpoint = len;
                }
                else if (ch == static_cast<int>('e') || ch == static_cast<int>('E'))
                {
                    exp = len;
                }

                ++len;
                ++crdCur.X;

                if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect)
                    || !SetConsoleCursorPosition(mOut, crdCur))
                {
                    return false;
                }
            }
        }

        if (ch == static_cast<int>('\b') && len > 0)
        {
            str = str.substr(0, str.size() - 1);
            --len;
            --crdCur.X;
            chiVec.at(len).Char.AsciiChar = 0;

            if (len == sign)
            {
                sign = -1;
            }
            else if (len == fpoint)
            {
                fpoint = -1;
            }
            else if (len == exp)
            {
                exp = -1;
            }

            if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect)
                || !SetConsoleCursorPosition(mOut, crdCur))
            {
                return false;
            }
        }
    }
    if (!(std::istringstream(str) >> numtmp).fail())
    {
        rNum = numtmp;
        return true;
    }
    return false;
}

template <>
inline
bool Console::getinput(std::string& rStr, const short maxlen) const
{
    short len = 0, i = 0;
    std::string strtmp;
    int ch = 0;
    CONSOLE_SCREEN_BUFFER_INFO csbi = { 0 };
    COORD coordBufSize = { maxlen, 1 };
    COORD coordBufCoord = { 0, 0 };

    if (maxlen < 1 || !GetConsoleScreenBufferInfo(mOut, &csbi))
    {
        return false;
    }

    std::vector<CHAR_INFO> chiVec(maxlen);
    SMALL_RECT srctRect = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, static_cast<short>(csbi.dwCursorPosition.X + maxlen), csbi.dwCursorPosition.Y };
    COORD crdCur = csbi.dwCursorPosition;

    if (!ReadConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect))
    {
        return false;
    }

    for (auto& i : chiVec)
    {
        i.Char.AsciiChar = 0;
    }

    if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect))
    {
        return false;
    }

    while (ch != 13)
    {
        ch = _getch();

        if (ch == 0 || ch == 224)
        {
            ch = 256 + _getch();
        }
        if (len < maxlen)
        {
            if (ch > 31 && ch < 256)
            {
                strtmp += static_cast<char>(ch);
                chiVec.at(len).Char.AsciiChar = ch;
                ++len;
                ++crdCur.X;

                if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect)
                    || !SetConsoleCursorPosition(mOut, crdCur))
                {
                    return false;
                }
            }
        }
        if (ch == static_cast<int>('\b') && len > 0)
        {
            strtmp = strtmp.substr(0, strtmp.size() - 1);
            --len;
            --crdCur.X;
            chiVec.at(len).Char.AsciiChar = 0;

            if (!WriteConsoleOutputA(mOut, &chiVec.front(), coordBufSize, coordBufCoord, &srctRect)
                || !SetConsoleCursorPosition(mOut, crdCur))
            {
                return false;
            }
        }
    }

    if (!strtmp.empty())
    {
        rStr = strtmp;
        return true;
    }
    return false;
}

Console.cpp

#include "Console.h"
#include <process.h>
#include <iostream>
#include <deque>
#include <array>
#include <stdexcept>

Console::Console()
    : mHwndCon(GetConsoleWindow())
    , mOut(GetStdHandle(STD_OUTPUT_HANDLE))
    , mIn(GetStdHandle(STD_INPUT_HANDLE))
    , mMode(getmode())
    , mConsoleSceenBufferInfo(getscreenbufferinfo())
    , mFontsSize(getfontsize())
    , mWindowPlacemet(getplacement())
{
    if (!mHwndCon
        || mOut == INVALID_HANDLE_VALUE
        || mIn == INVALID_HANDLE_VALUE)
        throw std::runtime_error("Failed to construct winconshelper.");
}

Console::~Console()
{
    CONSOLE_FONT_INFOEX cfix = { 0 };
    cfix.cbSize = sizeof(CONSOLE_FONT_INFOEX);
    if (mMode != static_cast<DWORD>(-1)
        && mConsoleSceenBufferInfo.dwMaximumWindowSize.X
        && mFontsSize.X
        && mFontsSize.Y
        && mWindowPlacemet.length
        && GetCurrentConsoleFontEx(mOut, FALSE, &cfix))
    {
        SetConsoleMode(mIn, mMode);
        MoveWindow(mHwndCon,
            mWindowPlacemet.rcNormalPosition.left,
            mWindowPlacemet.rcNormalPosition.top,
            mWindowPlacemet.rcNormalPosition.left + 1,
            mWindowPlacemet.rcNormalPosition.top + 1, TRUE);
        cfix.dwFontSize = mFontsSize;
        SetCurrentConsoleFontEx(mOut, FALSE, &cfix);
        SetConsoleScreenBufferSize(mOut, mConsoleSceenBufferInfo.dwSize);
        SetConsoleWindowInfo(mOut, FALSE, &mConsoleSceenBufferInfo.srWindow);
        SetWindowPlacement(mHwndCon, &mWindowPlacemet);
    }
}

int Console::wait(const std::string& rStrMessage, const unsigned timeout) const
{
    int ch = 0;
    HANDLE hThread = NULL;

    std::cout << rStrMessage;
    FlushConsoleInputBuffer(mIn);

    if ((hThread = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, key, static_cast<void*>(&ch), 0, NULL))))
    {
        if (WaitForSingleObject(hThread, timeout) == WAIT_TIMEOUT)
        {
            TerminateThread(hThread, 0);
        }

        CloseHandle(hThread);
    }

    std::cout << '\n';
    FlushConsoleInputBuffer(mIn);
    return ch;
}
unsigned __stdcall Console::key(void *pch)
{
    int ch = 0;
    ch = _getch();

    if (ch == 0 || ch == 224)
    {
        ch = 256 + _getch();
    }

    *(static_cast<int*>(pch)) = ch;
    _endthreadex(0);
    return 0;
}

DWORD Console::getmode() const
{
    DWORD tmpmode = 0;
    return (GetConsoleMode(mIn, &tmpmode)) ? tmpmode : -1;
}

CONSOLE_SCREEN_BUFFER_INFO Console::getscreenbufferinfo() const
{
    CONSOLE_SCREEN_BUFFER_INFO temp = { 0 };

    if (GetConsoleScreenBufferInfo(mOut, &temp))
    {
        return temp;
    }

    temp.dwMaximumWindowSize.X = 0;

    return temp;
}

COORD Console::getfontsize() const
{
    COORD tempfontsize = { 0, 0 };

    CONSOLE_FONT_INFOEX cfix = { 0 };

    cfix.cbSize = sizeof(CONSOLE_FONT_INFOEX);

    if (GetCurrentConsoleFontEx(mOut, FALSE, &cfix))
    {
        tempfontsize = GetConsoleFontSize(mOut, cfix.nFont);
    }

    return tempfontsize;
}

WINDOWPLACEMENT Console::getplacement() const
{
    WINDOWPLACEMENT temp = { 0 };

    temp.length = sizeof(WINDOWPLACEMENT);

    if (GetWindowPlacement(mHwndCon, &temp))
    {
        return temp;
    }

    temp.length = 0;

    return temp;
}

bool Console::title(const std::string& rStrTitle) const
{
    return (SetConsoleTitleA(rStrTitle.c_str())) ? true : false;
}

bool Console::resize(const short width, const short height) const
{
    long lHeight = 0, lWidth = 0;
    HMONITOR hMonitor = NULL;
    MONITORINFO mi = { 0 };
    CONSOLE_FONT_INFO cfi = { 0 };
    COORD crdfontsize = { 0, 0 };
    COORD coord = { 0, 0 };
    COORD crdbuf = { width, height };

    hMonitor = MonitorFromWindow(mHwndCon, MONITOR_DEFAULTTONEAREST);
    mi.cbSize = sizeof(MONITORINFO);

    if (!GetMonitorInfo(hMonitor, &mi)
        || !GetCurrentConsoleFont(mOut, FALSE, &cfi))
    {
        return false;
    }

    crdfontsize = GetConsoleFontSize(mOut, cfi.nFont);

    lWidth = width * crdfontsize.X + 2 * (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXBORDER));
    lHeight = height * crdfontsize.Y + 2 * (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYBORDER)) + GetSystemMetrics(SM_CYCAPTION);

    if (crdfontsize.X == 0
        || crdfontsize.Y == 0
        || lWidth > (mi.rcWork.right - mi.rcWork.left)
        || lHeight > (mi.rcWork.bottom - mi.rcWork.top)
        || lWidth < GetSystemMetrics(SM_CXMIN)
        || lHeight < GetSystemMetrics(SM_CYMIN))
    {
        return false;
    }

    coord.X = static_cast<short>((mi.rcWork.right - mi.rcWork.left) / crdfontsize.X);
    coord.Y = static_cast<short>((mi.rcWork.bottom - mi.rcWork.top) / crdfontsize.Y);

    if (SetConsoleScreenBufferSize(mOut, coord)
        && SetWindowPos(mHwndCon, NULL, 0, 0, lWidth, lHeight, SWP_NOZORDER | SWP_NOMOVE)
        && SetConsoleScreenBufferSize(mOut, crdbuf))
    {
        SetWindowPos(mHwndCon, NULL, 0, 0, lWidth + 50, lHeight + 50, SWP_NOZORDER | SWP_NOMOVE);
        return true;
    }

    return false;
}

bool Console::move(const int left, const int top) const
{
    return SetWindowPos(mHwndCon, NULL, left, top, 0, 0, SWP_NOZORDER | SWP_NOSIZE) ? true : false;
}

test.cpp

#include <iostream>
#include <string>
#include <stdexcept>

#include "Console.h"


int main()
{
    try
    {
        Console console;

        // Put window in the upper left corner of the screen
        console.move(0, 0);

        // Window size to a width of 100 and a height of 30 character Cells
        console.resize(100, 30);

        // Change Window Title
        console.title("Test");

        // input test for float number
        std::cout << "enter the number: ";
        float f = 0.f;
        console.getinput(f, 5);
        std::cout << "\ninput was " << f << "\n\n";

        // input test for int number
        std::cout << "enter the int number: ";
        int i = 0;
        console.getinput(i, 7);
        std::cout << "\ninput was " << i << "\n\n";

        // input test for string
        std::cout << "enter your full name: ";
        std::string str;
        console.getinput(str, 79);
        std::cout << "\ninput was " << str << "\n\n";
        console.wait();

        return 0;
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
    catch (...)
    {
        std::cerr << "An error occured." << std::endl;
    }
    return 1;
}
\$\endgroup\$
5
\$\begingroup\$

A suggestion on architecture

I like the idea of your code and think it could be useful, however, it is very Windows specific, which limits its uses. The first thing I would think about, after putting the class together, would be separating the platform specific code from the portable code. The simplest and most straightforward way of doing this is by defining an interface class. Console should be your interface. It might even have some methods implemented, it doesn't necessarily has to be a pure virtual interface, it could, for instance, make use of the template method pattern. Just make sure that you extract the platform specific code to a Win32Console or such class. No need to implement a UnixConsole now if you don't need one, just leave the code ready in case someone wants to expand on in the future (that might include yourself!).

Code review

Now focusing on the presented code:

  1. I don't like the idea of getinput() as a template. You have provided four specializations of the function and left the generic one unimplemented and returning a false flag. You could have simply declared four overloads of the getinput() method for each type you wish to handle. In this case, calling the function for an unhandled type would produce a compiler error instead of an ambiguous runtime error.

  2. Still talking about getinput(), it seems that the four overloads of this method share a lot of common code. The 10 first lines or so of each function seem identical. I would try to consolidate that repeated code into helper methods ASAP, if you haven't done this already. One possibility would be having a low-level helper that operates on a byte array and reads a number of bytes (e.g.: getInput(unsigned char * data, std::size_t numBytes). Then this low level function can be called with a buffer and the result reinterpreted into the proper type. This is much like a de-serialization routine, so it would be fine for a low-level helper to perform some unsafe type reinterpretation inside a safer type-aware method and in a controlled environment.

  3. Looking inside the getinput() methods, you have several variables declared at the top of the functions. Remember to always declare variables as close to the first usage as possible. Also remember to apply const if it makes sense.

  4. Again, inside the getinputs, instead of using &chiVec.front() to access a pointer to the underlaying data of a vector, use the data() method (C++11).

  5. std::istringstream is fine for number parsing, but it is a bit verbose and perhaps not so obvious. You can use the simpler std::stoi()/std::stof() for this task now.

  6. You have a few hardcoded constants in some places, which are not obvious. Once again an excerpt from one of the getinputs:

    while (ch != 13)
    {
        ch = _getch();
        if (ch == 0 || ch == 224)
        {
            ch = 256 + _getch();
        }
    

    A comment would be adequate here. 13 could be replaced by its ASCII character literal, making it self-documented.

  7. All the static_cast<int>('+') (there are several of these) are not necessary at all. A char is automatically promoted to int, so the comparisons don't require the type cast.

  8. Instead of using the wacky <process.h> API from Windows, switch to the Standard <thread> library. No porting effort there once you make the switch.

  9. I'm not particularly fond of the tightlypacked naming convention. An _ between words or camelCasing would read easier to my eyes.

  10. mIn / mOut are vague names. Call them properly by their functions: std-in and std-out.

  11. Lastly, looking at your usage example:

        Console console;
    
        // Put window in the upper left corner of the screen
        console.move(0, 0);
    
        // Window size to a width of 100 and a height of 30 character Cells
        console.resize(100, 30);
    
        // Change Window Title
        console.title("Test");
    

    You are setting several parameters thru method calls soon after the creation of a Console. You should also offer the user the possibility of passing these configuration parameters as constructor arguments. And keep the empty constructor to create a default Console for the "lazy" user.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.