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

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

Запрос на подключение к сети инициируется пользователем, выбирающим пункт меню, который вызывает диалоговое окно для заполнения информации о целевом компьютере. Когда пользователь нажимает кнопку «ОК» в диалоговом окне, запрос на сетевое подключение обрабатывается с использованием рабочего потока.

Что я хочу сделать, так это изменить курсор мыши на индикатор ожидания, а затем удалить индикатор ожидания после фактического установления соединения или истечения времени попытки.

Я сталкиваюсь с тем, что курсор мыши остается указателем, а курсор мыши не меняется на индикатор ожидания.

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

Дальнейшее чтение показывает, что мне также нужно переопределить метод afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) класса CScrollView, однако я не могу найти ничего полезного, что описывает, что мне нужно делать в этом методе. Кажется, что метод OnSetCursor() вызывается по разным причинам, и простое перемещение мыши вызывает срабатывание точки останова в этом методе.

Похоже, что в методе OnSetCursor() я должен определить текущее состояние приложения и на его основе использовать функцию SetCursor() для установки одного из возможных стилей курсора мыши, которые ранее были загружены с помощью LoadCursor(). См. Запретить приложению MFC возвращать курсор к значку по умолчанию, а также Изменить курсор на время потока

Однако я не уверен, так ли это на самом деле, и что на самом деле означают параметры, предоставляемые OnSetCursor(), и как их использовать.

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


person Richard Chambers    schedule 16.09.2019    source источник
comment
Пробовали ли вы CWaitCursor?   -  person 1201ProgramAlarm    schedule 16.09.2019
comment
@ 1201ProgramAlarm Я только что попробовал и обнаружил, что он меняет курсор мыши на индикацию ожидания. Однако существует требование локальной области действия, которое означает, что как только обработчик меню запускает рабочий поток, а затем возвращается, объект CWaitCursor выходит из области действия. Я обнаружил, что если я использую BeginWaitCursor(), а затем помещаю сон после запуска рабочего потока, замораживая пользовательский интерфейс, я также вижу индикацию ожидания курсора мыши. Итак, похоже, что в дополнение к BeginWaitCursor() мне нужно сделать еще кое-что, чтобы курсор оставался ожидающим курсором.   -  person Richard Chambers    schedule 16.09.2019
comment
Вы можете динамически создать объект CWaitCursor, но, поскольку ваш основной поток сообщений продолжает работать, он все равно может заменить курсор. Как вы справляетесь с тем, что пользователь выбирает другой пункт меню (или какое-то другое взаимодействие), пока вы пытаетесь подключиться к сети? Возможно, вам придется прибегнуть к модальному диалоговому окну Connecting to server app.   -  person 1201ProgramAlarm    schedule 16.09.2019
comment
@ 1201ProgramAlarm, пока соединение не установлено и не установлено, большинство пунктов меню отключены и выделены серым цветом. Единственное, что пользователь может сделать, пока соединение не установлено, это выйти. Что такое модальное диалоговое окно «Подключение к серверу»? Это приложение использует UDP для подключения к целевому компьютеру, что на самом деле включает в себя информирование целевого сервера о необходимости создания нового сеанса связи и предоставление токена, необходимого для использования нового сеанса. Вся связь осуществляется по протоколу UDP, а не TCP.   -  person Richard Chambers    schedule 16.09.2019
comment
Вы создаете курсор ожидания, создаете диалоговое окно или окно с каким-то сообщением, которое является модальным для приложения (которое сохраняет ввод из остальной части вашего приложения) и, возможно, с кнопкой отмены. Закройте диалоговое окно, как только соединение будет установлено, и ваш поток завершится.   -  person 1201ProgramAlarm    schedule 16.09.2019
comment
Вы хотите изменить курсор для всего приложения или для определенного окна?   -  person Constantine Georgiou    schedule 16.09.2019
comment
@ 1201ProgramAlarm хорошо, понятно. В рамках обработки элемента меню для установления соединения приложение выводит модальное диалоговое окно для ввода данных соединения, принимает ввод, если была нажата кнопка «ОК», а затем выполняет соединение. Изменение будет заключаться в изменении диалогового окна с помощью кнопки «Подключить» и запуске потока подключения в модальном диалоговом окне. Затем либо обработайте сообщение о готовности к подключению, либо разрешите нажатие кнопки «Отмена», чтобы закрыть поток и открыть модальное диалоговое окно с указанием ошибки.   -  person Richard Chambers    schedule 16.09.2019
comment
@ConstantineGeorgiou Я планирую изменить курсор для всего приложения. Почему вы спрашиваете?   -  person Richard Chambers    schedule 16.09.2019
comment
Тогда, возможно, используйте OnSetCursor() для основного фрейм-окна.   -  person Constantine Georgiou    schedule 16.09.2019
comment
@ConstantineGeorgiou Мне не удалось найти жизнеспособный пример того, как это сделать. Вторая публикация кажется лучшим примером, однако я не уверен, является ли это хорошим примером для подражания или нет. У вас есть мнение по этому поводу?   -  person Richard Chambers    schedule 16.09.2019


Ответы (2)


Сначала объявите следующие глобальные переменные:

BOOL bConnecting = FALSE; // TRUE if connecting, set by your application
HCURSOR hOldCursor = NULL; // Cursor backup

Когда вам нужно отобразить вызов курсора песочных часов:

bConnecting = TRUE;
hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

Как только соединение установлено (или не удалось), вызовите:

bConnecting = FALSE;
SetCursor(hOldCursor);
// Alternatively you can call SetCursor(LoadCursor(NULL, IDC_ARROW)); - no need to backup the cursor then
// Or even not restore the cursor at all, it will be reset on the first WM_MOUSEMOVE message (after bConnecting is set to FALSE)

Вам также необходимо переопределить OnSetCursor():

BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    if (bConnecting) return TRUE; // Prevent MFC changing the cursor

    // else call the default
    return CFrameWndEx::OnSetCursor(pWnd, nHitTest, message);
}

И добавьте директиву ON_WM_SETCURSOR() в карту сообщений для CMainFrame, чтобы включить обработчик сообщений OnSetCursor().

«Основной фрейм» является родителем всех окон в приложении MFC, поэтому мы переопределяем для него OnSetCursor(). Это влияет на все остальные окна.

В среде MFC вы также можете использовать функции BeginWaitCursor(), RestoreWaitCursor() и EndWaitCursor(). Это методы CCmdTarget, доступ к которым можно получить с помощью AfxGetApp(), а также любого производного класса CWnd.

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

person Constantine Georgiou    schedule 16.09.2019
comment
Причина, по которой установки курсора ожидания (в любом случае, с помощью BeginWaitCursor() или CWaitCursor) недостаточно, заключается в том, что вы обрабатываете очередь сообщений после этого. Во время работы очереди сообщений окно под курсором мыши получает событие WM_SETCURSOR для движений мыши, чтобы дать этому окну возможность управлять курсором над ним. Если поток окна (обычно основной поток) занят, однократная установка курсора ожидания оставит этот курсор видимым до его сброса или до обработки WM_SETCURSOR. - person Nick; 17.09.2019
comment
@Nick: Точно, проблема в том, что MFC обрабатывает сообщение WM_SETCURSOR, поэтому нам нужно предоставить переопределение. Во многих примерах кода я видел вызов SetCursor() с OnSetCursor() (обрабатывает сообщение WM_SETCURSOR), который вызывается даже при движениях мыши. Вместо этого в решении, которое я предложил выше, я устанавливаю его только один раз, а затем просто возвращаю TRUE (предотвращая изменение обратно), пока оно не будет сброшено. То есть OnSetCursor() можно вызвать, но он ничего не сделает, а возврат TRUE не позволит системе отправить его в другие окна. Поток ownign не обязательно должен быть занят. - person Constantine Georgiou; 18.09.2019
comment
@Richard Chambers: Спасибо за редактирование моего ответа. Что касается вашего примечания к карте сообщений, Редактор свойств/событий в Visual Studio обычно делает это за вас (добавляет объявление и реализацию И изменяет карту сообщений), поэтому процедура довольно автоматизирована. Именно так его использует большинство разработчиков, поэтому я не упомянул об этом конкретно. Что касается многопоточной среды, действительно, доступ/изменение глобальных переменных из разных потоков требует блокированного или синхронизированного доступа. - person Constantine Georgiou; 18.09.2019
comment
@ConstantineGeorgiou, ваш опубликованный ответ помог мне. Когда я работал с моим источником, реализующим предложенное вами решение, я столкнулся с несколькими вещами, откуда и произошли изменения. Я добавил их в ваш пост, чтобы быть полным. Так много других ответов находятся на полпути, и я хотел получить хотя бы один по этому вопросу и в этой области, который был бы достаточно полным. - person Richard Chambers; 18.09.2019

Реализация @Constantine OnSetCursor у меня не сработала (VC++ 2013; Win 10) - курсор ожидания все еще возвращался к стрелке после запуска потока. Но я решил свою проблему с помощью следующего кода.

BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    if (bConnecting) {
          SetCursor(LoadCursor(NULL, IDC_WAIT));
          return TRUE; // Prevent MFC changing the cursor
    }
    // else call the default
    return CFrameWndEx::OnSetCursor(pWnd, nHitTest, message);
}

Обратите внимание, что все открытые диалоги или представления должны иметь OnSetCursor, если вы хотите отображать курсор ожидания при наведении курсора на представления.

person Tae-Sung Shin    schedule 08.04.2020