Use C ++ and DirectX to develop gui (2)

Source: Internet
Author: User
Tags call back

You are welcome to continue reading the second part of "using C ++ and DirectX to develop Gui. this is the first part. next we will discuss the theme (how to use the GUI in my future games). This article will explain many mysteries of the form. we will focus on how the form tree works, plan for us to use the GUI, and create details of the form class, including drawing, messaging mechanisms, coordinate systems, and all other troubles. here we will focus on C ++. if you are unfamiliar with pure virtual functions, such as dynamic_cast 'ING, please flip through C ++ and continue.

Let's get started.

Before coding, it is important to clarify our goals.

In our game's completed GUI, we will use a tree to track every form displayed on the screen. the form tree is a simple N-node tree. the root of the tree is Windows desktop ). a subform of a desktop window is usually the main form of an application, a subform of a main form is a dialog box, and a subform of a dialog box is an independent dialog control (buttons, text boxes, etc ). the important difference is that the appearance of a form does not depend on its position in the tree. for example, many games place buttons directly on their desktop forms, just like a dialog box. yes, the button is also a form. it is important to realize this. A button is just a form with an interesting appearance. in fact, all gui controls have simple forms with different appearances. this reflects the capabilities of C ++. if we create an inherited form class and give it several virtual functions, we can easily create our controls by reloading the base class functions. in fact, many c ++ books use polymorphism as an example (I will detail this in part 3 ). this is our basic design. Let's think about the application method below.


When I applied my gui, I took the following steps:

1. first, I wrote some basic form Management Code. these codes are responsible for the form tree, adding/deleting forms, displaying/hiding forms, moving them to the top of zcoordinate (that is, display them at the beginning), and so on. I drew a rectangle at the position of the form, and then drew a number in the upper left corner according to the zcoordinate of the form. if you purchase or write a template class with excellent and reliable pointer arrays, your life will become very easy. STL (Standard Template Library) is supported by many c ++ versions. It has many good template-based pointer array classes, but if you want to use your own template class, perform a complete and thorough test before you apply it to your form management. now you need to pay attention to the memory leakage or NULL pointer reference caused by incorrect array classes.

2. Once I have basic form management functions, I spent some time thinking about my coordinate system and wrote some coordinate management functions.

3. next, I will process the code for drawing the form. I inherit a "singular form" class and show how it uses a set of nine sprite programs to draw its own-four sprite programs draw corners, four painted edges, and one painted background. using these nine forms sprite programs, it is possible to create a form with both a unique artistic appearance and a dynamic size change (Ala Stardock's windowblinds. the foundation for doing so is that you need to have a very intelligent drawing library, a library that can process and seal the genie program, the elastic genie program, and the centralized genie program, and it is a very complex form Generation Program (some artists can use it to create their form code), so that this method can be actually implemented. of course, you should also pay attention to the form drawing speed.

4. once the code for drawing a common form is complete, I start to implement the Control Section. code control is simple, but it still needs to be thoroughly tested. I started to repeat my work as I explained above with simple controls: static and icons.

5. finally, after completing my control, I began to write a simple resource editor, a program that allows users to visually place controls and layout the dialog box. this resource editor took me a whole month, but I strongly recommend that you do this (instead of using text files to determine the location)-it is very easy to create a graphical dialog box, this is also a good exercise: I did not find several bugs in the code of my control part during the improvement, and it is proved to be difficult to solve in the actual program.

I have compiled a resource that can be converted to msvc ++ (. RC) The idea of files is a resource file program that can be used by my GUI for a long time. finally, I found that such a program is far more valuable than it. the purpose of writing this GUI is to get rid of Windows Restrictions. To do this, I need to use my own resource file format in my own editor, do things in your own form. I decided to use MFC to implement a WYSIWYG resource editor at the underlying layer. I decided on my needs. Your needs may be different. if someone wants to write a converter, I will be happy to hear such a message. where is it now? The rest of this article will explore the first two steps. the third part of this series will go into the numbly Controlled Code details. the fourth part will discuss the implementation and serialization form of the resource editor. so... let's start Step 1: Basic Form Management Code.


Let's start. This is a good start defined for our basic form class:

Class gui_window
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 vertex xtopixels (coord pixel X); // convert GUI units to actual pixels
Int yytopixels (coord between Y); // Ditto

Virtual gui_window * findchildatcoord (coord X, coord y, int flags = 0 );

// Section III: Drawing code

// Renders this window + all children recursively
Int renderall (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 wm_command (gui_window * 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 );}


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 caption
Uti_string m_caption;

When you read the functions we discuss, you will find recursion everywhere. for example, our program will call the renderall () method of the source form to draw the entire GUI system. This method will call back the renderall () method of its child form, the renderall () method of these subforms also calls the renderall () method of their subforms, and so on. most functions follow this recursive mode. the entire GUI system has a global static variable-the source form. for security reasons, I encapsulate it in a global function getdesktop.
Now, let's start to complete some functions, starting 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 already 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 ));

/*************************************** *************************************
Bringtotop: Bring this window to the top of the Z-order. The top of
Z-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
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 ));

These functions are used to manage forms: Create and delete forms, display and hide forms, and change their zcoordinates. all these operations are complete Array Operations: Here Your array class is tested. the only question of interest in adding or deleting a form function is: "Who is responsible for the form pointer? "In C ++, this is always a good question. both addwindow and removewindow must obtain the pointer of the Form class. this means to create a new form. Your Code creates a pointer and transmits the pointer to the parent (desktop) form through addwindow. who will delete the pointer you created?

My answer is "the GUI does not have a form pointer; the game itself is responsible for adding Pointers". This is consistent with the c ++ clumsy rule "who creates and deletes.

The feasible method I select is "parent form is responsible for all its child form Pointers ". this means that in order to prevent memory leakage, each form must have an inheritance class in its (virtual) destructor (remember) search for its child form array and delete all the forms included in it.

If you decide to implement a GUI with a pointer system, pay attention to an important principle-all forms must be dynamically allocated. the fastest way to crash is to upload the address of a variable to the stack, such as calling "addwindow (& mywindow)", where mywindow is defined as a local variable in the stack. the system will work well until mywindow exceeds its effective zone, or the destructor of its parent form is called. At this time, the system will try to delete it to the address, and the system will crash. therefore, "Be very careful with pointers ".

This is the main reason why my gui does not have a form pointer. if you process a large number of complex form pointers in your gui (that is, for example, you want to process attribute tables), you will want a system like this, it does not need to trace every pointer ratio and deletion only means "This pointer is under my control now: only remove it from your array but not delete it ". in this way, as long as you can ensure removewindow () before the pointer exceeds the valid zone, you can also use (Be careful) the local variable address in the stack.

Continue? A boolean variable is used to display and hide a form. showwindow () and hindewindow () are just simple settings or clear this variable: the form Plotting Program and the message processing program check the "visible form" flag before they process any. very easy!

Zcoordinate sequence is also quite simple. if you are not familiar with this statement, you can use the zcoordinate sequence to overlap the form "stack. in the beginning, you may want to implement the zcoordinate sequence like the DirectDraw processing overwrite. You may decide to give each form an integer to describe its absolute position in the zcoordinate, that is, 0 may indicate the top of the screen, then-1000 indicates the last. I thought about the zcoordinate sequence implementation method, but I do not agree that zcoordinate is definitely not what I care about; I care more about their relative positions. that is to say, I do not need to know exactly whether a given form is behind another one or not.

Therefore, I decided to implement the zcoordinate sequence as follows: the largest index value in the array, m_subwins, and the form is in the "beginning ". the form with [size-1] is followed by [size-2], and so on. the form with the position of [0] will be at the bottom. it is very easy to implement the zcoordinate sequence using this method. in addition, I will regard the first form as an active form or a more technical statement. It will be considered as a form with the input focus. although the "always before" form that my gui uses is limited (for example, the task manager in Windows NT always has a focus before all forms regardless of the input focus ), I think this helps to make the code as simple as possible.

Of course, I used a series to indicate that the zcoordinate sequence paid a small price when I moved the form to the very beginning to process the series. for example, I want to move the second form to the very beginning in 50 forms, and I will move 48 forms to move the second form. however, it is believed that moving a form to zcoordinate is not the most time-consuming function. Even if it is, there are many good methods to process it quickly, such as a linked list.

Look at my tips in the bringtotop () function. because I know that the form does not have a pointer, I will delete this form and create another one immediately. It is very efficient to relocate it to the beginning of the series. I did this because my pointer class, uti_pointerarray, has been written. Once an element is deleted, all the higher elements will move backward.

This is the form management. Now, go to the 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: pair xtopixels (INT pair X)
Int width = (m_parent )? M_parent-> getpos (). getwidth (): getscreendims (). getwidth ();
Return (INT) (double) rows x * (double) width/gui_scalex ));

Int gui_window: virtytopixels (INT Parameter Y)
Int Height = (m_parent )? M_parent-> getpos (). getheight (): getscreendims (). getheight ();
Return (INT) (double) y * (double) height/gui_scaley ));

/*************************************** *************************************
Findchildatcoord: returns the top-most child window at coord (x, y );
**************************************** ************************************/
Gui_window * gui_window: findchildatcoord (coord X, coord y, int flags)
For (INT q = 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 (found );

// Check to see if this window itself is at the coord-this breaks the recursion
If (! Getinvisible () & m_position.ispointin (x, y ))
Return (this );
Return (null );

The biggest advantage of my GUI is its independent solution. I call it "elastic dialog box ". basically, I want my forms and dialog boxes to determine their size based on their screen settings. A higher requirement for the system is that I want forms and controls to expand or zoom out on a 640x480 screen. at the same time, I also hope they can fit regardless of the size of their parent form.

This means that I need to implement a virtual coordinate system like a Microsoft form. I define my virtual coordinate system with any data -- or, "from now on, no matter the actual size of the form, suppose every form is 10000x10000 units ", then my gui will work under this set of coordinates. for the desktop, coordinates correspond to the physical size of the display.

I use the following four functions to implement my ideas: xxtopixels (), yytopixels (), pixelstovirtx (), and pixelstovirty (). (Note: two items are listed in the Code. I guess you have understood this idea ). these functions are responsible for either converting the virtual 10000x10000 Unit coordinates to the real size of the parent form or to the physical coordinates of the display. obviously, functions that display forms will depend on them.

The screentoclient () function is used to obtain the absolute position of the screen and convert it to the relative virtual coordinates. the relative coordinates start from the upper left corner of the form, which is the same as that of 3D space. relative coordinates are required for the dialog box.

All coordinates in the GUI system are relative to other things. the only exception is the desktop form. Its coordinates are absolute. the relative method ensures that the child form is also moved when the parent form moves, and the structure of the child form is consistent when you drag the dialog box to different positions. at the same time, because our entire virtual coordinate system is relative, all the controls in a dialog box will change as you stretch or zoom out, and the new size will automatically fit as much as possible. this is an amazing feature for those of us who have tried the same features in Win32.

Finally, the findchildatcoord () function obtains (virtual) coordinates to determine which (if any) child form is useful in the current coordinate. For example, when you click the mouse, we need to know which form handles the mouse-click event. this function searches the subform array in reverse order (remember that the first form is at the end of the column) to see which form is located in the rectangle. the flag parameter provides more conditions to determine whether a click occurs. For example, when we start to implement control, we will realize that it is useful not to allow the flag and the cursor control to respond to the click, take and take it with an opportunity response to the form below them -- if a label is placed on a button, even if the user clicks the label, the button is clicked. flag parameters control these special cases.


Now that we have coordinates, can we start to draw our form?

Draw form

Recursion is a double-edged sword. It makes the code for drawing a form easy to trace, but it also causes repeated pixel painting, which seriously affects performance. (That is to say, for example, if you have 50 forms with the same size and location, the program will run 50 cycles and each pixel will be taken 50 times ). This is a notorious issue. There must be a cropping algorithm to address this situation. In fact, this is a field that I need to spend some time on. In my own program-quaternion's Gui, it is usually activated in non-game screen processes (Column Titles and closures, etc, it is silly to put the most accurate position in the GUI, because there is no other action at all.

However, I am repairing it. Now I try to use the directdrawclipper object in my painting method. So far, the initial code looks promising. The following is how it works: The desktop window "Clears" the cropping object. Next, draw the Child Window of each window, first draw the top, at the bottom of the painting. After each window is drawn, add its screen rectangle to the cutter, this area is effectively excluded from the window below it (assuming that all windows are 100% opaque ). this helps to ensure that at least each pixel will be drawn once. Of course, the program is still messy by the computing and calling required by all gui rendering, (and the cutter may be working at full load), but at least the program will not draw unnecessary pixels. whether the running speed of the cutter object makes it worthwhile or not.

I am also trying several other ideas-maybe using the built-in Z buffer of the 3D graphics card, or some complicated rectangle generator (dirty rectangle setup ). if you have any comments, please let me know; or try and tell me your findings.

I have cut off a lot of form drawing code, because this code is the case for me (it calls my custom sprite class ). once you know the exact screen dimensions of the form you want to draw, the actual drawing code can be directly used. Basically, I used nine genie-four corners, four edges, and one background-to draw forms with these genie.

The color set needs some explanation. I decided that each window had two unique color sets. One set was used when the window was activated and the other one was used when the window was not activated. call getappropriatecolorset () before the code is drawn. This function returns the correct color set based on the window activation status. windows with different colors for activation and non-activation states are the basic rules of GUI Design, and they are easy to use.

Now our window has been painted. Let's start to see the message.

Window message

This section is the core of GUI execution. A window message is an event sent to a window when you perform a specific operation (click the mouse, move the mouse, press the key, and so on. some messages (such as wm_keydown) are sent to the activation window, some (wm_mousemove) are sent to the window on which the mouse moves, and some (wm_update) are always sent to the desktop.

Microsoft has a message queue in windows. my GUI does not-When calcall () calculates the message to be sent to the window, it stops and sends the message-it calls the appropriate wm_xxxx () virtual function for the window. I found that this method is suitable for a simple GUI. unless you have a good reason, do not use a too complex message queue, where you store and use threads to get and send messages. most of the game guis are not worth it.

Note that wm_xxxx () is a virtual function. this will make the C ++ polymorphism serve us. you need to change some forms of Windows (or controls, such as buttons) to process the events in which the left mouse button has just been pressed? It is very simple to derive a class from the base class and reload its wm_lbuttondown () method. The system will automatically call the method of the derived class when appropriate; this reflects the power of C ++.

As far as I want, I cannot go too deep into the details of calcall (). This function gets all input devices and sends messages. it does a lot of things and has a lot of specific behavior for my gui. for example, if you want your GUI to run like X-window, windows within the range of mouse activity are always in the active state. or, you want to make the activation window a system modal window (meaning that nothing else can happen until the user closes it), just like many apple platform (MAC)-based programs. you will want to click any position in the window to close the window, instead of just in the title bar, like Winamp. the execution result of calcall () varies greatly depending on what features you want the GUI to accomplish.

I will give you a prompt, although the-calcall () function is not stateless, in fact, your calcall () function may become a very complex state machine ). an example of this is drag-and-drop objects. to properly calculate the differences between common "mouse button release" events and similar but completely different "the user has just put down the dragged object" events, calcall () there must be a status parameter. if you are unfamiliar with finite state machines, reviewing them before you execute calcall () will make you less of a headache.

The wm_xxxx () function included in the window header file represents the minimum set of information that is to be calculated and sent by a GUI. your needs may be different, and you do not have to stick to the Message Set of Microsoft Windows. If custom messages are suitable for you, you can create one by yourself.


Window message

In the first part of the article, I mentioned a function called capplication: rendergui (), which is the main function for drawing our GUI after computation:

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 to add some PDL (page description language ).

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 ();

Viewing the code will show you how the program starts to work together.

In the next chapter, the third part processes the control, button, text box, and progress bar of the dialog box.

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: 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.