Recently, I come across a very fat C++ client application. It includes very rich GUI elements for Settings and Sending protocol message. But it had a serious design issue that all its UI behavior logic was coded inside Windows message handlers, sometimes even its data model was buried deeply and was very hard to see.
Suppose you have a dialog with about 30 controls, every move of 20 controls among them will change other control's behavior (Enable/Hide/Set default value/Change constraint...) If you don't model this complicated properly, soon it will become a logic hell because of unseen recursive function calls or logic contradiction. And that's the reality I am facing.
But in order to model it correctly, there are too many rules that have to be followed:
- Never mix data with your logic.
- Never call another message handler from handler.
- Keep a good trace of existing logic diagram, but where is the diagram!?
Some friend may say that you should never put so many controls in one window. I have to agree with that, but I believe we all have met tough customers or real tough Product Managers, who definitely like to 'Have everything in one screen'.
'Have everything in one Screen!', it sounds crazy! But it does happen in many requirements especially for Management Apps, Operator Consoles, etc.
So when there is absolutely no way around, we will have to model it someway.
There are a few leads before I start to build my solution:
- Controls' state are affected by data model, not by other control as it seems. They change only because the data current working upon is changed. That change can be caused by an selection on one combo box on your dialog, but that combo should not trigger you controls' change, but the DATA. That's the key of Data Driven.
ON_UPDATE_COMMAND_UI is a very good message model we can rely on. But we should not use
WM_KICKIDLE, which would waste your CPU and also cause unexpected behavior in your GUI.
UpdateDialogControls() is a very good way to broadcast the changes in GUI.
Context classes can be very useful to define a snapshot state of your Data Model. Furthermore, sophisticated checking can be added in
Context class in order to support such as additional calculation, logic modeling.
Context class should inherit from Data Model class, because they have the same structure.
Context class instance then can be reused by controls, if they have same dependency on Data Model. It's very common for example: When electricity is out, you cannot swith on the light, and you cannot watch TV either. Sounds funny? :-)
Why Use Data Driven Model?!
I believe it's a simulation of our natural world. Let's use the light example again here. let's presume you have a check box presenting a light switch. You uncheck it for off and check it for on, which will change the visibility of other staff in your room (dialog). You may notice that it's the nature of dark or lit affecting the visibility NOT the action of switch. Switch may be in malfunction, when no matter how you play with it, light is still off, and you still cannot see anything because of the dark!
OK, back to the code counterpart. If you coded the user behavior according to the check event of your switch. Then all UI logic will still function the same regardless the switch itself, even the previous status of your dialog (if it was lit or in dark?). Unless you like to check if all conditions are well met repeatedly for every other control, when you can simply check one data member (Is it still dark?)! Does it make sense?
It's simple but a common mistake we all made or are making. I also agree this is totally unnecessary to those simple minded. But if you are required to include 20 controls in one screen, I do think it can help.
What's in the Demo?
Based on those in mind, I wrote the demo app. After you went through the code, you will definitely know what I am talking about. If you have problems understanding MFC, then you may need to read some MSDN, but I believe it's quite straightforward for most developers.
For the sake of simplicity, I included a data object that only has one member variable. It is used along with other dialog members to determine the status of 'After controls'. In real life, you may have business logic objects which should be completely independent from presentation codes. To me, those dialog members are all presentation related besides the instance of data model.
By the Way
I used my
CCheckedGroupBox in this project to demostrate some situation to deal with custom built control. Please feel free to refer to my previous doc about it if you like it.
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.
I have over 13 Years IT industry experience as Principle/Senior Programmer. I am experienced in .NET/J2EE system design and detailed implementation. I master UML modelling and OO design methodology with a strong C#/C++/Java coding background. I have been in working/managing a distributed project using Agile/Waterfall approach. My business knowledge includes Telecommunication, Financial Investment/Trading, and Banking.