Introduction
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 is buried deeply
and was very hard to see.
Suppose you have an 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 has to
follow:
- 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 Manager, who definitely likes 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.
Approach
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.
- Some Context classes can be very useful to define a snapshot state of your
Data Model. Further more 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 can not swith on the light, and you can not 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 swith. 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 can not 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 condition are well
met repeatedly for every other control, when you can simply check one data
member (Is it still dark?)! Dose 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 problem to
understand MFC then you may need to read some MSDN, but I believe it's quite
straight forward 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 presentaion codes. To me, those dialog members are all presentaion 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 my
previous doc about it if you like it.