Вставить cmd в приложение Win32 и текст не выбирается - PullRequest
0 голосов
/ 26 апреля 2018

Я пишу как графический интерфейс, так и консольную программу. Для консоли я использую цветной вывод, например \ 33 [0m . Для GUI мне нужно написать больше кода, если я переключаюсь на другую библиотеку GUI, мне нужно переписать код. У некоторой простой библиотеки (которую я сейчас использую) даже нет API, настраивающего цвет текста Поэтому я стараюсь использовать cmd в качестве вывода для всего приложения.

Проблема в том, что я не могу выделить текст в консоли, даже если для консоли я установил Режим QuickEdit по умолчанию.

Код: (если cmd не появляется, измените размер главного окна, и оно должно появиться)

#include <windows.h>

#include <iostream>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nShowCmd)
{
    static TCHAR lpszAppName[] = TEXT("HelloWin");
    HWND      hwnd;
    MSG       msg;
    WNDCLASS  wc;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = lpszAppName;

    if (!RegisterClass(&wc))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"),
            lpszAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(lpszAppName,
        TEXT("The Hello Program"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL);

    ShowWindow(hwnd, nShowCmd);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    switch (message)
    {
    case WM_CREATE:
        {
            using namespace std;
            AllocConsole();
            freopen("CONOUT$", "w", stdout); // redirect std::cout to console
            cout << "test console" << endl;

            // get console handle
            HWND console = GetConsoleWindow();
            SetParent(console, hwnd);
            SetWindowLong(console, GWL_STYLE, WS_CHILD | WS_VISIBLE);
            // ShowWindow(console, SW_MAXIMIZE);

            DWORD prev_mode;
            GetConsoleMode(console, &prev_mode); 
            SetConsoleMode(console, prev_mode | ENABLE_QUICK_EDIT_MODE);
            cout << "aaaaaaaaaa" << endl;
            cout << "aaaaaaaaaa" << endl;
            cout << "aaaaaaaaaa" << endl;
            cout << "aaaaaaaaaa" << endl;
        }
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

Как сделать его доступным для выбора? И есть ли другое альтернативное консольное приложение, которое можно использовать для встраивания в мое приложение? (Я попробовал ConEmu, но не повезло)

Ответы [ 2 ]

0 голосов
/ 01 мая 2018

SetParent документация гласит, что если вы используете эту функцию с другим процессом, вы должны синхронизировать UISTATE для обеих Windows:

Когда вы меняете родителя окна, вы должны синхронизировать UISTATE обоих окон. Для получения дополнительной информации см. WM_CHANGEUISTATE и WM_UPDATEUISTATE.

Но у вас нет доступа к циклу сообщений консоли. Есть две петли сообщений, и Windows должна заблокировать некоторые сообщения. Сразу же вы видите проблемы с фокусом и живописью. Окно консоли не фокусируется, когда вы нажимаете на него, или оно не окрашивается. Использование WS_CLIPCHILDREN улучшит рисование. Чтобы перенаправить фокус, вы должны вызвать SetForeground(console) и SetFocus(console) из своего собственного окна (это должно быть сделано после возврата WM_CREATE, вы можете обработать это, например, в WM_LBUTTONDOWN или с PostMessage), но затем Вы сталкиваетесь с еще большими проблемами. Даже если бы у вас был доступ к другому процессу, это было бы нелегко. Синхронизация потоков достаточно сложна, синхронизация процессов будет хуже.

См. Также: Законно ли иметь межпроцессные отношения между родителем / ребенком или владельцем / владельцем?
https://blogs.msdn.microsoft.com/oldnewthing/20130412-00/?p=4683

У вас есть более простые варианты. Вы можете немного изменить свой код для записи в std::ostringstream и вставить поток для редактирования элемента управления или перенаправить cout в элемент управления для редактирования.

В приведенном ниже примере используется элемент управления RichEdit для поддержки цвета и стиля шрифта, в основном на основе кодировки Bash:

#include <sstream>
#include <string>
#include <iomanip>
#include <Windows.h>
#include <Richedit.h>

class my_stream
{
    HWND hedit;
public:
    std::wostringstream oss;

    HWND create(HWND hwnd, int x, int y, int w, int h, HINSTANCE hinst, int menu_id)
    {
        //create rich edit control
        LoadLibrary(L"Msftedit.dll");
        hedit = CreateWindow(MSFTEDIT_CLASS, 0,
            ES_READONLY | ES_MULTILINE | WS_CHILD | WS_VISIBLE, x, y, w, h, 
            hwnd, HMENU(menu_id), NULL, NULL);

        //default background color
        SendMessage(hedit, EM_SETBKGNDCOLOR, 0, (LPARAM)RGB(0, 0, 0));

        //default text color
        CHARFORMAT cf = { sizeof(cf) };
        cf.dwMask = CFM_COLOR | CFM_FACE | CFM_SIZE;
        cf.yHeight = 220;
        cf.crTextColor = RGB(255, 255, 255);
        //Consolas font is available since Vista
        wcscpy_s(cf.szFaceName, _countof(cf.szFaceName), L"Consolas"); 
        SendMessage(hedit, EM_SETSEL, (WPARAM)0, (LPARAM)-1);
        SendMessage(hedit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
        return hedit;
    }

    template<typename T>
    my_stream& operator<<(const T& rhs)
    {
        //write to stream
        oss.str(L"");
        oss << rhs;
        std::wstring s = oss.str();

        if(s.find(L"\033[") == 0)
        {
            bool bold = false;
            if(s.find(L"\033[1") == 0)
            {
                bold = true;
                s[2] = L'0';
            }

            COLORREF color = RGB(255, 255, 255);
            if(s == L"\033[0m") color = RGB(255, 255, 255);
            if(s == L"\033[0;30m") color = RGB(0, 0, 0);//black
            if(s == L"\033[0;31m") color = RGB(255, 0, 0);//red
            if(s == L"\033[0;32m") color = RGB(0, 255, 0);//green
            if(s == L"\033[0;33m") color = RGB(128, 64, 0);//brown
            if(s == L"\033[0;34m") color = RGB(0, 128, 255);//blue
            if(s == L"\033[0;35m") color = RGB(255, 0, 255);//magenta
            if(s == L"\033[0;36m") color = RGB(0, 255, 255);//cyan
            if(s == L"\033[0;37m") color = RGB(192, 192, 192);//light gray

            CHARFORMAT cf = { sizeof(cf) };
            cf.dwMask = CFM_BOLD | CFM_COLOR;
            cf.dwEffects = bold ? CFE_BOLD : 0;
            cf.crTextColor = color;
            SendMessage(hedit, EM_SETSEL, (WPARAM)-1, (LPARAM)-1);
            SendMessage(hedit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
        }
        else
        {
            append_to_richedit(s.c_str());
        }

        return *this;
    }

    //this is for std::endl
    my_stream& operator<<(std::wostream& (*func)(std::wostream&))
    {
        oss.str(L"");
        oss << func;
        append_to_richedit(oss.str().c_str());
        return *this;
    }

    void append_to_richedit(const wchar_t *text)
    {
        if(text && wcslen(text))
        {
            SendMessage(hedit, EM_SETSEL, (WPARAM)-1, (LPARAM)-1);
            SendMessage(hedit, EM_REPLACESEL, (WPARAM)FALSE, (LPARAM)text);
        }
    }
};

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    static my_stream cout;
    switch(msg)
    {
    case WM_CREATE:
    {
        RECT rc;
        GetClientRect(hwnd, &rc);
        InflateRect(&rc, -10, -10);
        cout.create(hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
            ((LPCREATESTRUCT)lparam)->hInstance, 0);

        cout << "\033[0;31m" << "red\n";
        cout << "\033[1;31m" << "bold red\n";
        cout << "\033[0m" << "reset\n";
        cout << "\033[0;32m" << "green\n";
        cout << "\033[0;34m" << std::showbase << std::hex << 17 << std::endl;
        cout << "\033[1m";
        cout << L"bold, unicode ☺ ελληνική\n";
        cout << L"Win10 symbols ?\n";
        cout.oss.precision(3);
        cout << "numbers " << std::setw(10) << 3.1415 << std::endl;
        break;
    }

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
}

int WINAPI wWinMain(HINSTANCE hinst, HINSTANCE, LPTSTR, int)
{
    WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.hInstance = hinst;
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    wcex.lpszClassName = L"Test";
    RegisterClassEx(&wcex);

    CreateWindow(wcex.lpszClassName, L"Test", WS_VISIBLE | WS_OVERLAPPEDWINDOW, 
        100, 100, 600, 400, 0, 0, hinst, 0);

    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

enter image description here

Это элемент управления редактирования только для чтения, он должен поддерживать выделение мышью и Ctrl + C для копирования. Вы можете создать подкласс richedit для добавления возможностей меню.

0 голосов
/ 29 апреля 2018

Это можно частично решить, добавив вызовы к InvalidateRect( console, NULL, TRUE ); и RedrawWindow( console, NULL, NULL, RDW_INVALIDATE );. Попробуйте сделать оба звонка внутри WM_CREATE, WM_MOVE и WM_SIZE. Но звоните только RedrawWindow внутри WM_PAINT, так как в противном случае программа зависает.

Также звоните DefWindowProc() после WM_PAINT и других сообщений. Вы должны return 0 только тогда, когда уверены, что стандартная обработка окон не требуется, что бывает редко, если вы не отвечаете на пользовательское сообщение.

Наконец, окно консоли - это отдельный процесс. Встраивание окна одного процесса в другой по своей сути рискованно. Например, консоль обычно имеет стандартную оконную раму, поэтому текстовая область не рисуется в [0,0] клиентского прямоугольника. Это рисует в правом нижнем углу этого. Без рамки окна текстовая область теперь отображается с размером [0,0], но имеет тот же размер, что и окружающий прямоугольник клиента. Что оставляет пустое неокрашенное пространство справа и снизу текстовой области. Установка WS_HSCROLL и WS_VSCROLL в окне, чтобы сразу показывать полосы прокрутки, показывает пустое пространство между текстовой областью и полосами прокрутки.

Вообще говоря, встраивание одного окна процесса в другое нетривиально. Быстрый поиск выявил некоторые связанные темы здесь , здесь . Если вы хотите перенаправить стандартный вывод в дочернее текстовое окно, не используя консоль, другой связанный раздел - здесь . Если вам просто нужна возможность печати отладочных сообщений, рассмотрите возможность использования OutputDebugString для печати в окне вывода Visual Studio.

...