Mengelola Status Aplikasi

Prosedur jendela hanyalah fungsi yang dipanggil untuk setiap pesan, sehingga secara inheren tanpa status. Oleh karena itu, Anda memerlukan cara untuk melacak status aplikasi Anda dari satu panggilan fungsi ke panggilan berikutnya.

Pendekatan paling sederhana adalah hanya untuk menempatkan semuanya dalam variabel global. Ini bekerja cukup baik untuk program kecil, dan banyak sampel SDK menggunakan pendekatan ini. Namun, dalam program besar mengarah pada proliferasi variabel global. Selain itu, Anda mungkin memiliki beberapa jendela, masing-masing dengan prosedur jendelanya sendiri. Melacak jendela mana yang harus mengakses variabel mana yang menjadi membingungkan dan rawan kesalahan.

Fungsi CreateWindowEx menyediakan cara untuk meneruskan struktur data apa pun ke jendela. Ketika fungsi ini dipanggil, fungsi ini mengirim dua pesan berikut ke prosedur jendela Anda:

Pesan-pesan ini dikirim dalam urutan yang tercantum. (Ini bukan satu-satunya dua pesan yang dikirim selama CreateWindowEx, tetapi kita dapat mengabaikan yang lain untuk diskusi ini.)

Pesan WM_NCCREATE dan WM_CREATE dikirim sebelum jendela terlihat. Itu menjadikannya tempat yang baik untuk menginisialisasi UI Anda—misalnya, untuk menentukan tata letak awal jendela.

Parameter terakhir dari CreateWindowEx adalah penunjuk jenis void*. Anda dapat meneruskan nilai pointer apa pun yang Anda inginkan dalam parameter ini. Saat prosedur jendela menangani pesan WM_NCCREATE atau WM_CREATE , prosedur jendela dapat mengekstrak nilai ini dari data pesan.

Mari kita lihat bagaimana Anda akan menggunakan parameter ini untuk meneruskan data aplikasi ke jendela Anda. Pertama, tentukan kelas atau struktur yang menyimpan informasi status.

// Define a structure to hold some state information.

struct StateInfo {
    // ... (struct members not shown)
};

Saat Anda memanggil CreateWindowEx, teruskan pointer ke struktur ini dalam parameter 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
    );

Saat Anda menerima pesan WM_NCCREATE dan WM_CREATE , parameter lParam dari setiap pesan adalah penunjuk ke struktur CREATESTRUCT . Struktur CREATESTRUCT , pada gilirannya, berisi pointer yang Anda lewati ke CreateWindowEx.

diagram yang memperlihatkan tata letak struktur createstruct

Berikut adalah cara Anda mengekstrak pointer ke struktur data Anda. Pertama, dapatkan struktur CREATESTRUCT dengan mentransmisikan parameter lParam .

CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);

Anggota lpCreateParams dari struktur CREATESTRUCT adalah penunjuk kekosongan asli yang Anda tentukan di CreateWindowEx. Dapatkan pointer ke struktur data Anda sendiri dengan mentransmisikan lpCreateParams.

pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);

Selanjutnya, panggil fungsi SetWindowLongPtr dan teruskan penunjuk ke struktur data Anda.

SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);

Tujuan dari panggilan fungsi terakhir ini adalah untuk menyimpan pointer StateInfo dalam data instans untuk jendela. Setelah Anda melakukan ini, Anda selalu bisa mendapatkan pointer kembali dari jendela dengan memanggil GetWindowLongPtr:

LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);

Setiap jendela memiliki data instansnya sendiri, sehingga Anda dapat membuat beberapa jendela dan memberi setiap jendela instans struktur datanya sendiri. Pendekatan ini sangat berguna jika Anda menentukan kelas jendela dan membuat lebih dari satu jendela kelas tersebut—misalnya, jika Anda membuat kelas kontrol kustom. Lebih mudah untuk membungkus panggilan GetWindowLongPtr dalam fungsi pembantu kecil.

inline StateInfo* GetAppState(HWND hwnd)
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
    return pState;
}

Sekarang Anda dapat menulis prosedur jendela Anda sebagai berikut.

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;
}

Pendekatan Object-Oriented

Kita dapat memperluas pendekatan ini lebih jauh. Kami telah mendefinisikan struktur data untuk menyimpan informasi status tentang jendela. Masuk akal untuk menyediakan struktur data ini dengan fungsi anggota (metode) yang beroperasi pada data. Ini secara alami mengarah pada desain di mana struktur (atau kelas) bertanggung jawab atas semua operasi di jendela. Prosedur jendela kemudian akan menjadi bagian dari kelas .

Dengan kata lain, kami ingin pergi dari ini:

// 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.
    }
}

Untuk ini:

// pseudocode

LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            this->HandleResize(...);
            break;

        case WM_PAINT:
            this->HandlePaint(...);
            break;
    }
}

Satu-satunya masalah adalah cara menghubungkan MyWindow::WindowProc metode . Fungsi RegisterClass mengharapkan prosedur jendela menjadi penunjuk fungsi. Anda tidak dapat meneruskan penunjuk ke fungsi anggota (non-statis) dalam konteks ini. Namun, Anda dapat meneruskan pointer ke fungsi anggota statis lalu mendelegasikan ke fungsi anggota. Berikut adalah templat kelas yang menunjukkan pendekatan ini:

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;
};

Kelas BaseWindow adalah kelas dasar abstrak, dari mana kelas jendela tertentu diturunkan. Misalnya, berikut adalah deklarasi kelas sederhana yang berasal dari BaseWindow:

class MainWindow : public BaseWindow<MainWindow>
{
public:
    PCWSTR  ClassName() const { return L"Sample Window Class"; }
    LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};

Untuk membuat jendela, panggil 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;
}

Metode pure-virtual BaseWindow::HandleMessage digunakan untuk mengimplementasikan prosedur jendela. Misalnya, implementasi berikut setara dengan prosedur jendela yang ditunjukkan pada awal Modul 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;
}

Perhatikan bahwa handel jendela disimpan dalam variabel anggota (m_hwnd), jadi kita tidak perlu meneruskannya sebagai parameter ke HandleMessage.

Banyak kerangka kerja pemrograman Windows yang ada, seperti Microsoft Foundation Classes (MFC) dan Active Template Library (ATL), menggunakan pendekatan yang pada dasarnya mirip dengan yang ditunjukkan di sini. Tentu saja, kerangka kerja yang sepenuhnya umum seperti MFC lebih kompleks daripada contoh yang relatif sederhana ini.

Berikutnya

Modul 2: Menggunakan COM di Program Windows Anda

Sampel BaseWindow