RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Build an AJAX Content Management System with Visual WebGUI: Creating a Framework : Page 2

Flexible software systems require a foundation that supplies the basic system services that's easy for developers to use, yet powerful enough to support complex and flexible systems—in other words, a framework.


Using the Workplace and Module Classes

The LoadModule method takes care of loading a module into the workplace container and binding appropriate events to allow passing control between the calling and called modules:

public static IWTModule LoadModule(IWTWorkplace hostPanel, 
  IWTModule module, IWTModule parentModule, bool loadVisible){
  Control control = module as Control;
  if (control == null)
    module = null;
  else {
    Control hostBody = hostPanel.BodyPanel;
    // assign a unique name to the module we add, 
    // based on existing controls already loaded
    int currentControlCnt = hostBody.Controls.Count + 1;
    control.Name = hostBody.Name + "_ctrl" + currentControlCnt;
    control.Location = new System.Drawing.Point(1, 1);
    control.Dock = DockStyle.Fill;
    // if we don't have to show the control right now 
    // then just don't do anything
    if (!loadVisible)
      control.Visible = false;
      ShowModule(hostPanel, module);
    // set reference to caller module
    module.CallerModule = parentModule;
    // finally, set reference to workplace panel
    module.HostWorkplace = hostPanel;
    // bind close event to the controller, so it can get notified 
    // when a module needs to close, and perform required actions
    module.Close +=new WTCancelGenericEventHandler(Module_Close);
  return module;

The handler for the Close event removes the module from the workplace, and optionally passes control back to the calling module, along with arguments sent by the called module:

static void Module_Close(object sender, WTCancelGenericEventArgs e) {
  IWTModule module = sender as IWTModule;
  if (module != null)
    // ret reference to caller module and the workplace
    IWTModule parentModule = module.CallerModule;
    IWTWorkplace workplace = module.HostWorkplace;
    // and removes the module from workplace to unload it
    if (parentModule != null)
      // if I have reference to parent module
      // check if the module was not canceled, and if so
      // call Refresh method of the caller module passing 
      // it received arguments, so it can update itself
      if (!e.Cancel)
      // get reference to workplace the parent is loaded into
      workplace = parentModule.HostWorkplace
      // and activate the parent

Two interesting things in the preceding code are WTCancelGenericEventHandler and WTCancelGenericEventArgs. WTCancelGenericEventHandler is a delegate designed to work in conjunction with WTCancelGenericEventArgs, which implements a cancelable generic event-based mechanism that you can use to pass essentially any data as EventArgs between modules. You'll see more about these a little later.

With the interfaces and the WTWorkplace controller in place, you can create the actual components. Both the workplace and module inherit from UserControl, and they implement IWTWorkplace and IWTModule, respectively. Here's the code for the WTWorkplace component:

public partial class WTWorkplace : UserControl, IWTWorkplace {
   protected Control m_container;
   public WTWorkplace(){
      m_container = (Control)this;
   public string Caption {
      get { return this.Text; }
      set { 
         m_container.Text = value;
   public virtual Control BodyPanel {
      get {
         if (m_container == null)
            m_container = this;
         return m_container; 
   public IWTModule LoadModule(IWTModule module, 
      IWTModule parentModule) {
      return WTWorkplaceController.LoadModule(
         this, module, parentModule, true);
   public IWTModule LoadModule(IWTModule module, 
      IWTModule parentModule, bool loadVisible) {
      return WTWorkplaceController.LoadModule(
         this, module, parentModule, loadVisible); 
   public void ShowModule(IWTModule module) {
      WTWorkplaceController.ShowModule(this, module);
   public void RemoveModule(IWTModule module) {
      WTWorkplaceController.RemoveModule(this, module);
   public void HideAllModules() {
   public bool CheckModuleExists(IWTModule module) {
      return WTWorkplaceController.CheckModuleExists(this, module);

The code is simple and straightforward. All the methods are just wrappers around similar methods defined in the WTWorkplaceController class.

In the downloadable code for this article, you will also find the WTHeaderedWorkplace class.

Author's Note: Due to rapid Visual WebGUIdevelopment, the current version is 6.3.8a. The downloadable code was tested with that version. To build and run the sample solution from the file, you need to download and install Visual WebGUI Professional Edition, and register it as demo/trial. You can test the code with the Express edition, which is free, but to do that you would have to reorganize the code, because Visual Studio Express does not allow web projects, so the code must be organized as a web site.

The WTHeaderedWorkplace class is a workplace component implemented as a HeaderedPanel, with a simple implementation:

  • Create a new component named WTHeaderedWorkplace that inherits from WTWorkplace.
  • Add a HeaderedPanel to it and use the following code for its constructor:
public partial class WTHeaderedWorkplace : WTWorkplace {
    public WTHeaderedWorkplace() {
        headeredPanel.Dock = Gizmox.WebGUI.Forms.DockStyle.Fill;
        m_container = headeredPanel;

The code for WTModule derives from UserControl and implements IWTModule. Again, the members of IWTModule are very straightforward; they are simply getters/setters for two private fields that hold references to the workplace and parent modules:

    // …
    private IWTWorkplace m_hostPanel;
    private IWTModule m_callerModule;
    public IWTWorkplace HostWorkplace {
        get { return m_hostPanel; }
        set { m_hostPanel = value; }
    public IWTModule CallerModule {
        get { return m_callerModule; }
        set { m_callerModule = value; }

The Refresh method is implemented as a virtual method, so you must override it in the application's modules. It performs the task of updating the calling module, when the called module was closed with data to save:

public virtual void Refresh(WTCancelGenericEventArgs e){ }

WTModule contains also three important versions of the protected CloseModule method, each with a different signature:

// CloseModule should be called when the module needs to be closed
// it fires Close event, which is intercepted by WTWorkplaceController    
protected void CloseModule() {
protected void CloseModule(bool cancel) {
   CloseModule(new WTCancelGenericEventArgs(cancel));
protected void CloseModule(WTCancelGenericEventArgs e){
   if (Close != null)
      Close(this, e);

The module must call one of these methods when it needs to close. Call the no-argument version when the module is no longer relevant and needs to be closed. Call the second version, CloseModule(bool cancel), in the following situations:

  • When the module closes due to a Cancel button click, and pass true as the parameter
  • When the module is closing but no data needs to be saved; pass false as the parameter
  • When the module is closing with some data saved, but the calling module doesn't need any data

Finally, call the third method when the module is closing but some data needs to be passed to the calling module, as exemplified in the ModuleName and Dashboard modules in the downloadable sample application:

// in called module, in the event handler for Save button
private void buttonSave_Click(object sender, EventArgs e) {
    WTCancelGenericEventArgs evt = new WTCancelGenericEventArgs();
    evt.AddEventData("name", textName.Text);
// . . .
// in caller module, the code for Refresh method
public override void Refresh(WTCancelGenericEventArgs e){
    if (e.Cancel)
        statusBar1.Text = "Name was canceled";
    else {
        if (e.ExistsEventData("name"))
            statusBar1.Text = 
                string.Format("Welcome, {0}", 

That's all the required code. Now you need to register and set a startup form.

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date