Menggunakan Beberapa Antarmuka Dokumen

Bagian ini menjelaskan cara melakukan tugas-tugas berikut:

Untuk mengilustrasikan tugas-tugas ini, bagian ini menyertakan contoh dari Multipad, aplikasi antarmuka multi-dokumen (MDI) yang khas.

Mendaftarkan Kelas Jendela Anak dan Bingkai

Aplikasi MDI yang khas harus mendaftarkan dua kelas jendela: satu untuk jendela bingkai dan satu untuk jendela anaknya. Jika aplikasi mendukung lebih dari satu jenis dokumen (misalnya, spreadsheet dan bagan), aplikasi harus mendaftarkan kelas jendela untuk setiap jenis.

Struktur kelas untuk jendela bingkai mirip dengan struktur kelas untuk jendela utama dalam aplikasi non-MDI. Struktur kelas untuk jendela anak MDI sedikit berbeda dari struktur untuk jendela anak dalam aplikasi non-MDI sebagai berikut:

  • Struktur kelas harus memiliki ikon, karena pengguna dapat meminimalkan jendela anak MDI seolah-olah itu adalah jendela aplikasi normal.
  • Nama menu harus NULL, karena jendela anak MDI tidak dapat memiliki menunya sendiri.
  • Struktur kelas harus mencadangkan ruang ekstra dalam struktur jendela. Dengan ruang ini, aplikasi dapat mengaitkan data, seperti nama file, dengan jendela anak tertentu.

Contoh berikut menunjukkan bagaimana Multipad mendaftarkan kelas bingkai dan jendela anaknya.

BOOL WINAPI InitializeApplication() 
{ 
    WNDCLASS wc; 
 
    // Register the frame window class. 
 
    wc.style         = 0; 
    wc.lpfnWndProc   = (WNDPROC) MPFrameWndProc; 
    wc.cbClsExtra    = 0; 
    wc.cbWndExtra    = 0; 
    wc.hInstance     = hInst; 
    wc.hIcon         = LoadIcon(hInst, IDMULTIPAD); 
    wc.hCursor       = LoadCursor((HANDLE) NULL, IDC_ARROW); 
    wc.hbrBackground = (HBRUSH) (COLOR_APPWORKSPACE + 1); 
    wc.lpszMenuName  = IDMULTIPAD; 
    wc.lpszClassName = szFrame; 
 
    if (!RegisterClass (&wc) ) 
        return FALSE; 
 
    // Register the MDI child window class. 
 
    wc.lpfnWndProc   = (WNDPROC) MPMDIChildWndProc; 
    wc.hIcon         = LoadIcon(hInst, IDNOTE); 
    wc.lpszMenuName  = (LPCTSTR) NULL; 
    wc.cbWndExtra    = CBWNDEXTRA; 
    wc.lpszClassName = szChild; 
 
    if (!RegisterClass(&wc)) 
        return FALSE; 
 
    return TRUE; 
} 

Membuat Bingkai dan Jendela Anak

Setelah mendaftarkan kelas jendelanya, aplikasi MDI dapat membuat jendelanya. Pertama, ini membuat jendela bingkainya dengan menggunakan fungsi CreateWindow atau CreateWindowEx . Setelah membuat jendela bingkainya, aplikasi membuat jendela kliennya, sekali lagi dengan menggunakan CreateWindow atau CreateWindowEx. Aplikasi harus menentukan MDICLIENT sebagai nama kelas jendela klien; MDICLIENT adalah kelas jendela yang telah terdaftar sebelumnya yang ditentukan oleh sistem. Parameter lpvParam dari CreateWindow atau CreateWindowEx harus menunjuk ke struktur CLIENTCREATESTRUCT . Struktur ini berisi anggota yang dijelaskan dalam tabel berikut ini:

Anggota Deskripsi
hWindowMenu Tangani ke menu jendela yang digunakan untuk mengontrol jendela anak MDI. Saat jendela anak dibuat, aplikasi menambahkan judulnya ke menu jendela sebagai item menu. Pengguna kemudian dapat mengaktifkan jendela anak dengan mengklik judulnya pada menu jendela.
idFirstChild Menentukan pengidentifikasi jendela anak MDI pertama. Jendela anak MDI pertama yang dibuat diberi pengidentifikasi ini. Jendela tambahan dibuat dengan pengidentifikasi jendela bertahap. Ketika jendela anak dihancurkan, sistem segera menetapkan ulang pengidentifikasi jendela untuk menjaga rentang mereka tetap berdekatan.

 

Saat judul jendela anak ditambahkan ke menu jendela, sistem menetapkan pengidentifikasi ke jendela anak. Saat pengguna mengklik judul jendela anak, jendela bingkai menerima pesan WM_COMMAND dengan pengidentifikasi dalam parameter wParam . Anda harus menentukan nilai untuk anggota idFirstChild yang tidak bertentangan dengan pengidentifikasi item menu di menu jendela bingkai.

Prosedur jendela bingkai Multipad membuat jendela klien MDI saat memproses pesan WM_CREATE . Contoh berikut menunjukkan bagaimana jendela klien dibuat.

case WM_CREATE: 
    { 
        CLIENTCREATESTRUCT ccs; 
 
        // Retrieve the handle to the window menu and assign the 
        // first child window identifier. 
 
        ccs.hWindowMenu = GetSubMenu(GetMenu(hwnd), WINDOWMENU); 
        ccs.idFirstChild = IDM_WINDOWCHILD; 
 
        // Create the MDI client window. 
 
        hwndMDIClient = CreateWindow( "MDICLIENT", (LPCTSTR) NULL, 
            WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL, 
            0, 0, 0, 0, hwnd, (HMENU) 0xCAC, hInst, (LPSTR) &ccs); 
 
        ShowWindow(hwndMDIClient, SW_SHOW); 
    } 
    break; 

Judul jendela anak ditambahkan ke bagian bawah menu jendela. Jika aplikasi menambahkan string ke menu jendela dengan menggunakan fungsi AppendMenu , string ini dapat ditimpa oleh judul jendela anak saat menu jendela dicat ulang (setiap kali jendela anak dibuat atau dihancurkan). Aplikasi MDI yang menambahkan string ke menu jendelanya harus menggunakan fungsi InsertMenu dan memverifikasi bahwa judul jendela anak belum menimpa string baru ini.

Gunakan gaya WS_CLIPCHILDREN untuk membuat jendela klien MDI untuk mencegah jendela melukis melalui jendela anaknya.

Menulis Perulangan Pesan Utama

Perulangan pesan utama aplikasi MDI mirip dengan kunci akselerator penanganan aplikasi non-MDI. Perbedaannya adalah bahwa perulangan pesan MDI memanggil fungsi TranslateMDISysAccel sebelum memeriksa kunci akselerator yang ditentukan aplikasi atau sebelum mengirimkan pesan.

Contoh berikut menunjukkan perulangan pesan dari aplikasi MDI yang khas. Perhatikan bahwa GetMessage dapat mengembalikan -1 jika ada kesalahan.

MSG msg;
BOOL bRet;

while ((bRet = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0)
{
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else 
    { 
        if (!TranslateMDISysAccel(hwndMDIClient, &msg) && 
                !TranslateAccelerator(hwndFrame, hAccel, &msg))
        { 
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        } 
    } 
}

Fungsi TranslateMDISysAccel menerjemahkan pesan WM_KEYDOWN ke dalam pesan WM_SYSCOMMAND dan mengirimkannya ke jendela anak MDI aktif. Jika pesan bukan pesan Akselerator MDI, fungsi mengembalikan FALSE, dalam hal ini aplikasi menggunakan fungsi TranslateAccelerator untuk menentukan apakah salah satu tombol akselerator yang ditentukan aplikasi ditekan. Jika tidak, perulangan mengirimkan pesan ke prosedur jendela yang sesuai.

Menulis Prosedur Jendela Bingkai

Prosedur jendela untuk jendela bingkai MDI mirip dengan jendela utama aplikasi non-MDI. Perbedaannya adalah bahwa prosedur jendela bingkai meneruskan semua pesan yang tidak ditanganinya ke fungsi DefFrameProc daripada ke fungsi DefWindowProc . Selain itu, prosedur jendela bingkai juga harus meneruskan beberapa pesan yang ditanganinya, termasuk yang tercantum dalam tabel berikut.

Pesan Respons
WM_COMMAND Mengaktifkan jendela anak MDI yang dipilih pengguna. Pesan ini dikirim ketika pengguna memilih jendela anak MDI dari menu jendela jendela bingkai MDI. Pengidentifikasi jendela yang menyertai pesan ini mengidentifikasi jendela anak MDI yang akan diaktifkan.
WM_MENUCHAR Membuka menu jendela jendela anak MDI aktif saat pengguna menekan kombinasi tombol ALT+ – (minus).
WM_SETFOCUS Meneruskan fokus keyboard ke jendela klien MDI, yang pada gilirannya meneruskannya ke jendela anak MDI aktif.
WM_SIZE Mengubah ukuran jendela klien MDI agar pas di area klien jendela bingkai baru. Jika prosedur jendela bingkai mengukur jendela klien MDI ke ukuran yang berbeda, itu tidak boleh meneruskan pesan ke fungsi DefWindowProc .

 

Prosedur jendela bingkai di Multipad disebut MPFrameWndProc. Penanganan pesan lain oleh MPFrameWndProc mirip dengan aplikasi non-MDI. WM_COMMAND pesan di Multipad ditangani oleh fungsi CommandHandler yang ditentukan secara lokal. Untuk pesan perintah yang tidak ditangani Multipad, CommandHandler memanggil fungsi DefFrameProc . Jika Multipad tidak menggunakan DefFrameProc secara default, pengguna tidak dapat mengaktifkan jendela anak dari menu jendela, karena pesan WM_COMMAND dikirim dengan mengklik item menu jendela akan hilang.

Menulis Prosedur Jendela Anak

Seperti prosedur jendela bingkai, prosedur jendela anak MDI menggunakan fungsi khusus untuk memproses pesan secara default. Semua pesan yang tidak ditangani prosedur jendela anak harus diteruskan ke fungsi DefMDIChildProc daripada ke fungsi DefWindowProc . Selain itu, beberapa pesan manajemen jendela harus diteruskan ke DefMDIChildProc, bahkan jika aplikasi menangani pesan, agar MDI berfungsi dengan benar. Berikut adalah pesan yang harus diteruskan aplikasi ke DefMDIChildProc.

Pesan Respons
WM_CHILDACTIVATE Melakukan pemrosesan aktivasi ketika jendela anak MDI berukuran, dipindahkan, atau ditampilkan. Pesan ini harus diteruskan.
WM_GETMINMAXINFO Menghitung ukuran jendela anak MDI yang dimaksimalkan, berdasarkan ukuran jendela klien MDI saat ini.
WM_MENUCHAR Meneruskan pesan ke jendela bingkai MDI.
WM_MOVE Menghitung ulang bilah gulir klien MDI, jika ada.
WM_SETFOCUS Mengaktifkan jendela anak, jika bukan jendela anak MDI aktif.
WM_SIZE Melakukan operasi yang diperlukan untuk mengubah ukuran jendela, terutama untuk memaksimalkan atau memulihkan jendela anak MDI. Gagal meneruskan pesan ini ke fungsi DefMDIChildProc menghasilkan hasil yang sangat tidak diinginkan.
WM_SYSCOMMAND Menangani perintah menu jendela (sebelumnya dikenal sebagai sistem): SC_NEXTWINDOW, SC_PREVWINDOW, SC_MOVE, SC_SIZE, dan SC_MAXIMIZE.

 

Membuat Jendela Anak

Untuk membuat jendela anak MDI, aplikasi dapat memanggil fungsi CreateMDIWindow atau mengirim pesan WM_MDICREATE ke jendela klien MDI. (Aplikasi dapat menggunakan fungsi CreateWindowEx dengan gaya WS_EX_MDICHILD untuk membuat jendela anak MDI.) Aplikasi MDI satu utas dapat menggunakan salah satu metode untuk membuat jendela anak. Utas dalam aplikasi MDI multithread harus menggunakan fungsi CreateMDIWindow atau CreateWindowEx untuk membuat jendela anak di utas yang berbeda.

Parameter lParam dari pesan WM_MDICREATE adalah penunjuk jauh ke struktur MDICREATESTRUCT . Struktur mencakup empat anggota dimensi: x dan y, yang menunjukkan posisi horizontal dan vertikal jendela, dan cx dan cy, yang menunjukkan luas horizontal dan vertikal jendela. Salah satu anggota ini dapat ditetapkan secara eksplisit oleh aplikasi, atau mereka dapat diatur ke CW_USEDEFAULT, dalam hal ini sistem memilih posisi, ukuran, atau keduanya, sesuai dengan algoritma bertingkat. Bagaimanapun, keempat anggota harus diinisialisasi. Multipad menggunakan CW_USEDEFAULT untuk semua dimensi.

Anggota terakhir dari struktur MDICREATESTRUCT adalah anggota gaya , yang mungkin berisi bit gaya untuk jendela. Untuk membuat jendela anak MDI yang dapat memiliki kombinasi gaya jendela apa pun, tentukan gaya jendela MDIS_ALLCHILDSTYLES . Ketika gaya ini tidak ditentukan, jendela anak MDI memiliki gaya WS_MINIMIZE, WS_MAXIMIZE, WS_HSCROLL, dan WS_VSCROLL sebagai pengaturan default.

Multipad membuat jendela anak MDI-nya dengan menggunakan fungsi AddFile yang ditentukan secara lokal (terletak di file sumber MPFILE. C). Fungsi AddFile mengatur judul jendela anak dengan menetapkan anggota szTitle dari struktur MDICREATESTRUCT jendela ke nama file yang sedang diedit atau menjadi "Untitled." Anggota szClass diatur ke nama kelas jendela anak MDI yang terdaftar dalam fungsi InitializeApplication Multipad. Anggota hOwner diatur ke handel instans aplikasi.

Contoh berikut menunjukkan fungsi AddFile di Multipad.

HWND APIENTRY AddFile(pName) 
TCHAR * pName; 
{ 
    HWND hwnd; 
    TCHAR sz[160]; 
    MDICREATESTRUCT mcs; 
 
    if (!pName) 
    { 
 
        // If the pName parameter is NULL, load the "Untitled" 
        // string from the STRINGTABLE resource and set the szTitle 
        // member of MDICREATESTRUCT. 
 
        LoadString(hInst, IDS_UNTITLED, sz, sizeof(sz)/sizeof(TCHAR)); 
        mcs.szTitle = (LPCTSTR) sz; 
    } 
    else 
 
        // Title the window with the full path and filename, 
        // obtained by calling the OpenFile function with the 
        // OF_PARSE flag, which is called before AddFile(). 
 
        mcs.szTitle = of.szPathName; 
 
    mcs.szClass = szChild; 
    mcs.hOwner  = hInst; 
 
    // Use the default size for the child window. 
 
    mcs.x = mcs.cx = CW_USEDEFAULT; 
    mcs.y = mcs.cy = CW_USEDEFAULT; 
 
    // Give the child window the default style. The styleDefault 
    // variable is defined in MULTIPAD.C. 
 
    mcs.style = styleDefault; 
 
    // Tell the MDI client window to create the child window. 
 
    hwnd = (HWND) SendMessage (hwndMDIClient, WM_MDICREATE, 0, 
        (LONG) (LPMDICREATESTRUCT) &mcs); 
 
    // If the file is found, read its contents into the child 
    // window's client area. 
 
    if (pName) 
    { 
        if (!LoadFile(hwnd, pName)) 
        { 
 
            // Cannot load the file; close the window. 
 
            SendMessage(hwndMDIClient, WM_MDIDESTROY, 
                (DWORD) hwnd, 0L); 
        } 
    } 
    return hwnd; 
} 

Penunjuk yang diteruskan dalam parameter lParam dari pesan WM_MDICREATE diteruskan ke fungsi CreateWindow dan muncul sebagai anggota pertama dalam struktur CREATESTRUCT , diteruskan dalam pesan WM_CREATE . Di Multipad, jendela anak menginisialisasi dirinya sendiri selama pemrosesan pesan WM_CREATE dengan menginisialisasi variabel dokumen dalam data tambahannya dan dengan membuat jendela anak kontrol edit.