ATL window (1)

zhaozj2021-02-17  55

ATL window, first part

Source program

-------------------------------------------------- ------------------------------ This article is written by Andrew Whitechapel and translated by Sun Kai.

introduction

The ATL window class is not difficult to learn, but it is very different from MFC. Many developers say I like rich MFC support, why do you want to learn the ATL window? Oh, MFC has a large size and high performance consumption, forced you to work with the application framework system - these days you suspect whether the document continues to be valid - based on document templates. With a great advantage of using the ATL window, of course, it is very easy to integrate COM support. If you want faster, lightweight windows and free choice your application system and continuous protocol flexibility, you may not choose ATL.

In this article, I will provide an ATL window introduction and a simple ATL framework / view application recipe guide - you will see the same front end functionality that actually makes it easier to implement image MFC. The learning curve of the ATL window is smoother compared to the MFC, because ATL is smaller.

Although ATL is originally designed to support COM, it also includes a series of classes for building windows. You can use these classes to build a COM object, such as ActiveX control, creating a Windows application, but you don't have to include COM. The most important ATL window class is listed in the table below:

CWindow is a small package for the WIN32 API for the operation window, which contains a window handle and a HWnd operator that converts the CWindow object to HWND. Therefore, you can pass a CWindow object to any function that requires a window handle parameter. CWINDOWIMPL You can create one window of superclass or subclasses based on a new window class with CWINDOWIMPL. CCONTAINDWINDOW A window that implements a message map that is sent to other classes, allowing you to create a message to a class in a class. CAXWindow allows you to implement a window that can contain ActiveX control, which has some functions that create control or bind an existing control. CDialogIMPL is used to implement a base class for a modal or non-modal dialog. CDIALOGIMPL provides a dialog process with a default message map that includes send messages to your derived class. But it does not support ActiveX control. CSIMpleDialog Rely on dialog resource ID implementation modal dialog. CSIMpleDialog has a predetermined message mapping that can handle IDOK and IDCANCEL commands. CAXDIALOGIMPL and CDIALOGIMPL are also used to implement a base class for a modal or non-modal dialog. Also provide a dialog process that contains the default message mapping of send messages to your derived class. And it supports ActiveX control. ATL Object Wizard supports a derived class that adds a CAXDialogIMPL to your project and generates related dialog resources. CWndClassInfo manages information about a new window class (WNDClass) - actually packaged WNDCLASSEX. Cwintraits and CWINTRAINTSOR encapsulates an ATL window object window feature (WS_ window style).

Message mapping

People don't want to spend a main reason for learning the ATL window class is that the ATL's message mapping mechanism is somewhat strange. Yes, it is different from the MFC, but when you see the MFC message mapping macro, do you understand it? In fact, the ATL message mapping mechanism is very easy to master. Allows you to handle window messages in the CwindowImpl derived class because the ATL class inherits from an abstract base class CMessageMap. CMessageMap declares a pure virtual function ProcessWindowMessage, which is implemented in your CWindowIMPL derived class via macro Begin_MSG_MAP and End_MSG_Map. In addition, the message processing of the MFC is similar, and the message processing function of the ATL is just a multi-Bool & variable (translator description: In the ATL source code, the ProcessWindowMessage function defines the variable bool bhandled = true, which means it). This is the variable indicates whether a message is processed, it defaults to true. A message processing function can set this variable to false to indicate that the message is not processed. In this case, the ATL will enter the message processing function in the message mapping. By setting this variable to false, you can first perform some operations to respond to messages, and then allow the default handling or additional processing functions to complete the message response. The three sets of messages mapped in the table below:

. Message handler of all messages. The commander of the WM_COMMAND message is handled by the WM_NOTIFY message to map a window message to a process function. Command_handler maps a WM_COMMAND message to a process function Command_id_handler based on the notification code and menu item, control or shortcut key, map a WM_COMMAND message to a menu item, control or shortcut, the process_code_handler, a WM_COMMAND message Map to a process function notify_handler based on the notification code to map a WM_NOTIFY message to a processing function notify_id_handler to map a WM_NOTIFY message to a WM_NOTIFY message to a WM_NOTIFY message to a notification based on the control ID. Code processing function

For example, if you have an ATL dialog class with a child-controlled form, you will have a message map like this:

BEGIN_MSG_MAP (CMyDialog) MESSAGE_HANDLER (WM_INITDIALOG, OnInitDialog) COMMAND_HANDLER (IDC_EDIT1, EN_CHANGE, OnChangeEdit1) COMMAND_ID_HANDLER (IDOK, OnOK) COMMAND_CODE_HANDLER (EN_ERRSPACE, OnErrEdits) NOTIFY_HANDLER (IDC_LIST1, NM_CLICK, OnClickList1) NOTIFY_ID_HANDLER (IDC_LIST2, OnSomethingList2) NOTIFY_CODE_HANDLER (NM_DBLCLK, OnDblClkLists) END_MSG_MAP ()

The MFC system allows two different messaging schemes to transmit Windows messages longitudinally to the base class, and transversely transmit command messages in the document / view class. The first scheme is not suitable for ATL because ATL has a hierarchy realized relatively loose type template. The second solution is not suitable because ATL does not have a strict use (system) like the MFC document / view system.

In order to process messages sent by different windows in a message map, ATL provides two methods: preparative message mapping and link message mapping. A parent window can also handle a child control sent to it, a message that is sent to it as a reflection message. Prepared message mapping

The prepared message mapping is mainly designed to be used by the Alt class CContainedWindow. This class sends all of its messages to other class messages. The constructor of CContainedWindow needs to obtain an address of the class containing the message mapped to be used, and the id (or the default message mapping value 0) of the precomive message map inside the message map.

For example, when you create a Windows-based ATL control, ATL Object Wizard generates a class for control, which contains a member of the ccontainedWindow type of representative. In fact, this contains window superclars, special Windows control, you have decided to let it be based on your ActiveX control:

class ATL_NO_VTABLE CMyButton: public CComObjectRootEx, public CComCoClass, public CComControl, //...{public: CContainedWindow m_ctlButton; CMyButton (): m_ctlButton (_T ( "Button"), this, 1) {}

BEGIN_MSG_MAP (CMyButton) MESSAGE_HANDLER (WM_CREATE, OnCreate) MESSAGE_HANDLER (WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP (CComControl) file: // link message maps ALT_MSG_MAP (1) file: // preparatory message map END_MSG_MAP () // ...

Note "Button" is a Wndclass style, not title. The THIS pointer of the inclusive class is used as the second parameter, and the value 1 is passed to the constructor of the ContainedWindow to identify the preparation message mapping.

If you want to handle the controlled WM_LBUTTONDOWN message, you need to update the message mapping as follows. In this way, this message is sent to the parent window to listen to the message map, and then sends a preparation part of the message mapping.

BEGIN_MSG_MAP (CMyButton) MESSAGE_HANDLER (WM_CREATE, OnCreate) MESSAGE_HANDLER (WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP (CComControl) ALT_MSG_MAP (1) MESSAGE_HANDLER (WM_LBUTTONDOWN, OnLButtonDown) file: // see here END_MSG_MAP ()

Thus, the prepared message mapping is a simple policy that allows you to merge message processing, in a single begin_msg_map / end_msg_map macroblock.

Link message mapping

The message mapped to the message map sends a message through a message mapping through other classes or objects. ATL offers several mapping link macros: chain_msg_map (thebaseclass) transmits a message to a base class default message mapping. CHAIN_MSG_MAP_ALT (THEBASECLASS, MAPID) Transfer message to a preparatory message mapping of a base class. CHAIN_MSG_MAP_MEMBER (Themember) Transfer Messages to the default message mapping of the specified data member (derived from CMessageMap). CHAIN_MSG_MAP_ALT_MEMBER (Themember, MAPID) Transfer Messages to the Preparation Message Map of the specified data member.

For example, when you create a Windows Control ATL-based control, ATL Object Wizard generates code is as follows: BEGIN_MSG_MAP (CMyButton) MESSAGE_HANDLER (WM_CREATE, OnCreate) MESSAGE_HANDLER (WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP (CComControl) file: // see here ALT_MSG_MAP (1) END_MSG_MAP ()

This description WM_CREATE and WM_SETFOCUS messages are processed by this class, but other messages are transmitted to base class ccomcontrol <>. Similarly, if the processor of WM_CREATE and WM_SETFOCUS sets the variable bhandled to false, these messages are still transmitted to the base class for further processing.

Transfer a message to a data member, you need to update the map as follows:

BEGIN_MSG_MAP (CMyButton) MESSAGE_HANDLER (WM_CREATE, OnCreate) MESSAGE_HANDLER (WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP (CComControl) ALT_MSG_MAP (1) CHAIN_MSG_MAP_MEMBER (m_ctlButton) file: // see here END_MSG_MAP ()

This assumes that m_ctlbutton is a member of the inclusive window, and is an instance of a ccontainedWindow derived class. It can get a message mapping for your message:

Class CMYBUTTONCONTROL: PUBLIC CCONTAINEDWINDOW {// ... begin_msg_map (cmybuttonControl) message_handler (wm_lbuttondown, onlinettondown) end_msg_map ()

Therefore, the link message mapping allows you to form a messaging chain from a map to another mapping - MFC also uses a similar message delivery scheme.

Reflection message

A parent window can handle the window message sent to it, while sub-control relies on a message called a reflected message - this is a logo on the original message. When the child controls receives these messages, it can identify that they are reflecting the self-contained window and handles them properly. For example, a sub-control wants to handle the WM_DRAWITEM message. In order to do this, reflect_notifications must be given in the message map of the parent window:

BEGIN_MSG_MAP (CMyDialog) MESSAGE_HANDLER (WM_INITDIALOG, OnInitDialog) COMMAND_ID_HANDLER (IDOK, OnOk) NOTIFY_HANDLER (IDC_EDIT1, EN_CHANGE, OnChangeEdit1) REFLECT_NOTIFICATIONS () // see here END_MSG_MAP ()

The REFLECT_NOTIFICATIONS macro is a call to CWindowImpl :: ReflectNotifications, which has this signal:

Template Lresult CwindowImplroot :: ReflectNotifications (Uint UMSG, WPARAM WPARAM, LPARAM LPARAM, BOOL & BHAND)

This function takes out the sub-control window handle, the WPARAM or LPARAM of the message sent, and then send a message in the following way:

:: SendMessage (HWndchild, OCM_BASE UMSG, WPARAM, LPARM); Subsequent Message_Handler Macro Processing Reflection Messages, the ID definition of the predefined reflection message is defined in OleCtrl.h:

Begin_msg_map (CMYCONTAINEDCONTROL) message_handler (OCM_DRAWITEM, ONDRAWITEM) DEFAULT_REFLECTION_HANDLER () end_msg_map ()

OCM_DRAWITEM, the symbols seen in this example, which are defined as follows:

#define OCM__BASE (WM_USER 0X1C00) #define OCM_COMMAND (OCM_ _Base WM_COMMAND) //...# Define OCM_DRAWITEM (OCM_ _BASE WM_DRAWITEM)

Default_reflection_handler Macro will restore the reflected message in the original message and passed to DefWindowProc.

Step 1: ATL WINDOW APP

This is a very simple exercise, design to demonstrate how easy it is to create a simple application using the ATL window class. I have been doing this very early: ATL COM AppWizard is provided to COM object service. If you want to create a non-COM application, there is a lot of code generated by the ATL COM AppWizard. Therefore, create an ATL application, you have two options:

Create an EXE server for ATL COM AppWizard and accept common selection (possibly some). Create a Win32 Application and add ATL support manually. Only in this way can you see what is required, we will deliberately eliminate the code generated by the wizard and complete our application's smallest lightweight framework with the second route.

Create a new Win32 Application, choose a simple option so we can get stdafx.h and stdafx.cpp ('AFX', of course, the residue of MFC, but the name is not related - what is the most important, PCH) .

ATL support

Modify stdafx.h Add to ATL needed header and an external reference to a global object of ccommodule type:

#include extern ccommodule _module; #include #include

Join an ATL object map in the main CPP file - it is empty, but we need it for CCommodule objects. Declare the global ccommodule object:

CCOMMODULE _MODULE;

Begin_Object_map (ObjectMap) end_Object_map ()

Join a IDL file with this project.

Library Somethingorother {};

window

Since there is no wizard support to create a new class derived from the ATL window class, we use the New Class dialog to join a common class and modify it manually. Right-click on the root node of ClassView and a new class. Choose class type: normal class, name CMYWINDOW, base class is CWindowImpl . You will not be satisfied with the ATL window class, because the wizard generates a new header file for your new class but no #include stdafx.h (which contains ATLWIN.H) and does not know these classes. I have added #include stdafx.h.

In your new window class, declare a message map and save the file: begin_msg_map (cmywindow) end_msg_map ()

In ClassView, right-click the handle of WM_DESTROY in your new window class. Join the postage of an exit message:

Lresult OnDestroy (uint UMSG, WPARAM WPARAM, LPARAM LPARAM, BOOL & BHAND) {PostquitMessage (0); Return 0;}

Similarly, we can get the WM_Paint handle and write the code that prints a string. You can see - ATL common methods - we use the API code directly. ATL has a class that is unpacking HDC, WTL implements it:

LResult OnPaint (Uint Umsg, WParam WParam, LParam Lparam, Bool & Bhand) {PaintStruct PS; HDC HDC = Getdc (); BeginPaint (& PS); Textout (HDC, 0, 0, _T ("Hello World"), 11); Endpaint (& PS); Return 0;

WinMain

At the beginning and end of the WinMain function, write common ccommodule :: init and term call:

_Module.init (NULL, HINSTANCE); / / ..._MODULE.TERM ();

Between INIT and TERM, declare an instance of your window class, and call the Create function to initialize it (don't forget to use the header file with #include to contain its header file). Then set a message loop:

CMyWindow wnd; wnd.Create (NULL, CWindow :: rcDefault, _T ( "Hello"), WS_OVERLAPPEDWINDOW | WS_VISIBLE); MSG msg; while (GetMessage (& msg, NULL, 0, 0)) {TranslateMessage (& msg); DispatchMessage ( & msg);

Ha, is it easy?

Step 2: ATL Framework / View Application

In this project we will create an example of the MFC SDI framework / view structure, but we use the ATL window class. The first version of this program looks similar to the previous example SimpleWin, but we will join the view, menus, and dialogs.

Create a new "Simple" Win32 Application. Between the encoding, STDAFX.H will be modified to join the header file required by ATL and the external reference of the global CCommodule object. Add ATL object maps in the primary CPP file and declare a global CCOMMODULE object. Also join a IDL file with the Library block frame.

Main frame window

Right click on the root node of ClassView and select New Class. Using class types: generic class, named CMAINFRAME, set the base class as CWindowImpl . Note that CFraMewintraits is Cwintraits that optimizes the primary frame window type (in atlwin.h) defined by TypeDef. In this new CMAINFRAME class, declaring WNDCLASS structure name and a message mapping: declare_wnd_class (_t ("myframe)) begin_msg_map (cmainframe) end_msg_map ()

We can get inheritively get a function onfinalMessage - one of a few virtual functions in ATL - it will be called when it receives a WM_NCDESTROY message. We need to overload this function to send an exit message:

Void onfinalMessage (hwnd / * hwnd * /) {:: postquitMessage (0);

Add some code to WinMain. Initialization / Termination Function of Common CCOMMODULE is added at its start and end:

_Module.init (NULL, HINSTANCE, NULL); _Module.Term ();

Use #include to include a header file of the main frame window, declare a instance of its instance between init'œTERM, or declare an additional instance of the framework, call CREATE to initialize it. Then a message loop:

CMAINFRAME MF; mf.create (getDesktopWindow (), CWindow :: rcdefault, _t ("my app"), 0, 0, 0); msg msg; while (GetMessage (& MSG, 0, 0) , 0) {TranslateMessage (& MSG); DispatchMessage (& MSG);

Compile and run.

View window

Now we will join a view class. Right click in ClassView to create another new class. Similarly, it also generates a common class. Named CVIEWIN, derived in CWindowImpl . Remember, there is no Cwintraits for view optimization with Typedef.

Use #include to include stdafx.h and declare WNDCLASS and message mapping. Then add a CViewWIN instance as a member of CMAINFRAME, handle WM_CREATE in the CMAINFRAME class to create a view:

LResult Oncreate (uint UMSG, WPARAM WPARAM, LPARAM LPARAM, BOOL & BHAND) {m_wndview.create (m_hwnd, cwindow :: rcdefault, _t ("myview"), 0, 0, 0); Return 0;}

We also have to process WM_SIZE in the framework to determine the size of the view. Compilation again:

Lresult Onsize (Uint Umsg, WPARAM WPARAM, LPARAM LPARAM, BOOL & BHAND) {Rect R; getClientRect (& r);

M_Wndview.SetWindowPos (NULL, & R, SWP_NOZORDER | SWP_NOACTIVATE); RETURN 0;} User interface

Now, we will process WM_LButtondown, WM_MOUSEMOVE, and WM_LBUTTONUP messages to provide very simple to let users use the doodle version of the mouse line. Yes, I know, but it provides a very easy way to learn UI response and message processing: First, add two Point type data members to the view class, in the constructor (-1, -1) To initialize them. We need to retain the trajectory of the start and end points of these lines - (-1, -1) Of course, the coordinate values ​​in the mouse message:

m_startpoint.x = m_startpoint.y = -1; m_endpoint.x = m_ENDPOINT.Y = -1;

Next, the handle of three mouse messages is obtained by clicking the right mouse button. Unlike the mouse message processing function of the CWnd class in the MFC, ATL does not declares that lparam is a cpoint object (nor Point), so you have to decompose the mouse coordinate from it. First, ONLBUTTONDOWN records the starting point of the code line:

Lresult OnlButtondown (UINT UMSG, WPARAM WPARAM, LPARAM LPARAM, BOOL & BHAND) {m_startpoint.x = loword (lparam); m_startpoint.y = HiWord (LPARAM); Return 0;}

In ONLBUTTONUP, reset the starting point to -1:

LResult Onlbuttonup (Uint Umsg, WPARAM WPARAM, LPARAM LPARAM, BOOL & BHAND) {m_startpoint.x = m_startpoint.y = -1; return 0;}

The onmousemove function is more working more. First, the end point of the drawing line is the resulting mouse coordinate value. Then get a DC and call MoveToex and Lineto to draw lines. Finally, the update start point is the current end point, so the next line can start from the current end point:

LResult OnMousemove (uint UMSG, WPARAM WPARAM, LPARAM LPARAM, BOOL & BHAND) {m_endpoint.x = loword (lparam); m_endpoint.y = HiWord (LPARAM); HDC HDC = getDC (); if (m_startpoint.x! = -1 ) {MoveToex (HDC, M_StartPoint.x, m_startpoint.y, null); lineto (HDC, M_ENDPOINT.X, M_ENDPOINT.Y); m_startpoint.x = m_endpoint.x; m_startpoint.y = m_endpoint.y;} return 0; }

Compile and run. You may ask why there is no news decryption in the ATL? Oh, of course, this is what WTL is going on ...

Step 3: ATL menu

Continue frame / view project. We will join a simple menu to choose the opportunity to choose a brush color: Continue this project. First, add a ColorRef type public member variable in the view class, named m_color. Initial into black in the constructor of the view class. Then, use it in the onmousemove function and use it to be used in the DC, and the operation is as follows. As usual, after use, select the original brush back to DC in order to release GDI resources:

HPEN HP = Createpen (PS_SOLID, 2, M_COLOR); HPEN OP = (HPEN) SelectObject (HDC, HP);

Insert a menu resource. Add Top Title: Color and Three Menu Items: Red, Green and Blue.

In WinMain, use #include to include the resource header file. And to create a menu resource, load the menu resource, and then call the handle of the menu as a parameter to call the CREATE function of the main frame window:

HMENU HMENU = LoadMenu (_Module.getResourceInstance (), makeintresource (idR_Menu1)); mf.create (getDesktopWindow (), CWindow :: rcdefault, _t ("my app"), 0, 0, (uint) hmenu);

In order to process the menu item in the main frame window We will join the command handler, so you need to use #include to include the resource header file, and then manually modify the message mapping to join three new entries:

BEGIN_MSG_MAP (CMainFrame) MESSAGE_HANDLER (WM_CREATE, OnCreate) MESSAGE_HANDLER (WM_SIZE, OnSize) COMMAND_ID_HANDLER (ID_COLOR_RED, OnColorRed) COMMAND_ID_HANDLER (ID_COLOR_GREEN, OnColorGreen) COMMAND_ID_HANDLER (ID_COLOR_BLUE, OnColorBlue) END_MSG_MAP ()

Set three processing functions as the following work, run it again:

LResult Oncolorred (Word WiND HWndCTL, Bool & Bhand) {m_wndview.m_color = RGB (255, 0, 0); Return 0;}

Step 4: ATL dialog

We will now join a simple dialog resource. A MFC user interface containing rich sub-control (CEDIT, CCOMBOBOX, etc.) is not available in ATL - although WTL is available. How hard is to create a dialog box with ATL? Hey, our dialog box will contain a combo box, we deliberately do not put in the resource editor into the string to the combo box - this can see how to work with the control in the dialog box program mechanism. Continue our project. Add a top-level menu title: View and a menu item: Dialog. We join a command processing function of a menu item in the message map of the main frame window:

Command_id_handler (id_view_dialog, onviewdialog)

Now we start our dialog: In the first edition, we use CSIMpleDialog directly. First, insert a new dialogue resource and place a simple static box above. Then change the menu item command to process the function to use this dialog. Compile and run: LResult OnViewDialog (Word Word Word, Bool & Bhand) {CSIMPLEDIALOG DLG; DLG.DOMODAL (); Return 0;}

If we want our dialog box with more complex behavior, we will derive from the CSIMpleDialog class. So, first go back to the resource editor to add a pull-down combo box to the dialog.

Create a new class named ClistDialog, derived from CSIMpleDialog . Don't forget to include STDAFX.H. Add messaging to this new class, add an entry to the mapping message map to the base class:

Begin_msg_map (clistdialog) CHAIN_MSG_MAP (CSIMPLEDIALOG) END_MSG_MAP ()

Next, we respond to the WM_INITDIALOG dialog box to add some strings into the combo box. First, remove the Sort style of the combo box. Then right click on the ClistDialog class and select Add Windows Message Handler. Changing the message filtering of the dialog. Add and edit the WM_INITDIALOG handler. The encoding is as follows - you will see that the key part of the combo box object is the same as the implementation in the MFC:

LRESULT OnInitDialog (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled) {CWindow combo (GetDlgItem (IDC_COMBO1)); combo.SendMessage (CB_ADDSTRING, 0, (LPARAM) "Red"); combo.SendMessage (CB_ADDSTRING, 0, ( LPARAM) "Green"); Combo.sendMessage (CB_ADDString, 0, (LPARAM) "Blue"); Return CsimpleDialog :: OnInitdialog (UMSG, WPARAM, LPARAM, BHANDLED);

Note: Make sure the chain_msg_map macro is the last one in the message map. Change the menu processing function in the main frame to use this new CLISTDIALOG class. Compile and run.

Yes, what should DDX / DDV? I heard what you said. Ok, let's write code for the IDOK button, return the string selected in the combo box. First add the appropriate macro in the message mapping:

Command_id_handler (IDOK, ONOK)

The onok processing function is encoded as follows. We save the text in the member variable m_text of the dialog class, a CCOMBSTR class:

LResult Onok (Word, Word WiD, Hwnd, Bool &) {CCOMBSTR Text; GetdlgiteMtext (IDC_COMBO1, M_TEXT.M_ST); :: EndDialog (m_hwnd, wid); return 0;}

Finally, modify the menu item processing function in the main frame, use the text from the dialog, then compile and run:

LRESULT OnViewDialog (WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL & bHandled) {// CSimpleDialog dlg; CListDialog dlg; if (IDOK == dlg.DoModal ()) {if (dlg.m_text == CComBSTR ( "Red")) M_WndView.m_Color = RGB (255, 0); Else IF (DLG.M_Text == CCOMBSTR ("Green")) M_WndView.m_Color = RGB (0,255,0); Else IF (dlg.m_text == ccombstr (" Blue ")) M_WndView.m_color = RGB (0, 0, 255);} return 0;}

If you extend this application to support toolbar and status bars, you can use ATL's CSTATUSBARCTRL and CTOOLBARCTRL classes - these classes are defined in AtlControls.h, although Microsoft does not formally support them. In the next article, I will talk about WTL - the same is the library that Microsoft has no formal support. You can smartly identify the differences between front-end support between ATL and WTL, and compare ATL / WTL and MFC.

转载请注明原文地址:https://www.9cbs.com/read-30049.html

New Post(0)