Как остановить Windows от блокировки программы во время перетаскивания окна или нажатия кнопки меню?

Я начинаю с Win32, и я преследовал проблему (если ее вообще можно назвать проблемой) с Windows, блокирующей ваши поток программы во время события, когда пользователь захватывает строку заголовка окна и перемещает ее по экрану.

У меня нет законных оснований для решения этой проблемы, за исключением того, что это беспокоит меня. Несколько возможностей включают удаление кадра в целом, но это кажется неудобным взломом. Некоторые игры (одиночный игрок) не считают эту проблему вообще. Однако я прочитал, что в многопользовательских играх могут возникать проблемы, когда программа зависает, поскольку она ожидает непрерывного потока информации и может быть перегружена после такой задержки.

Я попытался добавить это к моему WindowProc

switch (uMsg)
{
    case WM_SYSCOMMAND:
        if (wParam == SC_CLOSE)
            PostQuitMessage(0);

        return 0;
    ...
    ...
    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;

И это кажется быстрым взломом, за исключением того, что когда я просматриваю значок закрытия, я могу вытащить мышь и отпустить, не закрывая программу, и за это время, когда значок закрытия удерживается, программа еще раз заблокирован.

Кроме того, я не знаю, как вручную включать код, необходимый для перемещения окна, когда пользователь нажимает заголовок и перетаскивает мышь. Для начала я не знаю, какие uMsg и wParam обрабатывать.

Мой вопрос заключается в том, как я могу запретить блокировку во время случая, когда пользователь нажимает кнопку выхода (или сворачивает/увеличивает кнопки), все еще обрабатывая случай, когда мышь нажата и выпущена над кнопкой, и как сделать Я разрешаю пользователю перемещать/перетаскивать окно без блокировки программы (или какое сообщение отправляется при нажатии кнопки заголовка без его кнопки или меню)?

Я создаю окно с WS_SYSMENU | WS_MINIMIZEBOX.

Я все еще хочу, чтобы программа отвечала на команды минимизации, максимизации и завершения.

Если многопоточность может его исправить, то это интересно, но мне интересно, могу ли я заставить его работать на одноядерных процессорах. И я прочитал о крючках, но мне трудно интерпретировать страницу MSDN.

Ответ 1

Почему мое приложение замерзает? - Введение в циклы сообщений и потоки

Это явление не изолировано от какого-либо конкретного сообщения. Это фундаментальное свойство цикла сообщений Windows: при обработке одного сообщения другое сообщение не может быть обработано одновременно. Это не так точно реализовано, но вы можете думать о нем как о очереди, где ваше приложение вытаскивает сообщения из очереди для обработки в обратном порядке, в которые они вставлены.

Следовательно, слишком длительная обработка любого сообщения приостанавливает обработку других сообщений, фактически замораживая ваше приложение (потому что он не может обрабатывать какие-либо данные). Единственный способ решить эту проблему - очевидный: не тратьте слишком много времени на обработку какого-либо одного сообщения.

Часто это означает делегирование обработки в фоновый поток. Вам все равно придется обрабатывать все сообщения в основном потоке, а потоки рабочего фона должны сообщать основному методу, когда они будут завершены. Все взаимодействие с GUI должно происходить в одном потоке, и это почти всегда основной поток в вашем приложении (поэтому его часто называют потоком пользовательского интерфейса).

(И чтобы ответить на возражение, поднятое в вашем вопросе, да, вы можете управлять несколькими потоками на однопроцессорных машинах. Вы не увидите никаких улучшений производительности, но это сделает интерфейс более отзывчивым. поток может выполнять только одну вещь за раз, но процессор может очень быстро переключаться между потоками, эффективно имитируя выполнение нескольких операций одновременно.)

Более полезная информация доступна здесь в этой статье MSDN: Предотвращение зависаний в приложениях Windows

Специальные случаи: Модальные контуры обработки событий

Некоторые операции с окнами в Windows - это модальные операции. Модаль - обычное слово в вычислениях, которое в основном относится к блокировке пользователя в конкретном режиме, где они не могут ничего сделать, пока не изменятся (то есть не выйдут из этих) режимов. Всякий раз, когда начинается модальная операция, разворачивается отдельный новый цикл обработки сообщений и происходит обработка сообщений (вместо вашего основного цикла сообщений) в течение всего режима. Обычными примерами этих модальных операций являются drag-and-drop, изменение размера окна и окна сообщений.

Учитывая пример изменения размера окна, ваше окно получает сообщение WM_NCLBUTTONDOWN, которое вы передаете DefWindowProc для обработки по умолчанию. DefWindowProc показывает, что пользователь намеревается начать операцию перемещения или изменения размера и ввел петлю сообщения о перемещении/калибровке, расположенную где-то глубоко в недрах собственного кода Windows. Таким образом, ваш цикл сообщений приложения больше не работает, потому что вы вошли в новый режим перемещения/калибровки.

Windows запускает этот цикл перемещения/калибровки, пока пользователь интерактивно перемещает/выравнивает окно. Он делает это так, чтобы он мог перехватывать сообщения мыши и обрабатывать их соответственно. Когда операция перемещения/калибровки завершается (например, когда пользователь отпускает кнопку мыши или нажимает клавишу Esc), управление возвращается к вашему коду приложения.

Стоит отметить, что вы получили уведомление о том, что это изменение режима произошло через сообщение WM_ENTERSIZEMOVE; соответствующее сообщение WM_EXITSIZEMOVE указывает, что завершен цикл обработки модальных событий. Это позволяет создать таймер, который будет продолжать генерировать сообщения WM_TIMER, которые может обрабатывать ваше приложение. Фактические данные о том, как это реализовано, относительно неважны, но быстрое объяснение заключается в том, что DefWindowProc продолжает отправлять сообщения WM_TIMER в ваше приложение внутри своего собственного цикла обработки модальных событий. Используйте функцию SetTimer, чтобы создать таймер в ответ на сообщение WM_ENTERSIZEMOVE, а KillTimer function, чтобы уничтожить его в ответ на сообщение WM_EXITSIZEMOVE.

Я только указываю на полноту. В большинстве приложений Windows, которые я написал, мне никогда не нужно было это делать.

Итак, что не так с моим кодом?

Помимо всего этого, поведение, которое вы описываете в вопросе, необычно. Если вы создаете новое пустое приложение Win32 с использованием шаблона Visual Studio, я сомневаюсь, что вы сможете воспроизвести это поведение. Не видя остальной части вашей оконной процедуры, я не могу сказать, блокируете ли вы какие-либо сообщения (как обсуждалось выше), но часть, которую я вижу в вопросе, неверна. Вы всегда должны называть DefWindowProc для сообщений, которые вы явно не обрабатываете самостоятельно.

В этом случае вас может обмануть мысль, что вы это делаете, но WM_SYSCOMMAND может иметь множество разных значений для его wParam. Вы используете только один из них, SC_CLOSE. Все остальные просто игнорируются, потому что вы return 0. Это включает в себя все функциональные возможности перемещения и изменения окна (например, SC_MOVE, SC_SIZE, SC_MINIMIZE, SC_RESTORE, SC_MAXIMIZE и т.д. И т.д.).

И действительно нет веских оснований обращаться с WM_SYSCOMMAND самостоятельно; просто пусть DefWindowProc позаботится об этом для вас. Единственный раз, когда вам нужно обрабатывать WM_SYSCOMMAND, - это когда вы добавили пользовательские элементы в меню окна, и даже тогда вы должны передать все команды, которые вы не распознаете, на DefWindowProc.

Основная процедура окна должна выглядеть так:

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_CLOSE:
            DestroyWindow(hWnd);
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

Также возможно, что ваш цикл сообщений неверен. Идиоматический контур сообщения Win32 (расположенный рядом с нижней частью вашей функции WinMain) выглядит следующим образом:

BOOL ret;
MSG msg;
while ((ret = GetMessage(&msg, nullptr, 0, 0)) != 0)
{
    if (ret != -1)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else
    {
        // An error occurred! Handle it and bail out.
        MessageBox(nullptr, L"Unexpected Error", nullptr, MB_OK | MB_ICONERROR);
        return 1;
    }
}

Вам не нужны никакие крючки. Документация MSDN на них очень хорошая, но вы правы: они сложны. Держитесь подальше, пока не получите лучшее представление о модели программирования Win32. Это редкий случай, когда вам нужна функциональность, предоставляемая крюком.

Ответ 2

Если многопоточность может исправить это, то это интересно, но мне интересно если я могу заставить его работать на одноядерных процессорах. И я прочитал о крючках, но страница MSDN по-прежнему трудно интерпретировать.

Вы можете использовать несколько потоков на одноядерном процессоре. Производительность будет лучше в многоядерных системах, но это не должно мешать вам писать многопоточные приложения. Во всяком случае, пойдите для этого.

Ответ 3

Распечатав все сообщения, отправленные на WindowProc, появится сообщение WM_NCLBUTTONDOWN отправлено последним до того, как произойдет блок. Вы можете проверить местоположение мыши после этого события, но это кажется неудобным способом решить простую проблему.