Sl-Alex домашняя лаборатория

Руководство по WxWidgets: 5. События

Просмотров: 2987Комментарии: 0
Статьи
Руководство по WxWidgets: 5. События

Все GUI приложения управляются с помощью событий, являющихся их неотъемлемой частью. Приложение реагирует на различные типы событий, генерируемых во время его эксплуатации. Их источником в основном является пользователь. Однако, существуют и другие источники, например интернет соединение, оконный менеджер, таймер. При старте приложение запускает основной цикл, в котором оно благополучно пребывает в ожидании какого-нибудь события. Основной цикл прекращается при выходе из приложения.

Как обычно, в конце главы есть ссылки на остальные материалы курса.

Определения (описания)

Событие - часть информации прикладного уровня, лежащей в основе структуры, обычно набора инстурментов GUI (GUI toolkit). Событийный цикл - это программная конструкция, которая ожидает и диспетчеризирует события и сообщения в программе. Этот цикл постоянно находится в ожидании события для обработки. Диспетчер - это процесс прередающий события в обработчиках событий. Обработчики событий - это методы, реагирующие на события.

Событийный объект - это объект связанный с событием. Обычно это окно. Событийный тип - это уникальное сгенерированное событие

Пример простого события

Традиционным способом работы с событиями в wxWidgets является использование статических таблиц событий. Это сказалось влияние MFC. Более гибким и современным способом является использование метода Connect(). Именно поэтому я использую этот способ на протяжении всего руководства.

Таблица событий

В следующем примере мы покажем как использовать таблицы событий.

button.h:
#include <wx/wx.h>
class MyButton : public wxFrame
{
public:
	MyButton(const wxString& title);
	void OnQuit(wxCommandEvent& event);
private:
    DECLARE_EVENT_TABLE()
};
button.cpp:
#include "button.h"
MyButton::MyButton(const wxString& title)
	: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 150))
{
	wxPanel *panel = new wxPanel(this, wxID_ANY);
	wxButton *button = new wxButton(panel, wxID_EXIT, 
		wxT("Quit"), wxPoint(20, 20));
	Centre();
}
void MyButton::OnQuit(wxCommandEvent& WXUNUSED(event))
{
	Close(true);
}
BEGIN_EVENT_TABLE(MyButton, wxFrame)
	EVT_BUTTON(wxID_EXIT,  MyButton::OnQuit)
END_EVENT_TABLE()
main.h:
#include <wx/wx.h>
class MyApp : public wxApp
{
	public:
		virtual bool OnInit();
};
main.cpp:
#include "main.h"
#include "button.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
	MyButton *button = new MyButton(wxT("Button"));
	button->Show(true);
	return true;
}

В нашем примере мы создали простую кнопку. Нажимая на кнопку, мы закрываем приложение.

private:
     DECLARE_EVENT_TABLE()

В заголовочном файле мы объявили таблицу событий с помощью макроса DECLARE_EVENT_TABLE().

BEGIN_EVENT_TABLE(MyButton, wxFrame)
     EVT_BUTTON(wxID_EXIT,  MyButton::OnQuit)
 END_EVENT_TABLE()

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

Пример использования Connect()

Поговорим о событии перемещения. Оно содержит информацию о событиях изменения движения. Оно генерируется при перетаскивании окна на новую позицию. Классом, представляющим события перемещения является wxMoveEvent, а wxEVT_MOVE - событийным типом.

move.h:
#include <wx/wx.h>
class Move : public wxFrame
{
public:
	Move(const wxString& title);
	void OnMove(wxMoveEvent & event);
	wxStaticText *st1;
	wxStaticText *st2;
};
move.cpp:
#include "move.h"
Move::Move(const wxString& title)
	: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
{
	wxPanel *panel = new wxPanel(this, -1);
	st1 = new wxStaticText(panel, -1, wxT(""), wxPoint(10, 10));
	st2 = new wxStaticText(panel, -1, wxT(""), wxPoint(10, 30));
	Connect(wxEVT_MOVE, wxMoveEventHandler(Move::OnMove));
	Centre();
}
void Move::OnMove(wxMoveEvent& event)
{
	wxPoint size = event.GetPosition();
	st1->SetLabel(wxString::Format(wxT("x: %d"), size.x ));
	st2->SetLabel(wxString::Format(wxT("y: %d"), size.y ));
}
main.h:
#include <wx/wx.h>
class MyApp : public wxApp
{
	public:
		virtual bool OnInit();
};
main.cpp:
#include "main.h"
#include "move.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
	Move *move = new Move(wxT("Move event"));
	move->Show(true);
	return true;
}

Пример показывает текущую позицию окна.

Connect(wxEVT_MOVE, wxMoveEventHandler(Move::OnMove));

Здесь мы соединили (connect) событийный тип wxEVT_MOVE с методом OnMove().

wxPoint size = event.GetPosition();

Параметр event в методе OnMove() - это объект специфичный для данного события. В нашем случае это экземпляр класса wxMoveEvent. Этот объект содержит информацию о событии. Мы можем выяснить текущую позицию, вызвав метод GetPosition() события.

Рисунок 5.1: Событие перемещения

Рисунок 5.1: Событие перемещения

Репродукция события

Существует два типа события. Базовые события и командные события. Они отличаются репродукциями. Репродукция события путешествует от событии из виджетов потомков в родительские и прародительские виджеты. Базовые события не репродуцируются в отличие от командных. Например, wxCloseEvent - это базовое событие. Не имеет смысла репродуцировать его в родительский виджет.

По-умолчанию, событие захваченное обработчиком прекращает репродуцироваться. Для продолжения репродуцирования нужно вызвать метод Skip().

propagate.h:
#include <wx/wx.h>
class Propagate : public wxFrame
{
public:
	Propagate(const wxString& title);
  
	void OnClick(wxCommandEvent& event);
};
class MyPanel : public wxPanel
{
public: 
	MyPanel(wxFrame *frame, int id);
	void OnClick(wxCommandEvent& event);
};
class MyButton : wxButton
{
public:
	MyButton(MyPanel *panel, int id, const wxString &label);
	void OnClick(wxCommandEvent& event);
};
propagate.cpp:
#include <iostream>
#include "propagate.h"
const int ID_BUTTON = 1;
Propagate::Propagate(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
{
	MyPanel *panel = new MyPanel(this, -1);
	new MyButton(panel, ID_BUTTON, wxT("Ok"));
	Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED, 
		wxCommandEventHandler(Propagate::OnClick));
	Centre();
}
void Propagate::OnClick(wxCommandEvent& event) 
{
	std::cout << "event reached frame class" << std::endl;
	event.Skip();
}
MyPanel::MyPanel(wxFrame *frame, int id)
	: wxPanel(frame, id)
{
	Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED, 
		wxCommandEventHandler(MyPanel::OnClick));
} 
void MyPanel::OnClick(wxCommandEvent& event) 
{
	std::cout << "event reached panel class" << std::endl;
	event.Skip();
}
MyButton::MyButton(MyPanel *mypanel, int id, const wxString& label)
	: wxButton(mypanel, id, label, wxPoint(15, 15))
{
	Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED, 
		wxCommandEventHandler(MyButton::OnClick));
} 
void MyButton::OnClick(wxCommandEvent& event) 
{
	std::cout << "event reached button class" << std::endl;
	event.Skip();
}
main.h:
#include <wx/wx.h>
class MyApp : public wxApp
{
	public:
		virtual bool OnInit();
};
main.cpp:
#include "main.h"
#include "propagate.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
	Propagate *prop = new Propagate(wxT("Propagate"));
	prop->Show(true);
	return true;
}

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

event reached button class
event reached panel class
event reached frame class

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

Попробуйте опустить некоторые методы Skip() и посмотрите, что получится.

Запрещение событий

Иногда нам нужно прекратить обработку события. Для этого используется метод Veto().

veto.h:
#include <wx/wx.h>
class Veto : public wxFrame
{
public:
	Veto(const wxString& title);
	void OnClose(wxCloseEvent& event);
};
veto.cpp:
#include "veto.h"
Veto::Veto(const wxString& title)
	: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
{
	Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(Veto::OnClose));
	Centre();
}
void Veto::OnClose(wxCloseEvent& event) 
{
	wxMessageDialog *dial = new wxMessageDialog(NULL,
		wxT("Are you sure to quit?"), wxT("Question"),
		wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
	int ret = dial->ShowModal();
	dial->Destroy();
	if (ret == wxID_YES) {
		Destroy();
	} else {
		event.Veto();
	}
}
main.h:
#include <wx/wx.h>
class MyApp : public wxApp
{
	public:
		virtual bool OnInit();
};
main.cpp:
#include "main.h"
#include "veto.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
	Veto *veto = new Veto(wxT("Veto"));
	veto->Show(true);
	return true;
}

В нашем примере мы обрабатывали событие wxCloseEvent. Оно вызывается нажатием на кнопку X строке заголовка, сочетанием клавиш Alt+F4 или выбором пункта close в системном меню. Во многих программах нам нужно предотвратить случайное закрытие окна, чтобы не потерять изменённые во время работы данные. Для осуществления этого мы должны подключить событийный тип wxEVT_CLOSE_WINDOW.

wxMessageDialog *dial = new wxMessageDialog(NULL, 
	wxT("Are you sure to quit?"), wxT("Question"),
	wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);

При генерации события Close, выводится соответствующий диалог.

if (ret == wxID_YES) {
		Destroy();
	} else {
		event.Veto();
	}

В зависимости от возвращаемого значения мы уничтожим окно или запретим это событие. Внимание, чтобы закрыть окно мы должны вызвать метод Destroy(). Вызывав метод Close(), мы попадём в бесконечный цикл.

Идентификаторы окна

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

  • Позволить системе автоматически создавать идентификаторы;
  • Использовать стандартные идентификаторы;
  • Создать свои собственные.

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

wxButton(parent, -1)
 wxButton(parent, wxID_ANY)

Если мы зададим -1 или wxID_ANY для идентификационного параметра, то wxWidgets будет автоматически создавать идентификационные номера для нас. Автоматически сгенерированные идентификационные номера (id) всегда отрицательные, в то время как заданные пользователем id должны быть всегда положительными. Мы всегда используем эту опцию когда нам не нужно изменять характеристики виджета. Например, виджет статического текста, который никогда не изменяется за время работы с приложением. Но мы можем получить id, если захотим. Для этого используется метод GetId(), который определит id для нас.

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

ident.h:
include <wx/wx.h>
class Ident : public wxFrame
{
public:
	Ident(const wxString& title);
};
ident.cpp:
#include "ident.h"
Ident::Ident(const wxString& title)
	: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(200, 150))
{
	wxPanel *panel = new wxPanel(this, -1);
	wxGridSizer *grid = new wxGridSizer(2, 3);
	grid->Add(new wxButton(panel, wxID_CANCEL), 0, wxTOP | wxLEFT, 9);
	grid->Add(new wxButton(panel, wxID_DELETE), 0, wxTOP, 9);
	grid->Add(new wxButton(panel, wxID_SAVE), 0, wxLEFT, 9);
	grid->Add(new wxButton(panel, wxID_EXIT));
	grid->Add(new wxButton(panel, wxID_STOP), 0, wxLEFT, 9);
	grid->Add(new wxButton(panel, wxID_NEW));
	panel->SetSizer(grid);
	Centre();
}
main.h:
#include <wx/wx.h>
class MyApp : public wxApp
{
	public:
		virtual bool OnInit();
};
main.cpp:
#include "main.h"
#include "ident.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
	Ident *ident = new Ident(wxT("Identifiers"));
	ident->Show(true);
	return true;
}

В нашем примере мы использовали стандартные идентификаторы для кнопок. В Линукс кнопки снабжаются небольшими иконками.

Рисунок 5.2: Идентификаторы

Рисунок 5.2: Идентификаторы

Остальные материалы курса:

Вступление

1. Вспомогательные классы

2. Первые программы

3. Меню и панели инструментов

4. Управление компоновкой

5. События

6. Диалоги

7. Виджеты часть 1

8. Виджеты часть 2

9. Перетаскивание

10. Контексты устройств

11. Самодельные виджеты

12. Тетрис в wxWidgets

Примечание

Оригинал руководства расположен здесь. Автор оригинала - Jan Bodnar.

Перевод (без разметки и картинок) был выполнен пользователем ber113 с сайта translated.by. К сожалению, сайт сейчас не работает.

Данный документ представляет собой компиляцию указанных документов с сохранением оригинальной разметки и иллюстраций.