开发者

Data Structures to store complex option relationships in a GUI

I'm not generally a GUI programmer but as luck has it, I'm stuck building a GUI for a project. The language is java although my question is general.

My question is this:

I have a GUI with many enabled/disabled options, check boxes.

The relationship between what options are currently selected and what option are allowed to be selected is rather complex. It can't be modeled as a simple decision tree. That is options selected farther down the decision tree can impose restrictions on options further up the tree and the user should not be required to "work his way down" from top level options.

I've implemented this in a very poor way, it works but there are tons of places that roughly look like:

if (checkboxA.isEnabled() && checkboxB.isSelected()) 
{
   //enable/disable a bunch of checkboxs
   //select/unselect a bunch of checkboxs
}

This is far from ideal, the initial set of options specified was very simple, but as most projects seem to work out, additional options where added and the definition of what configuration of options allowed continually grew to the point that the code, while functional, is a mess and time didn't allow for fixing it properly till now.

I fully expect more options/changes in the next phase of the project and fully expect the change requests to be a fluid process. I want to rebuild this code to be more maintainable and most importantly, easy to change.

I could model the options in a many dimensional array, but i cringe at the ease of making changes and the nondescript nature of the array indexes.

Is 开发者_运维百科there a data structure that the GUI programmers out there would recommend for dealing with a situation like this? I assume this is a problem thats been solved elegantly before.

Thank you for your responses.


The important savings of code and sanity you're looking for here are declarative approach and DRY (Don't Repeat Yourself).

[Example for the following: let's say it's illegal to enable all 3 of checkboxes A, B and C together.]

Bryan Batchelder gave the first step of tidying it up: write a single rule for validity of each checkbox:

if(B.isSelected() && C.isSelected()) {
   A.forceOff();  // making methods up - disable & unselected
} else {
   A.enable();
}
// similar rules for B and C...

// similar code for other relationships...

and re-evaluate it anytime anything changes. This is better than scattering changes to A's state among many places (when B changes, when C changes).

But we still have duplication: the single conceptual rule for which combinations of A,B,C are legal was broken down into 3 rules for when you can allow free changes of A, B, and C. Ideally you'd write only this:

bool validate() {
    if(A.isSelected() && B.isSelected() && C.isSelected()) {
        return false;
    }
    // other relationships...
}

and have all checkbox enabling / forcing deduced from that automatically!

Can you do that from a single validate() rule? I think you can! You simulate possible changes - would validate() return true if A is on? off? If both are possible, leave A enabled; if only one state of A is possible, disable it and force its value; if none are possible - the current situation itself is illegal. Repeat the above simulation for A = other checkboxes...

Something inside me is itching to require here a simulation over all possible combinations of changes. Think of situations like "A should not disable B yet, because while illegal currently with C on, enabling B would force C off, and with that B is legal"... The problem is that down that road lies complete madness and unpredictable UI behaviour. I believe simulating only changes of one widget at a time relative to current state is the Right Thing to do, but I'm too lazy to prove it now. So take this approach with a grain of scepticism.


I should also say that all this sounds at best confusing for the user! Sevaral random ideas that might(?) lead you to more usable GUI designs (or at least mitigate the pain):

  • Use GUI structure where possible!
    • Group widgets that depend on a common condition.
    • Use radio buttons over checkboxes and dropdown selections where possible.
      Radio buttons can be disabled individually, which makes for better feedback.
    • Use radio buttons to flatten combinations: instead of checkboxes "A" and "B" that can't be on at once, offer "A"/"B"/"none" radio buttons.
  • List compatibility constraints in GUI / tooltips!
    • Auto-generate tooltips for disabled widgets, explaining which rule forced them?
      This one is actually easy to do.
  • Consider allowing contradictions but listing the violated rules in a status area, requiring the user to resolve before he can press OK.
  • Implement undo (& redo?), so that causing widgets to be disabled is non-destructive?
    • Remember the user-assigned state of checkboxes when you disable them, restore when they become enabled? [But beware of changing things without the user noticing!]


I've had to work on similar GUIs and ran into the same problem. I never did find a good data structure so I'll be watching other answers to this question with interest. It gets especially tricky when you are dealing with several different types of controls (combo boxes, list views, checkboxes, panels, etc.). What I did find helpful is to use descriptive names for your controls so that it's very clear when your looking at the code what each control does or is for. Also, organization of the code so that controls that affect each other are grouped together. When making udpates, don't just tack on some code at the bottom of the function that affects something else that's dealt with earlier in the function. Take the time to put the new code in the right spot.


Typically for really complex logic issues like this I would go with an inference based rules engine and simply state my rules and let it sort it out.

This is typically much better than trying to 1. code the maze of if then logic you have; and 2. modifying that maze later when business rules change.

One to check out for java is: JBoss Drools


I would think of it similarly to how validation rules typically work.

Create a rule (method/function/code block/lambda/whatever) that describes what criteria must be satisfied for a particular control to be enabled/disabled. Then when any change is made, you execute each method so that each control can respond to the changed state.


I agree, in part, with Bryan Batchelder's suggestion. My initial response was going to be something a long the lines of a rule system which is triggered every time a checkbox is altered.

  • Firstly, when a check box is checked, it validates (and allows or disallows the check) based on its own set of conditions. If allowed, it to propagate a change event.
  • Secondly, as a result of the event, every other checkbox now has to re-validate itself based on its own rules, considering that the global state has now changed.

On the assumption that each checkbox is going to execute an action based on the change in state (stay the same, toggle my checked status, toggle my enabled status), I think it'd be plausible to write an operation for each checkbox (how you associate them is up to you) which either has these values hardcoded or, and what I'd probably do, have them read in from an XML file.


To clear this up, what I ended up doing was a combination of provided options.

For my application the available open source rule engines were simply massive over kill and not worth the bloat given this application is doing real time signal processing. I did like the general idea.

I also didn't like the idea of having validation code in various places, I wanted a single location to make changes.

So what I did was build a simple rule/validation system.

The input is an array of strings, for me this is hard coded but you could read this from file if you wish.

Each string defines a valid configuration for all the check boxes.

On program launch I build a list of check box configurations, 1 per allowed configuration, that stores the selected state and enabled state for each checkbox. This way each change made to the UI just requires a look up of the proper configuration.

The configuration is calculated by comparing the current UI configuration to other allowed UI configurations to figure out which options should be allowed. The code is very much akin to calculating if a move is allowed in a board game.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜