views:

441

answers:

4

My question is simple: how bad is the following snippet of code? How would you do it?

CancelEventHandler _windowClosing;
private CancelEventHandler WindowClosing 
{
  set
  {
    clearEventHandlerList();
    this.Closing += value;
    _windowClosing = value;


    /*
     * if calling the method with null parameters,
     * it will set up itself as the primary control on the Window
     */
    _windowClosing(null,null);
  }
  get
  {
    return _windowClosing;
  }
}

private readonly CancelEventHandler[] CONTROLS = null;
private int current = 0;

public InitializerForm()
{
  InitializeComponent();

  /*
   * these are the handlers for the different controls,
   * in the order of appereance to the user
   */
  STATES = new CancelEventHandler[] { handler1, handler2, etc. };

  WindowClosing = CONTROLS[0];
}

private void clearEventHandlerList()
{
  foreach (CancelEventHandler c in CONTROLS)
  {
    this.Closing -= c;
  }
}

private void handler1(object obj, CancelEventArgs e)
{
  if (obj == null)
  {
    //hide every other control, but this one, also set up if necessary
  }
  else
  {
    //do something

    WindowClosing = CONTROLS[++current]; // set the next control to show

    e.Cancel = true;
  }
}

The point would be that the code wouldn't close a form, but instead show another component on it, and the set the way to handle that (this is mobile platform, so clicking OK button on the top generates a closing event). This is because showing several forms (4 or 5) one after another to the user makes the app blink, and also very annoying, while replacing just components is much smoother. This model works, but seems very nasty, and I would like a cleaner way to handle this.

Update: I updated the code sample so that variable names are somewhat speaky. Still, I'm convinced this is awful, (a) but not sure how much, and more importantly, (b) how to do it better.

Update 2: So, it seems that the code is still a bit mysterious.

Now here's what the problem is: I show the user a form, which instructs him what to do in several languages. He proceeds by clicking OK on the window. Next, I ask for his language, and then a few questions (where his/her GPS is, etc.) like this. After he could answer the questions (this shouldn't take more than a few seconds each), I show him a splash screen (I load stuff in a separate thread meanwhile), which has a picture. Showing these forms one after another makes the whole application start slow, and filled with UI lags.

Here's what I do to work around the lags: I put the content of the windows into panels, and put those panels one on another, and hide every one of them but the one that should be visible to the user. (current variable) Each of the windows does different things, so I need to change handler of the window closing event in addition. In this code the part which enables the panel is in the same function (handler1, handler2, etc.) with the part which handles the window closing event. If the arguments are null, it does the former, if it isn't (that means it was triggered by the user) it does the latter.

I need an extensible solution to this so that I can insert and remove dialogs anytime I want (the order and the pointers to the functions are stored in the CONTROLS field, and this seems to be very convenient, if you actually understand it. Although it is never easy to change the entire content of a form, there ought to be a simpler way to do this, as well a nicer one, that is what I'm looking for.

I hope this time I could explain how the model works.

+1  A: 

somehow your code makes me want to cry, i´m sorry. i read it twice and all i know about it is that it "doesStuff" with "STATES".

if you really want some help on this one you will have to work on it yourself first...

LDomagala
+2  A: 

I think it might be theoretically possible to make that code more delightfully diverting, perilously puckish, jovially jeopardous, cheerily chancy and unwarily whimsical but it would require some serious thought.

Daniel Earwicker
thanks, i thought i was the only one
LDomagala
For the person who flagged this 'offensive': http://stackoverflow.com/questions/135685/how-does-the-offensive-flag-work-in-stackoverflow - and note that the original question is titled "How bad is the following..." so there is an element of humour here that you may have missed.
Daniel Earwicker
Well, the choice of words might make your answer actually offensive. However, it's unfortunately also true.
Konrad Rudolph
Okay I chose some new words.
Daniel Earwicker
A: 

Use, XML! It's human-readable!

More seriously-

It seems like you're trying to create some sort of configuration wizard, so I'd start by researching that. Regarding your particular solution, I generally advocate very strongly against the "layered panel" approach. I do so because I maintain apps written by people who found this approach, or the related "hidden tabs on a tab control" approach, to be a good idea. It's not, and maintainers will curse your name for years to come.

That being said, what alternatives are there? Well, one alternative is what you've already dismissed because of its "flicker". I'd say that, in general, the flicker isn't that big of a deal for a quick and dirty application. It might be a good idea to make sure that your new window is called up before closing the old one, for example. (I'm assuming this is possible, I haven't developed on a mobile device.)

Another possibility might be a less-evil version of your layered panels. Instead of throwing a half-dozen panels into one form, create a separate user control for each wizard page and then add/remove the user controls to a containing form. This can avoid your flicker and will prove to be much easier to maintain because each page is in a different control. This might also ease any subsequent "Back" button functionality and make your data structures more naturally defined because those user controls will be associated with a specific logical bit of data. It's still not ideal, but it's probably good enough for a one-off solution.

A third technique, if you foresee extensive wizard modification as the product matures, might be to generalize the creation of your user controls by defining them in a more logical/declarative manner (e.g. via XML). If you dynamically generate sensible controls based on XML, then modifying the panels might be as easy as diving into your XML and doing something like:

<Questions>
    <Question type="Text"> <!-- generate a textbox for the answer field -->
        Favorite Color:
    </Question>
    <Question type="Number" range="0-255"> <!-- Maybe this is a spinner -->
        The answer to life, the universe, and everything:
    </Question>
</Questions>

That's just off the top of my head, and completely overkill for any one-off application, but it's a possibility.

Greg D
A: 

Now, let me caveat this by saying this might work, but it may not be the answer to your real problem - that of a slow and unresponsive UI when you have a lot of forms. The real answer may be to just go ahead and do all separate forms, but have each form load its child forms in a background thread while the user is staring at the first form.

But assuming you're still set on this, I'd start off by making a separate class just to handle the Panel stacking/hierarchy. Call it PanelManager. You would instantiate the PanelManager and associate it with the main form, then add Panels to it (perhaps keyed to a String) and set the order. In the main form, have the closing handler call PanelManager.CloseCurrentPanel() and if there are no more Panels to show then it's time to close the main form.

Time for pseudo-code! Here's a quick idea for the class, i'll leave it to you to implement it:

public class PanelManager {
// constructor
public PanelManager (Form ownerForm);

// short-cut properties
public Panel this[int idx]
{ get; set; }

public int Index
{ get; set; }

// main functionality

public int AddPanel (Panel p);
public void SetPanelOrder (Panel p, int idx);
public void RemovePanel (Panel p);
public void RemovePanelAt (int idx);

// shows the first Panel
public void Show ();

// shows Panel[idx]
public void Show (int idx);

// adds the panel to the top of the stack and displays it
// returns the index of the panel
public int AddPanelAndShow (Panel p);

// hides the current panel, displays the one underneath it
// returns false if there are no more panels
public bool HideCurrentPanel ();
}

in the constructor for the main form, instantiate it by new PanelManager (this), then in the closing event handler, call panelManager.HideCurrentPanel () and then figure out whether or not you need to close it after that.

ZaijiaN
after Greg D suggested, I implemented a model like yours but with better extensibility (like, load and unload callbacks for a panel, where it can set properties of the panel manager, etc.), and this card layout structure seems to work fine. Thanks for you help, anyway!
rhapsodhy