Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Procedura okna to tylko funkcja, która jest wywoływana dla każdego komunikatu, więc jest z natury bezstanowa. W związku z tym potrzebny jest sposób śledzenia stanu aplikacji z jednego wywołania funkcji do następnego.
Najprostszym podejściem jest po prostu umieszczenie wszystkiego w zmiennych globalnych. Działa to wystarczająco dobrze w przypadku małych programów, a wiele przykładów zestawu SDK używa tego podejścia. Jednak w dużym programie prowadzi to do rozprzestrzeniania się zmiennych globalnych. Ponadto możesz mieć kilka okien, z których każde ma własną procedurę okna. Kontrolowanie, które okno powinno uzyskiwać dostęp do których zmiennych, staje się mylące i podatne na błędy.
Funkcja CreateWindowEx umożliwia przekazywanie dowolnej struktury danych do okna. Po wywołaniu tej funkcji wysyła następujące dwa komunikaty do procedury okna:
Te komunikaty są wysyłane w podanej kolejności. (Nie są to jedyne dwa komunikaty wysyłane podczas CreateWindowEx, ale możemy zignorować inne komunikaty do tej dyskusji).
Komunikat WM_NCCREATE i WM_CREATE są wysyłane, zanim okno stanie się widoczne. To sprawia, że są dobrym miejscem do inicjowania interfejsu użytkownika — na przykład w celu określenia początkowego układu okna.
Ostatni parametr CreateWindowEx jest wskaźnikiem typu void*. Możesz przekazać dowolną wartość wskaźnika w tym parametrze. Gdy procedura okna obsługuje komunikat WM_NCCREATE lub WM_CREATE, może wyodrębnić tę wartość z danych komunikatu.
Zobaczmy, jak za pomocą tego parametru przekazać dane aplikacji do okna. Najpierw zdefiniuj klasę lub strukturę zawierającą informacje o stanie.
// Define a structure to hold some state information.
struct StateInfo {
// ... (struct members not shown)
};
Podczas wywoływania CreateWindowExprzekaż wskaźnik do tej struktury w ostatnim parametrze void*.
StateInfo *pState = new (std::nothrow) StateInfo;
if (pState == NULL)
{
return 0;
}
// Initialize the structure members (not shown).
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
pState // Additional application data
);
Po otrzymaniu komunikatów WM_NCCREATE i WM_CREATE parametr lParam jest wskaźnikiem dla każdego komunikatu do strukturyCREATESTRUCT. Struktura CREATESTRUCT z kolei zawiera wskaźnik przekazany do CreateWindowEx.
Poniżej przedstawiono sposób wyodrębniania wskaźnika do struktury danych. Najpierw pobierz strukturę CREATESTRUCT, odrzucając parametr lParam.
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
lpCreateParams element członkowski struktury CREATESTRUCT jest oryginalnym wskaźnikiem void określonym w CreateWindowEx. Pobierz wskaźnik do własnej struktury danych, rzutując lpCreateParams.
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
Następnie wywołaj funkcję SetWindowLongPtr i przekaż wskaźnik do struktury danych.
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
Celem tego ostatniego wywołania funkcji jest przechowywanie wskaźnika StateInfo w danych instancji okna. Gdy to zrobisz, zawsze możesz uzyskać wskaźnik z powrotem z okna, wywołując GetWindowLongPtr:
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
Każde okno ma własne dane wystąpienia, więc można utworzyć wiele okien i nadać każdemu okno własne wystąpienie struktury danych. Takie podejście jest szczególnie przydatne, jeśli zdefiniujesz klasę okien i utworzysz więcej niż jedno okno tej klasy — na przykład jeśli utworzysz niestandardową klasę sterowania. Wygodne jest zawijanie wywołania GetWindowLongPtr w małej funkcji pomocniczej.
inline StateInfo* GetAppState(HWND hwnd)
{
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
return pState;
}
Teraz możesz napisać procedurę okna w następujący sposób.
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
StateInfo *pState;
if (uMsg == WM_CREATE)
{
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
}
else
{
pState = GetAppState(hwnd);
}
switch (uMsg)
{
// Remainder of the window procedure not shown ...
}
return TRUE;
}
Podejście Object-Oriented
Możemy jeszcze bardziej rozszerzyć to podejście. Zdefiniowaliśmy już strukturę danych do przechowywania informacji o stanie okna. Warto zapewnić tę strukturę danych za pomocą funkcji składowych (metod), które działają na danych. To naturalnie prowadzi do projektu, w którym struktura (lub klasa) jest odpowiedzialna za wszystkie operacje w oknie. Procedura okna stałaby wówczas się częścią klasy.
Innymi słowy, chcielibyśmy przejść od tego:
// pseudocode
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
StateInfo *pState;
/* Get pState from the HWND. */
switch (uMsg)
{
case WM_SIZE:
HandleResize(pState, ...);
break;
case WM_PAINT:
HandlePaint(pState, ...);
break;
// And so forth.
}
}
Do tego:
// pseudocode
LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_SIZE:
this->HandleResize(...);
break;
case WM_PAINT:
this->HandlePaint(...);
break;
}
}
Jedynym problemem jest sposób podłączenia metody MyWindow::WindowProc. Funkcja RegisterClass oczekuje, że procedura okna będzie wskaźnikiem funkcji. W tym kontekście nie można przekazać wskaźnika do funkcji składowej (niestatycznej). Można jednak przekazać wskaźnik do funkcji składowej statycznej, a następnie delegować do funkcji składowej. Oto szablon klasy, który pokazuje to podejście:
template <class DERIVED_TYPE>
class BaseWindow
{
public:
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
DERIVED_TYPE *pThis = NULL;
if (uMsg == WM_NCCREATE)
{
CREATESTRUCT* pCreate = (CREATESTRUCT*)lParam;
pThis = (DERIVED_TYPE*)pCreate->lpCreateParams;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);
pThis->m_hwnd = hwnd;
}
else
{
pThis = (DERIVED_TYPE*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
}
if (pThis)
{
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
BaseWindow() : m_hwnd(NULL) { }
BOOL Create(
PCWSTR lpWindowName,
DWORD dwStyle,
DWORD dwExStyle = 0,
int x = CW_USEDEFAULT,
int y = CW_USEDEFAULT,
int nWidth = CW_USEDEFAULT,
int nHeight = CW_USEDEFAULT,
HWND hWndParent = 0,
HMENU hMenu = 0
)
{
WNDCLASS wc = {0};
wc.lpfnWndProc = DERIVED_TYPE::WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = ClassName();
RegisterClass(&wc);
m_hwnd = CreateWindowEx(
dwExStyle, ClassName(), lpWindowName, dwStyle, x, y,
nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), this
);
return (m_hwnd ? TRUE : FALSE);
}
HWND Window() const { return m_hwnd; }
protected:
virtual PCWSTR ClassName() const = 0;
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
HWND m_hwnd;
};
Klasa BaseWindow jest abstrakcyjną klasą bazową, z której pochodzą określone klasy okien. Na przykład poniżej znajduje się deklaracja prostej klasy pochodzącej z BaseWindow:
class MainWindow : public BaseWindow<MainWindow>
{
public:
PCWSTR ClassName() const { return L"Sample Window Class"; }
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};
Aby utworzyć okno, wywołaj BaseWindow::Create:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
MainWindow win;
if (!win.Create(L"Learn to Program Windows", WS_OVERLAPPEDWINDOW))
{
return 0;
}
ShowWindow(win.Window(), nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Metoda wirtualna abstrakcyjna BaseWindow::HandleMessage służy do implementowania procedury okna. Na przykład następująca implementacja jest równoważna procedurze okna pokazanej na początku modułu 1.
LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(m_hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(m_hwnd, &ps);
}
return 0;
default:
return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
}
return TRUE;
}
Zwróć uwagę, że uchwyt okna jest przechowywany w zmiennej składowej (m_hwnd), więc nie musimy przekazywać go jako parametru do HandleMessage.
Wiele istniejących struktur programowania systemu Windows, takich jak Klasy Microsoft Foundation (MFC) i Biblioteka active template Library (ATL), używają metod, które są zasadniczo podobne do przedstawionego tutaj. Oczywiście w pełni uogólniona struktura, taka jak MFC, jest bardziej złożona niż ten stosunkowo uproszczony przykład.
Następny
Module 2: Używanie COM w programie Windows