Programming with Microsoft Visual C++.NET 6ed [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming with Microsoft Visual C++.NET 6ed [Electronic resources] - نسخه متنی

George Shepherd, David Kruglinski

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید








A Custom Control DLL


Programmers have been using DLLs for custom controls since the early days of Windows because custom controls are neatly self-contained. The original custom controls were written in pure C and configured as standalone DLLs. Today, you can use the features of the MFC library in your custom controls, and the wizards help make coding easier. A regular DLL is the best choice for a custom control because the control doesn't need a C++ interface and it can be used by any development system that accepts custom controls (such as the Borland C++ compiler). You'll probably want to use the MFC dynamic linking option because the resulting DLL will be small and quick to load.


What Is a Custom Control?


Chapter 7 Windows common controls in Chapter 8 and ActiveX controls in Chapter 9 The custom control acts like an ordinary control, such as the edit control, in that it sends WM_COMMAND notification messages to its parent window and receives user-defined messages. The dialog editor lets you position custom controls in dialog templates. That's what the custom control button on the control palette is for.

You have a lot of freedom in designing your custom control. You can paint anything you want in its window (which is managed by the client application), and you can define any notification and inbound messages you need. You can use Class View's Properties window to map normal Windows messages in the control (WM_LBUTTONDOWN, for example), but you must manually map the user-defined messages and manually map the notification messages in the parent window class.



A Custom Control's Window Class


A dialog resource template specifies its custom controls by their symbolic window class names. Don't confuse the Win32 window class with the C++ class; the only similarity is the name. A window class is defined by a structure that contains the following:



    The name of the class



    A pointer to the WndProc function that receives messages sent to windows of the class



    Miscellaneous attributes, such as the background brush



The Win32 RegisterClass function copies the structure into process memory so that any function in the process can use the class to create a window. When the dialog window is initialized, Windows creates the custom control child windows from the window class names stored in the template.

Suppose that the control's WndProc function is inside a DLL. When the DLL is initialized (by a call to DllMain), it can call RegisterClass for the control. Because the DLL is part of the process, the client program can create child windows of the custom control class. To summarize, the client knows the name string of a control window class and it uses that class name to construct the child window. All the code for the control, including the WndProc function, is inside the DLL. All that's necessary is that the client load the DLL before creating the child window.



The MFC Library and the WndProc Function


Okay, so Windows calls the control's WndProc function for each message sent to that window. But you really don't want to write an old-fashioned switch-case statement—you want to map those messages to C++ member functions, as you've been doing all along. Now, in the DLL, you must rig up a C++ class that corresponds to the control's window class. Once you've done that, you can use Class View's Properties window to map messages.

The obvious part is the writing of the C++ class for the control. You simply use the Add Class Wizard to create a new class that's derived from CWnd. The tricky part is wiring the C++ class to the WndProc function and to the application framework's message pump. You'll see a real WndProc in the Ex20d example, but here's the pseudocode for a typical control WndProc function:

LRESULT MyControlWndProc(HWND hWnd, UINT message
WPARAM wParam, LPARAM lParam)
{
if (this is the first message for this window) {
CWnd* pWnd = new CMyControlWindowClass();
attach pWnd to hWnd
}
return AfxCallWndProc(pWnd, hWnd, message, WParam, lParam);
}

The MFC AfxCallWndProc function passes messages to the framework, which dispatches them to the member functions mapped in CMyControlWindowClass.



Custom Control Notification Messages


The control communicates with its parent window by sending it special WM_COMMAND notification messages with parameters, as shown here:



















Parameter


Usage


(HIWORD) wParam


Notification code


(LOWORD) wParam


Child window ID


lParam


Child window handle


The meaning of the notification code is arbitrary and depends on the control. The parent window must interpret the code based on its knowledge of the control. For example, the code 77 might mean that the user typed a character while positioned on the control.

The control might send a notification message such as this:

GetParent()->SendMessage(WM_COMMAND,
GetDlgCtrlID() | ID_NOTIFYCODE << 16, (LONG) GetSafeHwnd());

On the client side, you map the message with the MFC ON_CONTROL macro, like this:

ON_CONTROL(ID_NOTIFYCODE, IDC_MYCONTROL, OnClickedMyControl)

You then declare the handler function like this:

afx_msg void OnClickedMyControl();



User-Defined Messages Sent to the Control


User-defined messages (described in Chapter 7) are the means by which the client program communicates with the control. Because a standard message returns a 32-bit value if it is sent rather than posted, the client can obtain information from the control.



The Ex20d Example: A Custom Control


The Ex20d program is an MFC regular DLL that implements a traffic light control indicating off, red, yellow, and green states. When clicked with the left mouse button, the DLL sends a clicked notification message to its parent and responds to two user-defined messages, RYG_SETSTATE and RYG_GETSTATE. The state is an integer that represents the color. Credit for this example goes to Richard Wilton, who included the original C-language version of this control in his book Windows 3 Developer's Workshop (Microsoft Press, 1991).

The Ex20d project was originally generated using the MFC DLL Wizard, with linkage to the shared MFC DLL, just like Ex20c. The following is the code for the primary source file, with the added code in the InitInstance function in boldface. The dummy exported Ex20dEntry function exists solely to allow the DLL to be implicitly linked. The client program must include a call to this function. That call must be in an executable path in the program or the compiler will eliminate the call. Alternatively, the client program can call the Win32 LoadLibrary function in its InitInstance function to explicitly link the DLL.

Ex20d.cpp






// Ex20d.cpp : Defines the initialization routines for the DLL.
//
#include "stdafx.h"
#include "Ex20d.h"
#include "rygwnd.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
extern "C" __declspec(dllexport) void Ex20dEntry() {} // dummy function
// Application Wizard comments removed.


// CEx20dApp
BEGIN_MESSAGE_MAP(CEx20dApp, CWinApp)
END_MESSAGE_MAP()
// CEx20dApp construction
CEx20dApp::CEx20dApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
// The one and only CEx20dApp object
CEx20dApp theApp;
// CEx20dApp initialization
BOOL CEx20dApp::InitInstance()
{
CRygWnd::RegisterWndClass(AfxGetInstanceHandle());
CWinApp::InitInstance();
return TRUE;
}












The following is the code for the CRygWnd class, including the global RygWndProc function. You can use the Add Class Wizard to create this class by choosing Add Class from the Project menu. The code that paints the traffic light isn't very interesting, so we'll concentrate on the functions that are common to most custom controls. The static RegisterWndClass member function actually registers the RYG window class and must be called as soon as the DLL is loaded. The OnLButtonDown handler is called when the user presses the left mouse button inside the control window. It sends the clicked notification message to the parent window. The overridden PostNcDestroy function is important because it deletes the CRygWnd object when the client program destroys the control window. The OnGetState and OnSetState functions are called in response to user-defined messages sent by the client. Remember to copy the DLL to your system directory.

RygWnd.h






#pragma once
#define RYG_SETSTATE WM_USER + 0
#define RYG_GETSTATE WM_USER + 1
LRESULT CALLBACK AFX_EXPORT
RygWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// CRygWnd
class CRygWnd : public CWnd
{
private:
int m_nState; // 0=off, 1=red, 2=yellow, 3=green
static CRect s_rect;
static CPoint s_point;
static CRect s_rColor[3];
static CBrush s_bColor[4];
public:
static BOOL RegisterWndClass(HINSTANCE hInstance);
DECLARE_DYNAMIC(CRygWnd)
public:
CRygWnd();
virtual ~CRygWnd();
private:
void SetMapping(CDC* pDC);
void UpdateColor(CDC* pDC, int n);
protected:
afx_msg LRESULT OnSetState(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnGetState(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};












RygWnd.cpp






// RygWnd.cpp : implementation file
//
#include "stdafx.h"
#include "Ex20d.h"
#include "RygWnd.h"
LRESULT CALLBACK AFX_EXPORT
RygWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CWnd* pWnd;
pWnd = CWnd::FromHandlePermanent(hWnd);
if (pWnd == NULL) {
// Assume that client created a CRygWnd window
pWnd = new CRygWnd();
pWnd->Attach(hWnd);
}
ASSERT(pWnd->m_hWnd == hWnd);
ASSERT(pWnd == CWnd::FromHandlePermanent(hWnd));
LRESULT lResult = AfxCallWndProc(pWnd, hWnd, message,
wParam, lParam);
return lResult;
}
// static data members
CRect CRygWnd::s_rect(-500, 1000, 500, -1000); // outer rectangle
CPoint CRygWnd::s_point(300, 300); // rounded corners
CRect CRygWnd::s_rColor[] = {CRect(-250, 800, 250, 300),
CRect(-250, 250, 250, -250),
CRect(-250, -300, 250, -800)};
CBrush CRygWnd::s_bColor[] = {RGB(192, 192, 192),
RGB(0xFF, 0x00, 0x00),
RGB(0xFF, 0xFF, 0x00),
RGB(0x00, 0xFF, 0x00)};
BOOL CRygWnd::RegisterWndClass(HINSTANCE hInstance) // static member
// function
{
WNDCLASS wc;
wc.lpszClassName = "RYG"; // matches class name in client
wc.hInstance = hInstance;
wc.lpfnWndProc = RygWndProc;
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hIcon = 0;
wc.lpszMenuName = NULL;
wc.hbrBackground = (HBRUSH) ::GetStockObject(LTGRAY_BRUSH);
wc.style = CS_GLOBALCLASS;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
return (::RegisterClass(&wc) != 0);
}
// CRygWnd
IMPLEMENT_DYNAMIC(CRygWnd, CWnd)
CRygWnd::CRygWnd()
{
m_nState = 0;
TRACE("CRygWnd constructor\n");
}
CRygWnd::~CRygWnd()
{
TRACE("CRygWnd destructor\n");
}
BEGIN_MESSAGE_MAP(CRygWnd, CWnd)
ON_MESSAGE(RYG_SETSTATE, OnSetState)
ON_MESSAGE(RYG_GETSTATE, OnGetState)
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
void CRygWnd::SetMapping(CDC* pDC)
{
CRect clientRect;
GetClientRect(clientRect);
pDC->SetMapMode(MM_ISOTROPIC);
pDC->SetWindowExt(1000, 2000);
pDC->SetViewportExt(clientRect.right, -clientRect.bottom);
pDC->SetViewportOrg(clientRect.right / 2, clientRect.bottom / 2);
}
void CRygWnd::UpdateColor(CDC* pDC, int n)
{
if (m_nState == n + 1) {
pDC->SelectObject(&s_bColor[n+1]);
}
else {
pDC->SelectObject(&s_bColor[0]);
}
pDC->Ellipse(s_rColor[n]);
}
// CRygWnd message handlers
void CRygWnd::OnPaint()
{
int i;
CPaintDC dc(this); // device context for painting
SetMapping(&dc);
dc.SelectStockObject(DKGRAY_BRUSH);
dc.RoundRect(s_rect, s_point);
for (i = 0; i < 3; i++) {
UpdateColor(&dc, i);
}
}
void CRygWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
// Notification code is HIWORD of wParam, 0 in this case
GetParent()->SendMessage(WM_COMMAND, GetDlgCtrlID(),
(LONG) GetSafeHwnd()); // 0
}
void CRygWnd::PostNcDestroy()
{
TRACE("CRygWnd::PostNcDestroy\n");
delete this; // CWnd::PostNcDestroy does nothing
}
LRESULT CRygWnd::OnSetState(WPARAM wParam, LPARAM lParam)
{
TRACE("CRygWnd::SetState, wParam = %d\n", wParam);
m_nState = (int) wParam;
Invalidate(FALSE);
return 0L;
}
LRESULT CRygWnd::OnGetState(WPARAM wParam, LPARAM lParam)
{
TRACE("CRygWnd::GetState\n");
return m_nState;
}













Revising the Updated Ex20b Example: Adding Code to Test Ex20d.dll


The Ex20b program already links to the Ex20a and Ex20c DLLs. Now we'll revise the project to implicitly link to the Ex20d custom control.

Here are the steps for updating the Ex20b example:



    Add a new dialog resource and class to the Ex20b project.Use the dialog editor to create the IDD_EX20D template with a custom control with the child window ID IDC_RYG, as shown here:

    Specify RYG as the window class name of the custom control using the dialog editor's Properties window.

    Then use the Add Class Wizard to generate a class CTest20dDialog that is derived from CDialog.



    Edit the

    Test20dDialog.h file. Add the following private data member:

    enum { OFF, RED, YELLOW, GREEN } m_nState;

    Also add the following import and user-defined message IDs:

    extern "C" __declspec(dllimport) void Ex20dEntry(); // dummy
    // function
    #define RYG_SETSTATE WM_USER + 0
    #define RYG_GETSTATE WM_USER + 1



    Edit the constructor in

    Test20dDialog.cpp to initialize the state data member. Add the following boldface code:

    CTest20dDialog::CTest20dDialog(CWnd* pParent /*=NULL*/)
    : CDialog(CTest20dDialog::IDD, pParent)
    {
    m_nState = OFF;
    Ex20dEntry(); // Make sure DLL gets loaded
    }



    Map the control's clicked notification message. You can't use Class View's Properties window here, so you must add the message map entry and handler function in the

    Test20dDialog.cpp file, as shown here:

    void CTest20dDialog::OnClickedRyg()
    {
    switch(m_nState) {
    case OFF:
    m_nState = RED;
    break;
    case RED:
    m_nState = YELLOW;
    break;
    case YELLOW:
    m_nState = GREEN;
    break;
    case GREEN:
    m_nState = OFF;
    break;
    }
    GetDlgItem(IDC_RYG)->SendMessage(RYG_SETSTATE, m_nState);
    return;
    }
    BEGIN_MESSAGE_MAP(CTest20dDialog, CDialog)
    ON_CONTROL(0, IDC_RYG, OnClickedRyg) // Notification code is 0
    END_MESSAGE_MAP()

    When the dialog box gets the clicked notification message, it sends the RYG_SETSTATE message back to the control in order to change the color. Don't forget to add this prototype in the

    Test20dDialog.h file:

    afx_msg void OnClickedRyg();



    Integrate the CTest20dDialog class into the Ex20b application.



    You'll need to add a second command to the Test menu—an Ex20d DLL option with the ID ID_TEST_EX20DDLL. Use Class View's Properties window to map this option to a member function in the CEx20bView class, and then code the handler in

    Ex20bView.cpp as follows:

    void CEx20bView::OnTestEx20ddll()
    {
    CTest20dDialog dlg;
    dlg.DoModal();
    }

    Of course, you have to add the following line to

    Ex20bView.cpp :

    #include "Test20dDialog.h"



    Add the Ex20d import library to the linker's input library list. Choose Add Existing Item from the Project menu. Add \vcppnet\Ex20d\Debug\Ex20.lib to the project. With this addition, the program should implicitly link to all three DLLs.



    Build and test the updated Ex20b application. Choose Ex20d DLL from the Test menu. Try clicking the traffic light with the left mouse button. The traffic-light color should change.





/ 319