ATL interface class for wtl Development Guide for MFC programmers

Source: Internet
Author: User
Guide

WTL has two sides, indeed. It is not as powerful as the MFC interface (GUI) class library, but can generate very small executable files. If you use MFC for interface programming like me, you will feel that the interface control provided by MFC is very comfortable to encapsulate and use, not to mention the built-in Message Processing Mechanism of MFC. Of course, if you don't want your program to increase the size of several hundred kb simply by using the MFC framework, WTL is your choice. Of course, we also need to overcome some obstacles:

1) The ATL template class looks a little weird at the beginning
2) No wizard-like support, so you need to manually process all message mappings.
3) MSDN does not support official documentation. You need to collect relevant documents everywhere, or even view the source code of WTL.
4) You cannot buy any reference books.
5) No official support from Microsoft
6) The ATL/WTL window is very different from the MFC window. Not all the knowledge about MFC you know is applicable to WTL.
  
On the other hand, WTL also has its own advantages:

1) You do not need to learn or master complex document/view frameworks.
2) It has basic interface features of MFC, such as automatic update of DDX/DDV and command status (for example, Check mark and Enable mark of menu ).
3) enhanced some MFC features (such as easier-to-use window separation ).
4) it can generate a smaller executable file than the static link MFC program (the translator adds that all source code of WTL is statically linked to your program ).
5) you can fix the errors (bugs) in your own WTL without affecting other applications (in contrast, if you fix a BUG in the dynamic library of MFC/CRT, other applications may crash.

If you still need to use MFC, the windows of MFC and the windows of ATL/wtl can "coexist peacefully ". (For example, a prototype in my work uses the cframewnd of MFC and contains the csplitterwindow of wtl. In the csplitterwindow, The cdialogs of MFC is used. I didn't want to show off anything, only the MFC code is modified so that the wtl split window can be used, which is much better than the MFC split window ).

In this series of articles, I will first introduce the ATL window class. After all, wtl is a series of additional classes built on the ATL, so we need to have a good understanding of the ATL window class. After the introduction of ATL, I will introduce the features of wtl and show how it makes interface programming easy.

  Introduction

Wtl is a cool tool. Before you understand this, you must first introduce ATL. Wtl is a series of additional classes built on top of ATL. If you are a programmer who strictly uses MFC, you may not have the opportunity to access the interface class of ATL, therefore, we recommend that you use some other things before starting wtl. It is necessary to introduce ATL by detour.

In the first part of this article, I will provide some background knowledge about ATL, including some basic knowledge required to compile ATL code, quickly explain some confusing ATL template classes and basic ATL window classes.

  ATL background knowledge: the development history of ATL and wtl

"Active Template Library" is a strange name, isn't it? Older people may remember that it was originally called the "Network component template library", which may be more accurate, the purpose of ATL is to make it easier to compile component objects and ActiveX Controls (ATL was developed during the development of new product ActiveX-XX by Microsoft, which is now called XX.. net ). Because ATL exists to facilitate the compilation of component objects, only simple interface classes are provided, which is equivalent to the window classes (cwnd) and dialog classes (cdialog) of MFC ). Fortunately, these classes are flexible and can be used to build additional classes like wtl.

Wtl is now the second correction. The initial version is 3.1 and the current version is 7 (the reason why the wtl version is selected to match the ATL version is as follows, so there are no versions such as 1 and 2 ). Wtl 3.1 can be used with VC 6 and VC 7, but several pre-processing labels need to be defined in VC 7. Wtl 7 is backward compatible with wtl 3.1 and can be used with VC 7 without any modification. At present, there is no reason to use 3.1 for new development work.

  ATL-style Template

Even if you can read the template class code of C ++ effortlessly, there are two things that may make you dizzy. The following is an example of the definition of this class:

Class cmywnd: Public c0000wimpl <cmywnd>
{
...
};

This is legal because the C ++ syntax explains that even if the cmywnd class is only partially defined, the class name cmywnd has been included in the recursive inheritance list and can be used. Taking the class name as a parameter of the template class is because ATL is doing another mysterious thing, that is, the virtual function calling Mechanism during compilation.

If you want to know how it works, see the following example:

Template <class T>
Class B1
{
Public:
Void sayhi ()
{
T * PT = static_cast <t *> (this); // huh ?? I will explain it below
Pt-> printclassname ();
}
Protected:
Void printclassname () {cout <"this is B1 ";}
};

Class D1: Public B1 <d1>
{
// No overridden functions at all
};

Class D2: Public B1 <D2>
{
Protected:
Void printclassname () {cout <"this is D2 ";}
};

Main ()
{
D1 D1;
D2 D2;

D1.sayhi (); // prints "this is B1"
D2.sayhi (); // prints "this is D2"
}

This code static_cast <t *> (this) is the tip. It assigns this pointer to the B1 type to the D1 or D2 type according to the special processing during function calling, because the template code is generated during compilation, therefore, as long as the compiler generates a correct inheritance list, the assignment is safe. (If you write it as: Class D3: Public B1 <D2>, it will be troublesome.) The reason for security is that this object may only be a D1 or D2 (in some cases) type object, it's not something else. Note that this is similar to the C ++ polymorphism (polymorphism), but the sayhi () method is not a virtual function.

To explain how this works, first look at the call to each sayhi () function. In the first function call, the object B1 is assigned as D1, so the Code is interpreted:

Void B1 <d1 >:: sayhi ()
{
D1 * PT = static_cast <d1 *> (this );
Pt-> printclassname ();
}

Because D1 does not load printclassname (), check that the base class B1 and B1 have printclassname (). Therefore, the printclassname () of B1 is called.

Now we can see that the second function calls sayhi (). This time the object is assigned the D2 type, and sayhi () is interpreted:

Void B1 <D2 >:: sayhi ()
{
D2 * PT = static_cast <D2 *> (this );

Pt-> printclassname ();
}

This time, D2 contains the printclassname () method, so the printclassname () method of D2 is called.

The advantages of this technology are:

1) no pointer to the object is required.
2) saves memory because virtual function tables are not required.
3) because there is no virtual function table, it will not call the virtual function pointed to by a null pointer at runtime.
4) All function calls are determined during compilation (the translator adds: Dynamic concatenation used by the virtual function mechanism different from C ++), which is conducive to the code optimization of the Compilation Program.

Saving the virtual function table seems insignificant in this example (each virtual function has only 4 bytes), but imagine if there are 15 base classes, each class contains 20 methods, in addition, it is quite impressive.

 ATL window class

Now, I have explained a lot about the background of ATL. It's time to officially talk about ATL. The interface definition and implementation of ATL are strictly separated during design, which is the most obvious in the design of window classes. This is similar to com, the interface definition and implementation of COM are completely separated (or there may be multiple implementations ).

ATL has an interface specially designed for Windows, which can perform all window operations. This is cwindow. It is actually a packaging class for hwnd operations. It encapsulates almost all window APIs with the hwnd handle as the first parameter, such as setwindowtext () and destroywindow (). The cwindow class has a Public Member m_hwnd, allowing you to directly operate on the handle of the window. cwindow also has an operator hwnd. You can say that the cwindow object is passed to the function with the hwnd parameter, however, this is no equivalent to cwnd: getsafehwnd () (the translator's method of adding: MFC.

Cwindow is very different from the cwnd class of MFC. Creating a cwindow object consumes a small amount of resources, because there is only one data member and there is no object chain in the MFC window, MFC internally maintains this object chain, which maps hwnd to the cwnd object. Another difference with the cwnd class of MFC is that when a cwindow object is out of scope, its associated window is not destroyed, this means that you do not need to remember to separate the temporary cwindow object you created at any time.

In the ATL class, the implementation of the window process is csf-wimpl. Csf-wimpl contains all the window implementation code, such as registration of window classes, subclass of windows, message ing, and basic WindowProc () functions, it can be seen that this is very different from the design of MFC. MFC puts all the code in a CWnd class.

There are also two independent classes including the implementation of the dialog box, which are CDialogImpl and CAxDialogImpl, and CDialogImpl used to implement the normal dialog box while CAxDialogImpl implemented the dialog box containing ActiveX controls.

  Define the implementation of a window

Any non-dialog window is derived from csf-wimpl. Your new class must contain three things:

1. Definition of a window class
2. A message ing chain
3. The default window type used by the window is called window traits.
   
The definition of window classes is implemented through the DECLARE_WND_CLASS macro or DECLARE_WND_CLASS_EX macro. This macro defines a CWndClassInfo structure, which encapsulates the WNDCLASSEX structure. The DECLARE_WND_CLASS macro allows you to specify the Class Name of the window class. Other parameters use the default settings. The DECLARE_WND_CLASS_EX macro also allows you to specify the type and background color of the window class, you can also use NULL as the class name. ATL will automatically generate a class name for you.

Let's start to define a new class. In the subsequent sections, I will gradually complete the definition of this class.

Class cmywindow: Public c1_wimpl <cmywindow>
{
Public:
Declare_wnd_class (_ T ("My window class "))
};

Next is the message ing chain. The message ing chain of ATL is much simpler than that of MFC, and the message ing chain of ATL is expanded into a switch statement, the switch statement is the correct message handler and calls the corresponding function. The Macro that uses the message ing chain is BEGIN_MSG_MAP and END_MSG_MAP. Let's add an empty message ing chain for our window.

Class cmywindow: Public c1_wimpl <cmywindow>
{
Public:
Declare_wnd_class (_ T ("My window class "))
Begin_msg_map (cmywindow)
End_msg_map ()
};

In the next section, I will show you how to add a message to the Message ing chain. Finally, we need to define the features of the window for our window class. The features of the window are the Consortium of the window type and the extended window type, which is used to specify the window type when creating the window. The window type is specified as a parameter template, so the caller of the window does not need to worry about the correct type of the specified window. The following is an example of defining the window type with the ATL class CWinTraits:

Typedef cwintraits <ws_overlappedwindow | ws_clipchildren, ws_ex_appwindow> cmywindowtraits;

Class CMyWindow: public CWindowImpl <CMyWindow, CWindow, CMyWindowTraits>
{
Public:
DECLARE_WND_CLASS (_ T ("My Window Class "))

BEGIN_MSG_MAP (CMyWindow)
END_MSG_MAP ()
};

The caller can reload the type definition of CMyWindowTraits, but this is not necessary in general. ATL provides several pre-defined special types, one of which is CFrameWinTraits, a great framework window:

Typedef cwintraits <ws_overlappedwindow | ws_clipchildren | ws_clipsiblings, ws_ex_appwindow | ws_ex_1_wedge> cframewintraits;

  Enter message ing chain

The message ing chain of ATL is not very friendly to developers, and is also the most improved part of WTL. The Class Wizard at least allows you to add a message response. However, ATL does not have message-related macros and automatic expansion of parameters like MFC. In ATL, there are only three types of Message Processing: WM_NOTIFY, one is WM_COMMAND, and the third is other window messages. Let's start adding the corresponding functions of WM_CLOSE and WM_DESTROY to our window.

Class cmywindow: Public cwindowimpl <cmywindow, cwindow, cframewintraits>
{
Public:
Declare_wnd_class (_ T ("My window class "))

BEGIN_MSG_MAP (CMyWindow)
MESSAGE_HANDLER (WM_CLOSE, OnClose)
MESSAGE_HANDLER (WM_DESTROY, OnDestroy)
END_MSG_MAP ()

LRESULT OnClose (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled)
{
DestroyWindow ();
Return 0;
}

LRESULT OnDestroy (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled)
{
PostQuitMessage (0 );
Return 0;
}
};

You may notice that the message response function has the original WPARAM and LPARAM values. You need to expand them as the parameters required for the corresponding message. There is also the fourth parameter bHandled. this parameter is set to TRUE by ATL when the corresponding function call of the message is completed. If you need ATL to call the default WindowProc () to process the message after processing the message response, you can set bHandled to FALSE. Different from MFC, MFC displays the default message processing implemented by calling the response functions of the base class.

Let's also add a processing method for WM_COMMAND messages. Assume that our window has an About menu with ID IDC_ABOUT:

Class cmywindow: Public cwindowimpl <cmywindow, cwindow, cframewintraits>
{
Public:
Declare_wnd_class (_ T ("My window class "))
Begin_msg_map (cmywindow)
Message_handler (wm_close, onclose)
Message_handler (wm_destroy, ondestroy)
Command_id_handler (idc_about, onabout)
End_msg_map ()

Lresult onclose (uint umsg, wparam, lparam, bool & bhandled)
{
Destroywindow ();
Return 0;
}

Lresult ondestroy (uint umsg, wparam, lparam, bool & bhandled)
{
Postquitmessage (0 );
Return 0;
}

Lresult onabout (word wnotifycode, word WID, hwnd hwndctl, bool & bhandled)
{
MessageBox (_ T ("sample ATL window"), _ T ("about mywindow "));
Return 0;
}
};

Note that the COMMAND_HANDLER macro has expanded the parameters of the message. Likewise, the policy_handler macro expands the parameters of the WM_NOTIFY message.
Advanced Message ing link and embedding class

Another notable difference of ATL is that any C ++ class can respond to messages, while MFC only assigns the message response task to the CWnd class and c1_target class, plus several PreTranslateMessage () method class. This feature of ATL allows us to write the so-called "embedded class". To add a feature for our window, we only need to add this class to the inheritance list. This is so simple!

A basic class with a message ing chain is usually a template class. The class name of the derived class is used as a template parameter so that it can access members in the derived class, for example, m_hWnd (HWND member in the CWindow class ). Let's look at an example of an embedded class. This embedded class draws a window background by responding to the WM_ERASEBKGND message.

Template <class T, colorref t_crbrushcolor>
Class cpaintbkgnd: Public cmessagemap
{
Public:
Cpaintbkgnd () {m_hbrbkgnd = createsolidbrush (t_crbrushcolor );}
~ Cpaintbkgnd () {deleteobject (m_hbrbkgnd );}

Begin_msg_map (cpaintbkgnd)
Message_handler (wm_erasebkgnd, onerasebkgnd)
End_msg_map ()

Lresult onerasebkgnd (uint umsg, wparam, lparam, bool & bhandled)
{
T * PT = static_cast <t *> (this );
HDC Dc = (HDC) wparam;
Rect rcclient;

Pt-> getclientrect (& rcclient );
Fillrect (DC, & rcclient, m_hbrbkgnd );
Return 1; // we painted the background
}

Protected:
Hbrush m_hbrbkgnd;
};

Let's take a look at this new class. First, CPaintBkgnd has two template parameters: the name of the derived class using CPaintBkgnd and the color used to draw the window background. (The t _ prefix is usually used as the prefix of template parameters of the template class) CPaintBkgnd is also derived from CMessageMap, which is not necessary, because all classes that need to respond to messages only need to use the BEGIN_MSG_MAP macro, you may see examples of other embedded classes that are not derived from this base class. The constructor and destructor are both quite simple. They only create and destroy Windows Image brushes. The color is determined by the parameter t_crBrushColor. Next is the message ing chain, which responds to the WM_ERASEBKGND message. Finally, the response function OnEraseBkgnd () fills the background of the window with the painter created by the constructor. There are two things to note in OnEraseBkgnd (). One is that it uses a derived method of the window class (that is, GetClientRect (). How do we know that there is a GetClientRect () method in the derived class? If the derived class does not have this method, our code will not complain. The Compiler confirms that the derived class T is derived from CWindow. The other is that OnEraseBkgnd () does not expand the Message Parameter wParam into the device context (DC ). (WTL will eventually solve this problem and we will soon see it, I promise)

To use this embedded class in our window, you need to do two things: first, add it to the inheritance list:

Class cmywindow: Public cwindowimpl <cmywindow, cwindow, cframewintraits>,
Public cpaintbkgnd <cmywindow, RGB (255,)>

Next, cmywindow needs to pass the message to cpaintbkgnd, that is, link it to the Message ing chain, and add the chain_msg_map macro to the Message ing chain of cmywindow:

Class cmywindow: Public cwindowimpl <cmywindow, cwindow, cframewintraits>,
Public cpaintbkgnd <cmywindow, RGB (255,)>
{
...
Typedef cpaintbkgnd <cmywindow, RGB (255,)> cpaintbkgndbase;

Begin_msg_map (cmywindow)
Message_handler (wm_close, onclose)
Message_handler (wm_destroy, ondestroy)
Command_handler (idc_about, onabout)
Chain_msg_map (cpaintbkgndbase)
End_msg_map ()
...
};

Messages not processed by any cmywindow are passed to cpaintbkgnd. It should be noted that wm_close, wm_destroy and idc_about messages will not be transmitted, because once these messages are processed, the query of the message ing chain will be aborted. It is necessary to use typedef because the macro is a preprocessing macro and can only have one parameter. If we pass cpaintbkgnd <cmywindow, RGB (255,)> as the parameter, that ", "will make the pre-processor think that we use multiple parameters.

You can use multiple embedded classes in the inheritance list. Each embedded class uses a chain_msg_map macro, so that the message ing chain will pass the message to it. Different from MFC, The cwnd derived class in MFC can only have one base class, and MFC automatically transmits messages to the base class.

  Structure of the Atl program

So far, we have a complete landlord window class (even if it is not completely useful). Let's see how to use it in the program. An Atl program contains a global variable _ module of the ccommodule type, which is similar to the global variable theapp of the cwinapp type in the MFC program, the only difference is that the variable in ATL must be named as _ module.

The starting part of stdafx. H is as follows:

// Stdafx. h:
# Define strict
# Define vc_extralean

# Include <atlbase. h> // basic ATL class
Extern ccommodule _ module; // global _ Module
# Include <atlwin. h> // ATL window class

Atlbase. h already contains the most basic Window Programming header file, so we do not need to include windows. H, tchar. h and other header files. Declare the _ module variable in the CPP file:

// Main. cpp:
Ccommodule _ module;

The ccommodule contains the initialization and closure functions of the program, which must be displayed in winmain (). Let's start from here:

// Main. cpp:
Ccommodule _ module;

Int winapi winmain (hinstance hinst, hinstance hinstprev,
LPSTR szCmdLine, int nCmdShow)
{
_ Module. Init (NULL, hInst );
_ Module. Term ();
}

The first parameter of Init () is useful only for the COM Service Program. Because our EXE does not contain COM objects, we only need to pass null to Init. ATL does not provide its own winmain () and message pump similar to MFC, so we need to create a cmywindow object and add a message pump for our program to run.

// Main. cpp:
# Include "mywindow. H"
Ccommodule _ module;

Int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hInstPrev,
LPSTR szCmdLine, int nCmdShow)
{
_ Module. Init (NULL, hInst );

CMyWindow wndMain;
MSG msg;

// Create & show our main window
If (NULL = wndMain. Create (NULL, CWindow: rcDefault, _ T ("My First ATL Window ")))
{
// Bad news, window creation failed
Return 1;
}

WndMain. ShowWindow (nCmdShow );
WndMain. UpdateWindow ();

// Run the message loop
While (GetMessage (& msg, NULL, 0, 0)> 0)
{
TranslateMessage (& msg );
Dispatchmessage (& MSG );
}

_ Module. term ();
Return msg. wparam;
}

The only code above must be noted is cwindow: rcdefault, which is a member of cwindow (static data member) and the data type is rect. Similar to calling the createwindow () API, cw_usedefault is used to specify the width and height of the window. ATL uses rcdefault as the initial size of the window.

In the ATL code, ATL uses magic similar to assembly language to associate the handle of the main window with the corresponding cmywindow object, externally, the cwindow object can be passed between threads without any problem, but the cwnd of MFC cannot.

This is our window:

I have to admit that this is really nothing exciting. We will add an about menu and display a dialog box to add some fun to it.

Dialog Box in ATL

As we mentioned earlier, Atl has two dialog box classes, and our about dialog box uses cdialogimpl. Generate a new dialog box and generate a main window. There are only two differences:

1. The basic class of the window is cdialogimpl rather than c1_wimpl.
2. You need to define a public member named "IDD" to save the resource ID of the dialog box.

Now, define a new class for the about dialog box:

Class caboutdlg: Public cdialogimpl <caboutdlg>
{
Public:
Enum {IDD = idd_about };
Begin_msg_map (caboutdlg)
End_msg_map ()
};

ATL does not implement internal Response Processing for the "OK" and "cancel" buttons, so we need to add the code by ourselves. If you click the close button in the title bar, the wm_close response function will be called. We also need to process the wm_initdialog message, so that we can correctly set the keyboard focus when the dialog box appears. below is the complete class definition and message response function.

Class caboutdlg: Public cdialogimpl <caboutdlg>
{
Public:
Enum {IDD = idd_about };

Begin_msg_map (caboutdlg)
Message_handler (wm_initdialog, oninitdialog)
Message_handler (wm_close, onclose)
Command_id_handler (idok, onokcancel)
Command_id_handler (idcancel, onokcancel)
End_msg_map ()

Lresult oninitdialog (uint umsg, wparam, lparam, bool & bhandled)
{
Centerwindow ();
Return true; // Let the system set the focus
}

Lresult onclose (uint umsg, wparam, lparam, bool & bhandled)
{
Enddicel (idcancel );
Return 0;
}

Lresult onokcancel (word wnotifycode, word WID, hwnd hwndctl, bool & bhandled)
{
Enddialog (WID );
Return 0;
}
};

I use a message response function to process the wm_command message of the "OK" and "cancel" buttons at the same time, the WID parameter of the command response function indicates whether the message comes from the "OK" button or the "cancel" button.

The method for displaying the dialog box is similar to that for MFC. Create an instance of the new dialog box class and call the domodal () method. Now we return to the main window and add a menu with the about menu item to display our dialog box. We need to add two more message response functions, one is to respond to wm_create, the other is the idc_about command of the response menu.

Class cmywindow: Public cwindowimpl <cmywindow, cwindow, cframewintraits>,
Public cpaintbkgnd <cmywindow, RGB (255,)>
{
Public:
Begin_msg_map (cmywindow)
Message_handler (wm_create, oncreate)
Command_id_handler (idc_about, onabout)
//...
Chain_msg_map (cpaintbkgndbase)
End_msg_map ()

Lresult oncreate (uint umsg, wparam, lparam, bool & bhandled)
{
Hmenu = loadmenu (_ module. getresourceinstance (),
Makeintresource (idr_menu1 ));

Setmenu (hmenu );
Return 0;
}

Lresult onabout (word wnotifycode, word WID, hwnd hwndctl, bool & bhandled)
{
Caboutdlg DLG;

DLG. domodal ();
Return 0;
}
//...
};

The methods for specifying the parent window in the dialog box are somewhat different. MFC passes the pointer of the parent window to the dialog box through the constructor, while in ATL, the pointer of the parent window is used as domodal () the first parameter of the method is passed to the dialog box. If no parent window is specified like the code above, ATL will use the window obtained by getactivewindow () (that is, our main framework window) as the parent window of the dialog box.

The call to the loadmenu () method shows another method of ccommodule-getresourceinstance (), which returns your EXE hinstance instance, similar to the afxgetresourcehandle () method of MFC. (Of course, there is also ccommodule: getmoduleinstance (), which is equivalent to AfxGetInstanceHandle () of MFC ().)

This is the display effect of the Main Window and dialog box:

I will continue to talk about wtl, I promise!

I will continue with wtl, but in the second part. I think since these articles are intended for developers who use MFC, it is necessary to introduce some ATL before they invest in wtl. If you first came into contact with ATL, now you can try to write some small programs to process messages and use embedded classes. You can also try to use the Class Wizard to support message ing chains, enables automatic addition of message responses. Right-click the cmywindow item and click the "add windows message handler" menu item in the context menu.

In the second part, I will introduce the basic wtl window class and a better message ing macro.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.