Microsoft has always been occupied with the development of libraries that make programmers’ lives much easier inside their own operating system. The Microsoft Foundation Library is just a prime example of this. As with each library, this also needed to provide a reliable and easy way to handle the objects of the Windows OS. The solution found by the company was the creation of controls.
Contributed by Gabor Bernat Rating: / 2 February 24, 2009
This is the first part of a three-part article series that will appear here on the ASP Free website of the Developer Shed Network. Throughout this article, I will present the most representative controls inside MFC in general and how to use them. In the second part, the theme will be the Virtual List, one of the many smart tricks we can use to speed up our program that uses MFC controls.
In our final article, we will see how we can implement a selection on a list control with the report style, similar to a capability within the MS Excel suite. You don't need too much knowledge beforehand to get the most out of this series, just the basics of Object Oriented Programming (in C++); you should also be able to create an MFC application with the wizard. If you lack information on how to do this, I recommend that you read my Introduction to MFC articles.
Controls are all over the place in your calculator. The "OK" button, for instance, is a control. The edit box within which you type your name, or the text that appears inside a dialog, can also be a static control, not to mention radio buttons, check boxes, and more. If you managed to start up your Visual Studio, you've already seen countless examples of this:
There are a lot more controls, objects that are implemented inside the operating system directly. The list is long and full of useful items; just look at those currently available inside Visual Studio 2008 in the Toolbar section:
As you can see, there exist controls for all kinds of objects inside the Windows operating system. With the help of the resource editor, you can add them, assign variables to them, and create an object from them, as explained here.
Although you can create a dialog box with controls using just the resource editor in the background, this will do nothing other than generate the corresponding code that will be interpreted by the compiler when you build the application. In other words, you can do the same thing by writing only pure C++ code, but somehow this takes a little more time, and it is in addition much more troublesome to get it right.
Each control has a class. Everything is resolved within the class, from how to draw the control on the screen of your monitor to what messages will be sent when you push down a key, and what other effects that will have. For instance, when you are setting up some options inside a property page and push down the OK button, the dialog will be destroyed and your options saved/applied. Since every control is a window, they are all derived from the CWnd.
The code for the controls is already added to the operating system, so you don't have to provide a library that you need to carry around with the application so that it can work properly. The behavior described above is simply the default, but what if, by pushing down that button, we instead want to apply the settings and NOT destroy the dialog? This is the lesson for today.
Additionally, controls that you add to the dialogs will stay there for all time, and have just the basic functionality. The best example of this is requesting that an edit box be destroyed when we push down the ESC button. This behavior is not defined inside the CEdit Basic edit box controller. It is up to us to complete this task.
For this, we will use the inheritance traits of the C++ language and build our custom edit box upon the one that already exists within the MFC classes. There is a vast variety of virtual functions inside the CEdit class that will allow us to determine how our edit box behaves.
Our task for today is to build a dialog-based application that will have within it an edit box which is destroyed when we push down the "Cancel" Button, and create the edit box if it does not already exist when we push down the "OK" button. When the edit box is destroyed we will print the content of the box within a static control.
So start up your Visual Studio IDE, create a simple dialog-based application, and use the resource editor to modify it to look something like this:
You can keep the OK button, as we will remap its function; however, the Cancel is linked to the close button also (the little X on the right top center of the dialog), so if we remap its function we will have an application within which the close button won't work. When you push down the close X, it is exactly the same as if you had pressed the cancel button. Instead, create a new button and set it to be disabled, just as in the picture above.
Adding the static and edit box inside of it doesn't makes sense, as with the help of the resource editor we can add only the default type of controls (not the ones derived from it). Additionally, this will be dynamically present inside the dialog, so we have to resolve this on the coding level.
Now first we need to remap the OK and Destroy buttons, because for now, pushing the OK button will lead to the destruction of the window, while for the Destroy button, nothing happens. We simply need to overwrite the default task assigned to it. These are simple assignments; just right click on the button within the resource editor, and add a new event handler for both of them.
void CDialogEditTestDlg::OnBnClickedOk ()
{
// TODO: Add your control notification handler code here
OnOK ();
}
void CDialogEditTestDlg::OnBnClickedDestroy()
{
}
Now here you will find the OnOK() function. This is responsible for the destruction of the window, so delete it. We could, of course, also just overwrite the OnOK() virtual function in our application; however, I prefer to use this more general approach, because it shows how to proceed when you add new buttons in your dialog and want to assign some functionality to them.
Before we venture further, we need to design the base. Adding some new functionality to the CEdit will lead to the creation of a new class that cannot be added anymore via the resource editor. To get access to them we should declare a variable. Considering that this will be dynamically created/destroyed, we could go for a pointer, because via this we can allocate/delete the object.
The problem with this approach is that we need to consistently watch out for the validity of the pointers and make sure that they are valid. Things don't have to be like this, however. The key point to recognize is that the edit box is only for input and is not consistent. It is something like a box created in place that will automatically disappear once the data input is over.
I will implement the static text control in the usual way, but before I do this, let us create a custom control based on an existing one. The first task is to derive from the CEdit class. Here it is the class definition; we will discuss each of its functions immediately.
The derivation is public so we can use the newly created class just like a CEdit one. The first thing we need to do is ensure the creation of the class dynamically at run-time with the DECLARE_DYNCREATE macro. This allows the creation of the class to be dynamic, because the framework will create the object assigned to it as also dynamic. If you declare an object dynamic, you must also implement that. Add the IMPLEMENT_DYNAMIC inside the implementation file:
IMPLEMENT_DYNCREATE(gbEdit,CEdit)
Here we need to specify what to build from what. However, let us follow through with the rest of this. The private member is in fact just a flag that signals to us if we are done with the data introduction, in other words to tell us if we sent the end data signal or we just need to destroy the cell for whatever reason. This is by default false.
The smart data management that we construct relies on the loose focus of the edit box. As the text we enter is in fact typed inside an edit box created in place, once we finish this task we can destroy the box. As the box is created dynamically and the memory is allocated in the same way, we need to delete it once it becomes useless.
We will do this in the OnNcDestory() function. Within this, the box is properly destroyed. Therefore, all we need to do once we no longer will use the box is call the delete operator on the class in word. For now, this will be accessible through the "this" pointer and we will handle the issue as follows.
void gbEdit::OnNcDestroy()
{
CEdit::OnNcDestroy();
delete this;
}
After the item is destroyed, no one will use the object, so we can delete it with no fear of errors occurring or invalid pointer creation.
We are on a good path, but still far from the end. We resolved the object creation, so we will further implement the functionality for our custom, hand-coded edit box. Step 1 is that we want to return the text inside the text box once we push down the enter key. Here we need to take care of two parts. First we have to overwrite the default Windows handling of the character, and second, we need to return a message to the parent window once this occurs.
As the enter key is so specific, you won't get back anything during the OnChar message, so we need to overwrite the message translation process and assure that this message is going to be handled by the OnChar(WM_CHAR) message as well.
Additionally we will implement a Cancel like response for the F9 key, so we will make sure that this also is dispatched to the application. This is resolved by sending back the text once we push down the "Enter" or F9 key. Embedding the text in a text message is an elegant solution.
As to the message sent back to the parent of the control, we can decide later on if we want to have it do anything with that, or simply ignore it. You need to have a message ID defined under which you will send it back, and you can also return two pointers under the LPARAM and WPARAM arguments. If you do not wish to send data in either one, just add a 0 parameter here:
Of course, we give back the focus to the control's parent, with it at the same time destroying the object by the memory management system described above. With this, you are done, as we already learned how to create a custom/handwritten control that will act as you want when you want it to. In similar fashion you can customize the control as you wish; the only limit is your imagination.
Only one small task is left: to implement this in an application. We've described the lucky application earlier. After we declare a control, we call its Create member to create it properly on the screen. Furthermore, once we create it, not only will it appear as a proper control, but you can also call its members to customize its behavior.
This works just like disabling a button as follows in the example below. Remember that once you destroy the object in the application, the object on the screen will be also destroyed.
this->setButtons(false);
Finishing the thought about smart memory management, you can also declare a proper pointer inside the application as in the case of the CStatic* , but this will lead to a situation in which you need to make sure, every time you want to create a new one, that there is not one already.
If you want more depth on how this is implemented, and also how the application works in the end in real time, you can download the source package below. The solution was created in Visual Studio 2008. If you create a new solution, this should work under older versions as well.
Here are a couple of pictures that show how the end product turned out. If you do not have a compiler at hand, I have also provided the exe file in the package, so just feel free to test it and read through the source files.
I thank you for your patience reading my article and also invite you to comment with your thoughts related to it here on the blog, or just join our friendly ever-growing forum the Dev Hardware and express your thoughts in front of a dedicated community. The choices are all yours and also keep in mind that giving a rating for this article results in no shame at all. "Live with Passion!"