Developing GUI (2) using C and DirectX
Welcome to the second part of "using C and DirectX Development GUI". Here is the first part. Edward we've the subject (explain how to use the GUI (graphical user interface) in my future game), this article will explain many of the form The mystery. We will pay attention to how the form tree work, use the GUI development plan, and create a detail of the form, including the drawing, message mechanism, coordinate system, and all other troubles. We will focus on this C . If you have already been dismisted with pure virtual functions, Dynamic_cast'ing, etc., then turn over the C book and continue.
Don't kid, let us start.
It is important to clarify our goal before involving code.
In our game has been completed, we will use a tree to track each form displayed on the screen. The form tree is a simple n-node tree. The root of the tree is Windows Desktop. Desktop window The Son of Desktop Window is usually the main form of the application; the subsidiary of the main form is a dialog, the sub-form of the dialog is a separate dialogue control (button, text box, etc.). Important The difference between-form does not depend on the position in the tree. For example, many games are placed directly on their desktop forms, just like dialog boxes. Yes, buttons are also forms. Consciousness It is very important to this. A button is just a form with an interesting appearance. In fact, all GUI controls have simple forms with different appearances. This reflects C 's ability. If we create a inherited window Physical categories, give it a few virtual functions, we can easily create our controls by overloading the base class. This application polymorphism is simply elegant; in fact, many C books use it as an example (in Part III I will detail this). This is our basic design, let us think about the application method.
plan
When I applied my GUI, I did the following steps:
1. First I write some basic form management code. These code is responsible for the form tree, add / delete the form, display / hide the form, move them to the top of the z coordinate (ie at the forefront), etc. In the case of a rectangle, I draw a rectangle in the form, then draw a number according to the Z coordinates of the form. If you purchase or write a model class of an excellent and reliable pointer array, then Your life will become very relaxed. STL (Standard Try Base STANDARD TEMPLATE LIBRARY) Gets a lot of support, it has a lot of good template pointer array, but if you want to use your own template class, It is now possible to conduct a complete test before your form management. The problem you have to pay attention to is a non-perceived memory leak or empty pointer reference caused by the wrong array class.
2. Once I have the foundation form management function, I spent some time thinking about my coordinate system. I wrote some coordinate management functions.
3. Next, I deal with the form drawing code. I inherit a "singular form" class, and show how it uses a set of nine elves to draw itself - four elves draw the corner, four drawings , A drawing background. Use the nine form wizards to make the created form of the Ala Stardock's Windowblinds. The foundation is that you need a quite intelligent Painting library, a library that can handle the wizard programs, elastic wizards, and concentrated wizards, and it is a very complex form generating program (some artists can be used to create their forms of code), which makes this The method can be realized. Of course, you should also pay attention to the form of form drawing.
4. Once the ordinary form drawing code, I started to implement the control section. Code control is simple, but it still requires a very thorough test. I started from simple control: static, icon, etc., which started as follows in the previous explanation. I work.
5. Finally, after completing my control section, I start writing a simple resource editor, a program that allows users to place the control, layout dialog box. This resource editor uses me for a whole month, but I Strongly suggesting that this is very easy to establish a text file) - The establishment of the graphical dialog is very easy, and this is also a good practice: in the perfect, I didn't find a few bugs in the code of my control section. It is proven to be difficult to solve in the actual procedure.
I was written in a resource (.rc) file that converts MSVC files for my GUI can use the resources of the resource file for my GUI. Finally, I found that such a program is much more troublesome than it. I wrote this gui The purpose is to get rid of Windows's restrictions, in order to do this, I want to use my own editor, use my own resource file format, do things in my own form. I decided to implement one by the underlying by the MFC. See the resource editor of Wysiwyg. My needs, I decided; your demand may be different. If someone wants to write a transformation, I will be happy to hear such a message. Where is it? The remaining part of the article will begin two steps. The third part of this series will enter the detail of numb control code. The fourth part will discuss the implementation and serialization form of a resource editor. Therefore .. Let's start the first step: basic form management code. Implement
We started. This is a good start for our basic form definition:
Class GUI_WINDOW {public: gui_window (); // boring ~ gui_window (); // boring virtual void init (void); // boring gui_window * getParent (void) {return (m_pparent);}
/// section i: Window Management Controls /
INT AddWindow (GUI_WINDOW * W); int RemoveWindow (GUI_WINDOW * W);
Void show (void) {m_bisshown = true;} void hide (void) {m_bisshown = false;} Bool isshown (void) {return (m_bisshown);} void bringtotop (void); Bool Isactive (Void);
/// section II: coordinates /
Void SetPOS (Coord X1, Coord Y1); // Boring Void Setsize (Coord Width, Coord Height); // Boring
Void ScreenToClient (Coord & X, Coord & Y);
INT VirtXtopixels (Coord Virtx); // Convert GUI Units To Actual Pixels Int Virtytopixels (Coord Virty); // DITTO
Virtual GUI_Window * FindchildATCoord (Coord X, Coord Y, INT FLAGS = 0);
/// section III: Drawing Code /
// Renders this Window All children recursively int dependeral (Coord X, Coord Y, INT DRAWME = 1);
Gui_wincolor & getCurrentColorset (Void) {RETURN (isactive ()? m_activecolors: m_inactivecolors;
/// Messaging Stuff To Be Discussed in Later Parts /
INT CALCALL (Void);
virtual int wm_paint (coord x, coord y); virtual int wm_rendermouse (coord x, coord y); virtual int wm_lbuttondown (coord x, coord y); virtual int wm_lbuttonup (coord x, coord y); virtual int wm_ldrag (coord x , COORD Y); Virtual Int WM_LClick (Coord X, Coord Y); Virtual Int WM_KeyDown (INT Key); Virtual Int Win, INT CMD, INT Param) {Return (0);}; Virtual INT WM_CANSIZE Coord X, Coord Y); Virtual Int WM_SIZE (COORD X, COORD Y, INT CANSIZE); Virtual Int WM_SIZECHANGED (VOID) {RETURN (0);} Virtual Int WM_UPDATE (INT MSDELTA) {Return (0);} protected:
Virtual Void Copy (GUI_WINDOW & R); // Deep Copies One Window To Another
GUI_WINDOW * m_pparent; uti_pointerarray m_subwins; uti_Rectangle M_Position;
// Active and INACTIVE ColorSets GUI_Wincolor M_ActiveColor; gui_wincolor m_inactivecolor;
// window capeni_string m_caption;
When you read the functions we discussed, you will find that it is visible everywhere. For example, our program will draw the entire GUI system by calling a source of the source form, this method will call its subsidy. The renderall () method, the rendrall () method of these subforms, also adjusts the renderall () method of their subsidiary. Most of these functions follow this recursive mode. The entire GUI system has a global Static variable - source form. For security considerations, I package it in a global function getDesktop (). Now, we start, let's complete some functions, start with the form management code, how?
Form management
/ ************************************************** ****************************************** AddWindow: Adds a window to this window's subwin array *************** *********************************************************** ************* / INT GUI_WINDOW :: AddWindow (GUI_WINDOW * W) {if (! w) return (-1); // only add it if it isn't alleady in Our window List. if (m_subwins.find (w) == -1) m_subwins.add (w); W-> setParent (this); return (0);
/ ************************************************** ************************* REMOVEWINDOW: Removes a window from this window's subwin array ************* *********************************************************** ************ / INT GUI_WINDOW :: RemoveWindow (GUI_WINDOW * W) {W-> setParent (null); return (m_subwins.findandremove (w));} / ***** *********************************************************** ******************** BRINGTOP: BRINGTOP: BRINGTOP: BRINGTOP: BRINGTOP: BRING THIS WINDOW TOTEP OF THE Z-Order. The top of thez-order is the highest index in the subwin array. *********************************************************** ************************************ / VOID GUI_WINDOW :: Bringtotop (void) {if (m_parent) {// We Gotta Save The Old Parent So WE KNOW WHO TO Add Back to GUI_WINDOW * P = M_Parent; P-> RemoveWindow (this); P-> Addwindow (this);}} / ***************** *********************************************************** *********
ISACTIVE: RETURNS TRUE IF this Window is The Active One (The One with INPUT FOCUS). ******************************************* **************************************************************** / BOOL GUI_WINDOW :: ISACTIVE (VOID) {if (! m_parent) Return (1); if (! m_parent-> isactive ()) Return (0); return (this == m_parent-> m_subwins.getat (m_parent-> m_subwins.getsize () -1));}
This series of functions is to handle what I said: New Form, Remove Forms, Display / Hide Forms, change their Z coordinate. All of these are complete array: here your array The class gets the test. The problem in the increasing / delete form function is: "Who is responsible for the form pointer?" In C , this is always a good question. AddWindow and RemoveWindow must Get the form of the form class. This means that this creates a new form you create a new pointer and pass the addWindow () to pistach to the Father (Desktop) Form. So, who is responsible for deleting your new pointer? ?
My answer is "GUI does not have a form pointer; the game itself is responsible for adding a pointer." This is consistent with the C clumsy rule "Who creates who deletes".
The viable method I have chosen is "The parent" is responsible for all of its child form pointer. "This means that in order to prevent memory leaks, each form must be in its (virtual) destructor (remember, there is The inheritance class is searched for its sub-form array and deletes all the forms included therein.
If you decide to implement a Gui with a pointer system, pay attention to an important principle - all forms must be dynamically assigned. The fastest way to crash is to transfer the address of a variable to the stack, such as call "AddWindow (& myWindow ", MyWindow is defined as partial variables in the stack. The system will work well until myWindow exceeds its effective area, or the destructor of its parent form is called, at which time the system will try to delete the address, so The system has collapsed. So "I will be particularly careful" to treat pointers ". This is why my GUI does not have the main reason for the form pointer. If you handle a lot of complex form pointers in your GUI (that is, said, For example, if you want to deal with the property sheet), you will more want to have such a system, it doesn't have to track each pointer ratio and delete means "this pointer is now controlled: I only remove it from your array but and Don't delete it. "This way you can guarantee that before the pointer exceeds the effective area, RemoveWindow (), you can also use (be careful) local variable address in the stack.
Continue? Display and hide the form through a Boolean variable. Shotow () and Hindewindow () are just simple settings or clears this variable: Form drawing and message handler check this "form before they handle any Visible "flag bit. Very simple!
The Z coordinate order is also quite simple. Not familiar with this statement, you can overlap the Z coordinate order than the form "stack". At first, you may imagine the DirectDraw handling override to implement the Z coordinate order, you may decide to each A integer is an integer to describe its absolute position in Z coordinate, that is, maybe 0 indicates the top of the screen, then -1000 represents the last. I think this Z coordinate order implementation method, but I don't agree - The absolute position of Z coordinate is not what I care; I am more concerned about their relative position. That is to say, I don't need to know accurately, I know that a form is in another, I just know this given window. The body is still in front of the other.
Therefore, I decided to implement the order of the Z coordinate, and the maximum index value in the array, M_Subwins, the form in "the most". Have the form of [size-1] followed, followed by [size- 2], the form is pushed. The form of [0] will be at the bottom. It is very easy to achieve this method Z coordinate order. Moreover, one fell in two, I will treat the first form as an active form, or More technical, it will be considered a form that has input focus. Although this "always the most" form used by my GUI is limited (for example, the task manager in Windows NT does not attend the input focus Before all the forms), I think this is conducive to making the code as simple as possible.
Of course, I use a number of columns to handle some small price in the Z coordinate order. For example, I have to move the second form to the forefront in 50 forms; I will move The second form moves 48 forms. However, the transfer is that the mobile form to Z coordinate is not the most time-consuming function, even, there are many good and fast ways to handle, such as linked lists.
Look at the tip of my in the bringtotop () function. Because I know that the form does not have a pointer, I will delete this form and create one immediately, and it is very efficient to locate it in the most. I do this because it is because My pointer, UTI_POINTERARRAY, has been written, once deleted an element, all of the higher elements will move backward.
This is the form management. Now, enter interesting coordinate system?
coordinate system
/ ************************************************** ************************* Virtual Coordinate System to Graphics Card Resolution Converters *************** *********************************************************** *********** / const double gui_scalex = 10000.0; const Double gui_scaley = 10000.0;
INT GUI_WINDOW :: VirtXtopixels (int width = (m_parent)? m_parent-> getpos (). getWidth (): getScreenDIMS (). getWidth (); Return ((int) ((Double) Virtx * (double) Width / gui_scalex);} int Gui_window :: Virtytopixels (int "{int Height = (m_parent)? m_parent-> getpos (). GetHeight (): getScreenDIMS () (): getHeight (); Return ((int) ((); Double) Virty * (double) height / gui_scaley));
/ ************************************************** *********************************** FindchildATcoord: Returns the top-most child window at coord (x, y); recursive. **** *********************************************************** ******************************** / GUI_WINDOW * GUI_WINDOW :: FindChildATCoord (Coord X, Coord Y, INT FLAGS) {for (int = m_subwins.getsize () -1; q> = 0; q-) {gui_window * ww = (gui_window *) m_subwins.getat (q); if (ww) {gui_window * Found = WW-> FindChildATCoORD (x-m_position.getx1 (), Y-m_position.gety1 (), flags); if (found) return (bound);}}
// Check to see at the coord - this breaks the recursion if (! getInvisible () && m_position.ispointin (x, y)) Return (this); return (null);}
My GUI's biggest advantage is an independent solution, I call it "Elastic Dialog". Basically, I hope that my form and dialog box determine their size according to the screen settings of their running system. High requirements are, I want forms, controls, etc. to expand or shrink on the screen of 640 x 480. At the same time, I also hope that no matter the size of their parent forms, they can be appropriate.
This means that I need to implement a virtual coordinate system like a Microsoft form. I define my virtual coordinate system with an arbitrary data - or say, "From now, I will assume each of the actual size of the form. One form is 10000 x 1000 units ", then my GUI will work in this set. For the desktop, the coordinates will correspond to the physical size of the display.
I realize my thoughts through the following four functions: VirtXtopixels (), Virtytopixels (), Pixelstovirtx (), and Pixelstovirty (). (Note: Two in the code; I estimate that you have understood this idea) These functions are responsible for converting virtual 10000 x 10000 unit coordinates to the real size of the parent form either either to convert the physical coordinates of the display. Obviously, the function of the display form will rely on them.
The function screenToClient () is responsible for obtaining the absolute position of the screen and converts it into a relative virtual coordinate. The relative coordinates begin from the upper left corner of the form, the idea of 3D space is the same. Relative coordinates are essential to dialogue All coordinates in the GUI system are relative to other things. One exception is a desktop form, and its coordinates are absolute. The relative method ensures that its sub-forms are also moved when the parent form moves, and can ensure that the structure is consistent when the user drags the dialog to different locations. At the same time, because our entire virtual coordinate system is relative, all controls in which the user stretches or narrows a dialog, the automatic as possible is suitable for new sizes. For those who have tried the same characteristics in Win32, this is an amazing character.
Finally, the function FindChildATCoord () acquisition (virtual) coordinate determines which (if any) child form is very useful, for example, when the mouse click, we need to know which form handles the mouse click event. This function. By reverse search subform array (remember, the first form is in the last side of the column), see which form of the point is in the rectangle of the sign parameter provides more conditions to determine if the click is occur; For example, when we begin to implement control, we will realize that it is useful to be useful not to make the indicator and cursor control, and the strip should give a chance to respond below them - if a log is placed on a button Above, even if the user clicks the label is still represented by the click button. The logo parameter controls these special cases.
Now, we already have coordinates, can we start to draw our form?
Draw form
Recursive is a double-edged sword. It makes the code to draw forms easy to track, but it will also cause repeated drawing pixels, which will seriously affect performance. (This means, for example, there is a form that stores 50 identical positions of the same size, the program will run 50 cycles, each pixel will be taken 50 times). This is a notorious problem. There must be a croping algorithm for this situation, in fact, this is an area I need to spend some time. In my own procedure - quaternion's gui is usually activated in the non-game screen process (column header and closing, etc.), it is stupid about the most accurate location for GUI, because there is no Other movements are in progress.
However, I am patching it. Now I am trying to use the DirectDrawClipper object in my drawing method. So far, the initial code looks very promising. Below is its work mode: desktop window "Clear" crop object. Each window then draws its sub-window, draw first, on the bottom of the bottom. When each window is drawn, add its screen rectangle to the cripple, effectively "exclude" in its window (this assumes all the windows 100% opaque). This is Help to ensure that each pixel will be only drawn once; of course, the program is still a mess, the calculation required by all GUI rendering, (and the crop may be full of load), but the minimum program will not Draw a redundant pixel. If the crop object is running quickly, whether it is worth it.
I am also trying to do other ideas - maybe the 3D graphics card buffer, or some complex rectangle setup. If you have some comments, please tell me; or try it and tell me Your discovery.
I cut off a lot of form drawing code, because these code is this for me (it calls my own elves). Once you know the exact screen dimension you want to draw form (Screen Dimensions When the actual drawing code can be used directly. Basically, my drawing code has been used for 9 elf - 4, 4 edges, 1 background - and uses these elf forms.
Color set needs a little explanation. I decided to have two unique color sets in each window; a set of uses when the window is activated, and a set is not activated. Before the draw code begins, call getAppropriateColorset (), this function according to the window The activation state returns the correct color set. The window with different colors of activation and inactive states is the basic rule of GUI design; it is also easy to use. Now our window is finished, start to see the message.
Window message
This section is the core of performing the GUI. The window message is sent to the window when the user performs a particular action (click on the mouse, mobile mouse, keys, etc.). Some messages (such as WM_KeyDown) are sent to the activation window, some (WM_MOUSEMOVE) is sent to the mouse movement There are also some (wm_update) to be sent to the desktop.
Microsoft's Windows has a message queue. My gui is not - when Calcall () calculates that it is required to send a message to the window, it stops and send messages - it calls the appropriate WM_XXXXXX () virtual function for the window. I found This approach is suitable for simple GUIs. Unless you have a good reason, don't use a too complicated message queue, store and use threads to get and send messages. For most game GUI, it not worth it.
In addition, pay attention to WM_XXXX () is a virtual function. This will make C polymorphisms to us. You need to change some forms of windows (or controls, such as buttons), handle the left mouse button just pressed the event? Very simple, a class is derived from the base class and overloads its WM_LBUTTONDOWN () method. The system will automatically call the derived class when appropriate; this reflects the power of C .
As far as my own will, I can't go deep into the details of Calcall (). This function gets all the input devices and issues a message. It makes a lot of things, and there are many behaviors that are specific to my GUI. For example, you may want to think Let your GUI run as X-Window, and the window within the mouse activity is always in the activation state window. Or, you want to make the activation window becomes a system modal window (refer to the user until the user Close it), just like many Apple Plate (Mac)-based programs. You want to click anywhere in any location in the window to close the window, not just in the title bar, like WinAmp, the results of the execution result according to the results of Calcall () You want the GUI to complete what kind of functionality will have a lot.
I will give you a prompt, although the -calcall () function is not not status, in fact, your Calcall () function may become a very complex state machine. An example of this is dragging and drop Object. For the appropriate calculation of ordinary "mouse keys release" events and similar but completely different "users who are dragging the object just put down" events, Calcall () must have a status parameter. If you are limited The state machine has been maded, then reviewing reviews before you perform Calcall () will make you less pain.
The WM_XXXX () function included in the window head file is the minimum collection I feel that the information that the GUI wants to calculate and send. Your needs may be different, you don't have to stick to the message collection of Microsoft Windows; if you customize The news is very suitable for you, then you will do it yourself.
Window message
In the first part of the article I mentioned a function called capplication :: rendergui (), which is the main function of drawing our GUI after calculation:
Void Capplication :: Rendergui (void) {// Get Position and Button Status of Mouse Cursor // Calculate Mouse Cursor's Effects On Windows / Send Messages // Render All Windows // Render Mouse // Flip to Screen}
Finally, let's start adding some PDLs (page description languages).
Void Capplication :: rendergui (void) {// Get Position and button status of mouse cursor m_mouse.refresh ();
// Calculate Mouse Cursor's Effects on Windows / Send Messages getDesktop () -> Calcall (); // render all windows getdesktop () -> renderall ();
// render mouse m_mouse.render ();
// Flip to screen getBackBuffer () -> flip ();
Viewings will make you see how the program starts together.
In the next chapter, in the third part, we will handle dialog controls. Buttons, text boxes, and progress bars.