When talking about using VC ++ to develop Windows programs, we must mention MFC. MFC is enough to handle most of the situations, but frankly speaking, I don't like MFC because it is bloated and inefficient, so many times I directly use Win32 APIs to write my applications. Obviously, this is very difficult. You may already know the main difficulty, that is, writing interfaces. Now let's review how to use MFC to write the interface and create a button, which is roughly like this:
CButton btn;btn.Create(TEXT(“capital”), BS_PUSHBUTTON, &rect, pwndParent, BUTTON_ID);
You can also call the cbutton methods to perform operations on the button. If you think this button is not beautiful enough, you can set it to bs_ownerdraw, reload the member function drawitem, and manually draw it. If you still think it is not good enough, you can add processing functions for messages such as wm_mousemove. These operations are almost exactly the same as Win32 APIs, so we can say that MFC is a lightweight encapsulation of Win32 APIs. Now, my task is to write a super lightweight interface class library, which is similar to MFC and does not need to use a framework. I first call this imaginary class library flatcontrols, as its name implies, it is mainly used to repaint some existing controls and convert them into the flat style. To change the control's appearance, we immediately thought of the previously mentioned ownerdraw style. Many controls support this style. After setting this style, if the control needs to be drawn, A wm_drawitem message is sent to its parent window. The Message Parameter contains many useful information about the control to be drawn, including the control's window handle and DC (device context ), and some States of the control, which are used to guide the control to draw, is very necessary, such as a button, whether the status is pressed, enable/disable, there are active/non-active states, and so on. OK. Now that the function is clear, I started to work. The name of my first control class is cflatbutton (which is used as an example below, I arranged an ondrawitem () method for it to draw this button. But the question is, who will call the ondrawitem () method? Obviously, it is the owner of the control (parent window), but why does the parent window call this method when wm_drawitem is received? At this time, I don't want to worry about who the button's parent window is. If I have to call ondrawitem after receiving wm_drawitem, then I have to pay attention to this parent window. At least I need to add a message processing function in this parent window to something like this:
//Parent window’s procedure switch (message) { case WM_DRAWITEM: { //... //pCtrl is the pointer to the CFlatButton, you got it by some ways. pCtrl->OnDrawItem(wParam, lParam); } break; }
(Figure 1) This means that I need to change the parent window, which violates the original intention of designing this streamlined interface class library. By the way, we will tell you that MFC is doing this. If you try to separate the cbutton class from the MFC, you will not succeed, the control classes that support the owner draw of MFC are derived from cwnd. In the message map of cwnd, We can find on_wm_drawitem. Can we write the control manually without using the owner draw? At first, I thought it was okay. But when I did it, I found that this was not the case at all. Well, let's talk about it. As we all know, each window has its own message processing function, which is specified in registerclass. That is to say, it has been specified before createwindow, but it is specified, we still have a way to modify it. setclasslong and setwindowlong are methods. We can use one of these two functions to redirect window message processing to our function, for example:
class CFlatControl{ //...virtual LRESULT DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)=0;} class CFlatButton : public CFlatControl{public://... WNDPROC DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);//...protected: HWND m_hwnd; WNDPROC m_wpOld;} CFlatButton::Create(/* Arguments */){ m_hwnd = CreateWindow(/* Arguments */); m_wpOld = (WNDPROC)SetWindowLong(m_hwnd, GWL_WNDPROC, (LONG)DefWindowProc);}
This pseudo code indicates that when a flat button is created, its default processing function is defwindowproc, which is a member function of cflatbutton, in this way, we only need to fully implement defwindowproc to implement interface classes that are truly independent from any framework. Is that true? Maybe you will immediately say that the above Code is faulty. It won't be successful to change the member function of a class to long. Ha, it's really amazing, indeed, well, now let's modify this problem and replace this member function with a global function called defflatcontrolproc (hwnd, uint, wparam, lparam). Using the hwnd parameter, we can use a hash table, find the corresponding cflatcontrol object and call the defwindowproc method of this object. The Code is as follows:
HASHTABLE *g_phashHWNDtoCFlat; //Initialize this hashtable by a global function as AfxWinInit() class CFlatControl{ //...virtual LRESULT DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)=0;} class CFlatButton : public CFlatControl{public://... LRESULT DefWindowProc(HWND, UINT, WPARAM, LPARAM);//...protected: HWND m_hwnd; WNDPROC m_wpOld;} WNDPROC DefFlatControlProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ CFlatControl *p = (CFlatControl *) g_hashHWNDtoCFlat->Find((DWORD)hwnd); if(p!=NULL) return p-> DefWindowProc(hwnd, uMsg, wParam, lParam); return 0;} CFlatButton::Create(/* Arguments */){ m_hwnd = CreateWindow(/* Arguments */); m_wpOld = (WNDPROC)SetWindowLong(m_hwnd, GWL_WNDPROC, (LONG)DefFlatControlProc); g_hashHWNDtoCFlat->Add((DWORD)m_hwnd, (DWORD)this);} LRESULT CFlatButton::DefWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HWND hwndUnderPoint; POINT point; switch(uMsg) { WM_MOUSEMOVE: // Do what you want to do here. break; default: break; } return ::CallWindowProc(m_wpOld, hwnd, uMsg, wParam, lParam);}
(Figure 2) I finally knocked out the pseudo code. In fact, the real code is similar to this one. Does this change seem complicated? I have an additional initialization function (I did not write it in the pseudo code. In fact, MFC also has such a function. You can use the Wizard to create an MFC console program to see it ), A global hash table and a global window message processing function are added. In this way, is this streamlined interface class library still not simplified? It's not easy for me to write this. You just need to say "count" to your face ...... So, this is okay. The next step is to improve the cflatbutton: defwindowproc ...... Well, in theory, it is true that when I write this function, I will know what the difficulty is. There is no guidance on the states of the owner draw structure, it is very difficult to draw a button perfectly. I suggest you write it by yourself. A button is not just a box, but you should also consider the mouse press, move the mouse, move the mouse, and then enter ...... Add the button to check whether it is activated, whether it is valid, whether it is default, etc. After I wrote it for two days, it quickly crashed, So I declared that the task failed and became the title of this article. Now I finally understand why so many interface class libraries are extensions of MFC, rather than just opening a new stove. I also understand that it is impossible to create a completely independent micro-Interface Class Library (which can be used independently without any initialization), or there is a major lack of functions, for example, the owner draw cannot be used. I suddenly felt a bit regretful about the "impossible" that I just said. In fact, it is still possible, but the difficulty is not within the scope of my ability, that is, creating a set of controls on my own, you don't need to use the control that comes with windows, but I don't need to explain it. You also know how difficult it is ...... I shouted to him: next time! Next time! The sky replied: next time! Next time!