MFC Обработка сообщений Windows в однопоточной программе

Рубрика: MFC, Дата: 19 August, 2015, Автор:

Здорова! Немножко разберемся как обрабатывает сообщения Windows в однопоточной программе и создадим эту программку тестовую. В каждой программе виндовс спрятаны следующие инструкции

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

Это бесконечный цикл из которого никогда не будет выхода, потому что если нету сообщений функция GetMessage ждет пока они появятся, про нее вы можете почитать тут. Windows определяет, какие сообщения принадлежат вашей программе, а функция GetMessage возвращает управление, как только появляется сообщение для обработки. Если сообщений нет, то программа приостанавливается, и выполняются другие приложения. Когда сообщения поступают, то ваша программа “пробуждается”.

Передача управления

Для того чтобы Windows не подвисала, а подвесить ее может например функция обработчик, от например мы вызвали обработчик нажатия кнопки и в нем создали очень большой цикл от 0 до 1000000 например и этот цикл заберет все на себя, он как бы завесить программу. Чтобы такого зависания не было, нужно из цикла проверять существуют сообщения Windows или нет и обрабатывать их из очереди, вот такой конструкцией например

if(::PeekMessage(&message,NULL,0,0,PM_REMOVE))
{
	::TranslateMessage(&message);
	::DispatchMessage(&message);
}

Функция PeekMessage работает также как и GetMessage – за исключением того, что возвращает управление немедленно, даже если для вашей программы нет никаких сообщений. В этом случае “жадная” функция продолжает поглощать процессорное время. Однако как только появляется сообщение, вызывается обработчик и по завершению его работы выполнение функции возобновляется.

Таймеры

Таймер Windows – это полезный элемент, иногда устраняющий необходимость в многопоточном программировании. Например, если Вам нужно считывать содержимое коммуникационного буфера, установите таймер так, чтобы выбирать накопившиеся символы каждые 100 миллисекунд. Таймер можно использовать и для управления анимацией, поскольку он не зависит от тактовой частоты процессора.

Работать с таймерами легко. Вы просто вызываете функцию CWnd::SetTimer с параметром – интервалом времени, после чего с помощью ClassWizard определяете обработчик сообщения WM_TIMER. После запуска таймера с заданным интервалом в миллисекундах сообщения WM_TIMER постоянно посылаются вашему окну до тех пор, пока не будет вызвана CWnd::KillTimer или уничтожено окно. При необходимости можно задействовать несколько таймеров, каждый из которых идентифицируется целым числом. Поскольку Windows не является операционной системой реального времени, интервал значительно менее 100 миллисекунд приводит к потере точности.

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

Создание реального примерчика обработки сообщений Windows на MFC однопоточная прога.

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

1. Создание приложения.

Открываем Visual Studio 2010, выбираем “Файл”->”Создать”->”Прокт…”, в появившемся окошке “Создать проект” выбираем “Приложение MFC” вводим название “MyTestProg” и жмем “Ок”, в появившемся окошке “Мастер приложений MFC” жмем “Далее”, в появившейся вкладке выставляем следующие настройки

MFC create SDIжмем “Готово”, мастер у нас создаст приложение со следующим набором файлов

MFC WorcspaceТеперь нам нужно создать диалоговое окошко.

2. Добавление диалога

нам нужно создать от такой диалог

MFC dialogтам мы накинули на диалог progress control и две кнопочки. их ID следующие у кнопочки “start” IDSTART, а у progress control IDC_PROGRESS1.

Добавим класс к нашему диалогу, для этого кликаем правой  клавишей мышки по диалогу и выбираем из появившегося контекстного меню “Добавить класс…”, в появившемся окошке “Мастер добавления классов” введите имя класса “CMyDialog”

MFC add dialogжмите “Готово”, все у вас создалось два файла MyDialog.h и MyDialog.cpp с классом CMyDialog. Добавим в него следующие закрытые переменные члены

private:
	int m_nTimer;
	int m_nCount;
	enum{nMaxCount=10000};

переменная m_nCount будет увеличиваться по мере выполнения программы и деление ее на константу mMaxCount даст нам единицу измерения продвижения. Инициализируем m_nCount нулем в конструкторе.

Добавим обработчики OnBnClickedStartи OnBnClickedCancel для кнопок “start” и “Отменить”, а также добавим обработчик для сообщения WM_TIMER. Ниже представлены их обработчики с кодом.

CMyDialog::OnBnClickedStart »

void CMyDialog::OnBnClickedStart()
{
	// TODO: добавьте свой код обработчика уведомлений
	MSG message;

	m_nTimer=SetTimer(1,100,NULL);//1/10 секунды
	ASSERT(m_nTimer!=0);
	GetDlgItem(IDSTART)->EnableWindow(FALSE);
	volatile int nTemp;//указывает что значение может быть изменено в любой момент, поэтому не нужно аптимизировать
	for(m_nCount=0;m_nCount<nMaxCount;m_nCount++)
	{
		for(nTemp=0;nTemp<1000;nTemp++)
		{}//просто занимаем процессор
		if(::PeekMessage(&message,NULL,0,0,PM_REMOVE))
	//	if(::GetMessage(&message,NULL,0,0))
		{
			::TranslateMessage(&message);
			::DispatchMessage(&message);
		}
		TRACE("onen");
		TRACE("twon");
	}
	CDialogEx::OnOK();//имитация нажатия кнопки Ок
}

CMyDialog::OnBnClickedCancel »

void CMyDialog::OnBnClickedCancel()
{
	// TODO: добавьте свой код обработчика уведомлений
	TRACE("Входим в CComputeDlg::OnCanceln");
	if(m_nCount==0)//до нажатия кнопки Start
	{
		CDialogEx::OnCancel();
	}
	else//идут вычисления
	{
		m_nCount=nMaxCount;//принудительное завершение OnStart
	}

	CDialogEx::OnCancel();
}

CMyDialog::OnTimer »

void CMyDialog::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: добавьте свой код обработчика сообщений или вызов стандартного
	//получаем указатель на pBar из диалога
	CProgressCtrl* pBar=(CProgressCtrl*)GetDlgItem(IDC_PROGRESS1);
	pBar->SetPos(m_nCount*100/nMaxCount);//обновляем прогресс-бар

	CDialogEx::OnTimer(nIDEvent);
}

Отредактируем функцию OnDraw класса вида

CMyTestProgView::OnDraw »

void CMyTestProgView::OnDraw(CDC* pDC)
{
	CMyTestProgDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: добавьте здесь код отрисовки для собственных данных
	pDC->TextOut(0,0,L"Щелкните здесь левой кнопкой мыши.");
}

И добавим обработчик событий WM_LBUTTONDOWN в класс вида вот его обработчик

CMyTestPorgView::OnLButtonDown »

void CMyTestProgView::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: добавьте свой код обработчика сообщений или вызов стандартного
	CMyDialog dlg;
	dlg.DoModal();

	CView::OnLButtonDown(nFlags, point);
}

и подключите в файле вида сам диалог

#include "MyDialog.h"

Нажимаем F5 и проверяем работу нашего приложения.

У нас кнопка блокируется

GetDlgItem(IDSTART)->EnableWindow(FALSE);

при нажатии на start, это затем чтобы избежать повторного нажатия. В общем прогрессбар нормально заполняется все в работает.

MFC test progТут основной момент в том что у нас происходит обработка сообщений в основном цикле с помощью конструкции

if(::PeekMessage(&message,NULL,0,0,PM_REMOVE))
{
	::TranslateMessage(&message);
	::DispatchMessage(&message);
}

Если мы заменим PeekMessage на GetMessage, то оно будет притормаживать, потому что GetMessage будет ждать сообщения когда их не будет, а PeekMessage не ждет, а сразу передает управление. За PeekMessage и GetMessage можно почитать тут.

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

 

Комментарии:


Оставить комментарий

Your email address will not be published. Required fields are marked *