Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Une procédure de fenêtre est simplement une fonction qui est appelée pour chaque message, de sorte qu’elle est intrinsèquement sans état. Par conséquent, vous avez besoin d’un moyen de suivre l’état de votre application d’un appel de fonction à l’autre.
L’approche la plus simple consiste simplement à placer tout dans des variables globales. Cela fonctionne assez bien pour les petits programmes, et de nombreux exemples de SDK utilisent cette approche. Toutefois, dans un grand programme, il conduit à une prolifération de variables globales. En outre, vous pouvez avoir plusieurs fenêtres, chacune avec sa propre procédure de fenêtre. Suivre quelle fenêtre doit accéder à quelles variables devient déroutant et source d’erreurs.
La fonction CreateWindowEx permet de transmettre n’importe quelle structure de données à une fenêtre. Lorsque cette fonction est appelée, elle envoie les deux messages suivants à votre procédure de fenêtre :
Ces messages sont envoyés dans l’ordre indiqué. (Ce ne sont pas les deux seuls messages envoyés pendant CreateWindowEx, mais nous pouvons ignorer les autres pour cette discussion.)
Les messages WM_NCCREATE et WM_CREATE sont envoyés avant que la fenêtre ne devienne visible. Cela leur permet d’initialiser votre interface utilisateur, par exemple pour déterminer la disposition initiale de la fenêtre.
Le dernier paramètre de CreateWindowEx est un pointeur de type void*. Vous pouvez passer n’importe quelle valeur de pointeur souhaitée dans ce paramètre. Lorsque la procédure de fenêtre gère le message WM_NCCREATE ou WM_CREATE , elle peut extraire cette valeur des données du message.
Voyons comment utiliser ce paramètre pour transmettre des données d’application à votre fenêtre. Tout d’abord, définissez une classe ou une structure qui contient des informations d’état.
// Define a structure to hold some state information.
struct StateInfo {
// ... (struct members not shown)
};
Lorsque vous appelez CreateWindowEx, passez un pointeur vers cette structure dans le paramètre final 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
);
Lorsque vous recevez les messages WM_NCCREATE et WM_CREATE , le paramètre lParam de chaque message est un pointeur vers une structure CREATESTRUCT . La structure CREATESTRUCT contient à son tour le pointeur que vous avez passé dans CreateWindowEx.
Voici comment extraire le pointeur vers votre structure de données. Tout d’abord, obtenez la structure CREATESTRUCT en castant le paramètre lParam .
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
Le membre lpCreateParams de la structure CREATESTRUCT est le pointeur void d’origine que vous avez spécifié dans CreateWindowEx. Obtenez un pointeur vers votre propre structure de données en castant lpCreateParams.
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
Ensuite, appelez la fonction SetWindowLongPtr et transmettez le pointeur à votre structure de données.
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
L’objectif de cet appel de fonction est de stocker le pointeur StateInfo dans les données d’instance de la fenêtre. Une fois cette opération effectuée, vous pouvez toujours récupérer le pointeur à partir de la fenêtre en appelant GetWindowLongPtr :
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
Chaque fenêtre possède ses propres données d’instance. Vous pouvez donc créer plusieurs fenêtres et donner à chaque fenêtre sa propre instance de la structure de données. Cette approche est particulièrement utile si vous définissez une classe de fenêtres et créez plusieurs fenêtres de cette classe, par exemple si vous créez une classe de contrôle personnalisée. Il est pratique d’encapsuler l’appel GetWindowLongPtr dans une petite fonction d’assistance.
inline StateInfo* GetAppState(HWND hwnd)
{
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
return pState;
}
Vous pouvez maintenant écrire votre procédure de fenêtre comme suit.
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;
}
Une approche orientée objet
Nous pouvons étendre cette approche plus loin. Nous avons déjà défini une structure de données pour contenir des informations d’état sur la fenêtre. Il est judicieux de fournir cette structure de données avec des fonctions membres (méthodes) qui fonctionnent sur les données. Cela conduit naturellement à une conception où la structure (ou la classe) est responsable de toutes les opérations sur la fenêtre. La procédure de la fenêtre deviendrait alors partie intégrante de la classe.
En d’autres termes, nous aimerions aller de ceci :
// 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.
}
}
Par ceci :
// pseudocode
LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_SIZE:
this->HandleResize(...);
break;
case WM_PAINT:
this->HandlePaint(...);
break;
}
}
Le seul problème est la façon de raccorder la MyWindow::WindowProc méthode. La fonction RegisterClass s’attend à ce que la procédure de fenêtre soit un pointeur de fonction. Vous ne pouvez pas passer un pointeur vers une fonction membre (non statique) dans ce contexte. Toutefois, vous pouvez passer un pointeur vers une fonction membre statique , puis déléguer à la fonction membre. Voici un modèle de classe qui montre cette approche :
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;
};
La BaseWindow classe est une classe de base abstraite à partir de laquelle des classes de fenêtre spécifiques sont dérivées. Par exemple, voici la déclaration d’une classe simple dérivée de BaseWindow:
class MainWindow : public BaseWindow<MainWindow>
{
public:
PCWSTR ClassName() const { return L"Sample Window Class"; }
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};
Pour créer la fenêtre, appelez BaseWindow::Create:
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, 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;
}
La méthode pure-virtuelle BaseWindow::HandleMessage est utilisée pour implémenter la procédure de fenêtre. Par exemple, l’implémentation suivante équivaut à la procédure de fenêtre indiquée au début du module 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;
}
Notez que le handle de fenêtre est stocké dans une variable membre (m_hwnd), nous n’avons donc pas besoin de le transmettre en tant que paramètre à HandleMessage.
La plupart des infrastructures de programmation Windows existantes, telles que Microsoft Foundation Classes (MFC) et La bibliothèque de modèles actifs (ATL), utilisent des approches qui sont essentiellement similaires à celles présentées ici. Bien sûr, un framework entièrement généralisé tel que MFC est plus complexe que cet exemple relativement simpliste.
Suivant
Module 2 : utilisation de COM dans votre programme de Windows