AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1419&pId=-1
Creating an Object Model for a Windows Application - Part 1
page
by Brian Mains
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 38911/ 72

Introduction

When creating a custom application, there are many design features to consider.  Will this application expose its API to the user?  Can developers create custom API's and what all should be made available?  How can someone tap in to the API to create their own custom module?  This article is meant to shed some light on the process, taking a look at what it takes to create the custom interface.

API Example

Let's use Visual Studio as an example.  To start, Visual Studio is made up of "regions" or areas within the application; it has a menu bar (or in .NET 2.0 Windows control terms the MenuStrip).  Below it is multiple toolbars (or ToolStrips) that contains any toolbars buttons, drop downs, etc., which can be turned on or off through a context menu (or ContextMenuStrip) so that only certain toolbars are visible when needed.  The Server Explorer, Toolbox, Solution Explorer, and Team Explorer windows are called tool windows or sidebars.  These windows can appear in the left, right or bottom sides of the window, and can either be fixed and resizable.  Windows can also be collapsible and pinnable, for space reasons.  There are several toolkits available, such as Divelement's SanDock.  That leaves us with the documents that are tabbed in the center, and the status bar on the bottom, all which completes the major user interface items in an application.

Countless vendors use the Office API to create a user interface or component for the variety of Office products available; the same capabilities are being leveraged in Visual Studio, as companies realize that they can embed their software directly into the tool instead of creating a new interface.  The real benefit is that rather than having to develop your own custom tool, you can learn how to create an add-on and use it within the application.  There is less development effort in this approach.

General UI Concept

The concept at the UI level is pretty simple; any change, either through clicking, moving, dragging, or some other movement, will generate a change to the object model.  When the object model changes, the UI responds to its events and the change is made.  This seems like overkill, but the benefits are unit testing, customization capabilities, and the rich API.  I will focus on the UI in another article, as it is a complicated subject.

Base Architecture

Because all of the elements within an API implement a similar interface, a common interface can be used.  This makes development of the interface a lot easier.

Listing 1

public abstract class UIElement
{
  private string _name = string.Empty;
  private string _title = "Untitled";
  private bool _visible = true;
 
  public string Name
  {
    get
    {
      return _name;
    }
    set
    {
      _name = value;
    }
  }
  public string Title
  {
    get
    {
      return _title;
    }
    set
    {
      _title = value;
    }
  }
  public bool Visible
  {
    get
    {
      return _visible;
    }
    set
    {
      _visible = value;
    }
  }
}

Each object can be visible or invisible, and can have a name/title.  These are the core features that can be incorporated.  In addition, the next level of abstraction is for a window.  A window is either a document or tool window that contains a user interface.

Listing 2

public abstract class BaseWindow: UIElement
{
  private object _uiInterface = null;
  public object UIInterface
  {
    get
    {
      return _uiInterface;
    }
    set
    {
      _uiInterface = value;
    }
  }
}

If in the future, any window-based object needed additional features could easily incorporate them in the base class.

Document Windows

An application works with documents in some way.  Though some content is not document-specific, the term document is used to mean any content that appears in the center of the window.  For instance, in Visual Studio, a document is code, help content, or URL content that appears in the tabbed area in the center.  Even though this content varies, a user interface represents it in a specific and customized way.

Each document has a common set of attributes associated with it.  Each object has a name, which is a unique key, and a title, which is the display text.  This can be easily abstracted into the architecture described above.  Document windows go beyond it because it requires a user interface.  This user interface could be a Windows forms user control, a WPF user control, a form, or some other object that can render a UI.

To start, think of the functionality that is involved with document windows.  Document windows are the main area content windows, which can be added, inserted in the middle of the collection, removed when clicked on the "X", selected by clicking on the tab, and moved within the collection.  The first three items are normal collection-based behavior.  The last two are specialized behavior, but aren't hard to do.

All windows use the BaseWindow object, since they have similar behavior.  You first saw this in the previous section.  All BaseWindow objects have a BaseWindowCollection, which inherits from UIElementCollection, which utilizes all of these features as shown below:

Listing 3

public abstract class UIElementCollection < T > : IEnumerable < T > where T:
  UIElement
{
  public virtual void Add(T item)
  {
    //Typical add
  }
 
  public bool Contains(string name)
  {
    //Typical contains
  }
 
  public int IndexOf(string name)
  {
    //Typical index of
  }
 
  public virtual void Insert(int index, T item)
  {
    //Typical insert
  }
}
 
public virtual bool Remove(T item)
{
   //Typical remove
}
 
public bool Remove(string name)
{
  if (string.IsNullOrEmpty(name))
    throw new ArgumentNullException("name");
 
  T item = this[name];
  if (item == null)
    return false;
  return this.Remove(item);
}
}

These are the base features of a collection class.  Windows need other features, which the BaseWindowCollection class adds to this, as shown below:

Listing 4

public abstract class BaseWindowCollection < T > : UIElementCollection < T >
  where T: BaseWindow
{
  public event DataEventHandler < T > SelectedIndexChanged;
 
  public T Selected
  {
    get
    {
      return _selected;
    }
    set
    {
      if (_selected != value)
      {
        _selected = value;
        this.OnSelectedIndexChanged(new DataEventArgs < T > (value));
      }
    }
  }
}

With windows, such as a document window, the window can be selected by clicking on the related tab.  This property represents that by storing a reference to the selected item, and upon changing the item, an event is fired for that selection.  Not only can this property be used, but the BaseWindow class has a select method.  This select method is shown below:

Listing 5

protected virtual void OnSelecting(EventArgs e)
{
  if (Selecting != null)
  Selecting(this, e);
}
 
public void Select()
{
  this.OnSelecting(EventArgs.Empty);
}

This event is caught by the collection class and upon doing so this value is assigned to the Selected collection property.

Tool Windows

Tool windows are a very similar approach; however, they are a little different.  Tool windows can be featured in different areas of the applications.  Tool windows can be in the left, right, and bottom sides of the screen.  This is handled through the tool window collection constructor, using an enumeration that specifies the location that the collection represents:

Listing 6

public ToolWindowCollection(ToolWindow.LocationType location)
{
  _location = location;
}

Because a tool window is in a specific region, the tool window has to be in an area defined by the location type.  Is it possible through this approach, to move the location?  For each property, there is a detection of that property change.  For instance, in the Title property of the UIElement class, is the following in the setter:

Listing 7

if (string.Compare(_title, valueCheck, StringComparison.CurrentCulture) != 0)
{
  string oldValue = _title;
  _title = value;
  this.OnPropertyChanged(new PropertyChangedEventArgs("Title", oldValue, value));
}

This statement broadcasts a property change whenever the value changes or the title changes.  This works for all the properties, and is similar to the property change notification for certain windows components.  This can also work to change the location of a toolbar (say from left to right, in response to dragging the window).

Menu Items

Menu items are more complicated, because they can be rendered recursively.  Take the File menu in some applications, like Visual Studio.  The file menu can have New and Recent menu items as children, which New can have its own children (the type of file to create), which theoretically could have their own children, and so a recursive amount of relationships can exist.

To facilitate that, each menu item has an Items collection, which it listens for child items being created, so it can bubble up a child item event.  For instance, below is the collection of items within the MenuItem object:

Listing 8

public MenuItemCollection Items
{
  get
  {
    if (_items == null)
    {
      _items = new MenuItemCollection();
      _items.ItemAdded += ChildItems_ItemAdded;
      _items.ItemAdding += ChildItems_ItemAdding;
      _items.ItemInserted += ChildItems_ItemInserted;
      _items.ItemInserting += ChildItems_ItemInserting;
      _items.ItemRemoved += ChildItems_ItemRemoved;
      _items.ItemRemoving += ChildItems_ItemRemoving;
    }
 
    return _items;
  }
}

This class listens for each event, and uses the On<EventName> method to bubble up the event.

Listing 9

void ChildItems_ItemAdded(object sender, DataEventArgs < MenuItem > e)
{
  this.OnChildItemAdded(e);
}
 
protected virtual void OnChildItemAdded(DataEventArgs < MenuItem > e)
{
  if (ChildItemAdded != null)
    ChildItemAdded(this, e);
}

The ChildItemAdded event is listened to by the parent, which will be discussed later.  The menu item adds one more item of importance, which is the Path property.  Because menu item is a hierarchy element, this property returns the name of each item in an object that is a string collection of the names.  Below is the implementation of this property, and the important methods.

Listing 10

public ValuePath Path
{
  get
  {
    ValuePath path = new ValuePath();
    path.AddValues(this.GetParentNames(this));
    return path;
  }
}
private string[]GetParentNames(MenuItem item)
{
  List < string > names = new List < string > ();
 
  if (item.Parent != null)
  {
    names.AddRange(this.GetParentNames(item.Parent));
    names.Add(item.Name);
    return names.ToArray();
  }
  else
  return new string[]
  {
    item.Name
  };
}

You can see that recursion comes into play with the GetParentNames method.  It has to return a single element as a string, so I can leverage using the same method in a recursive manner.

Toolbars

Toolbars are similar to menu items, yet work in a different fashion.  Toolbars themselves are singular, but certain toolbar items (drop downs, etc.) can have children, though not always recursive.  Most of the nomenclature is the same as discussed above, and I won't bore you with another user interface item.

Putting It Together

So how are all these objects exposed?  The key is to create an object that exposes them globally, like a singleton.  For instance, in the Word API, there is an Application object that is the core object for references.  I like to follow a similar approach, where I group all of these sorts of objects in an application object.

Thus, the creation of the ApplicationModel object exists, which exposes menu items, document windows, tool windows for the left, right, and/or bottom, and toolbars.  One central object can be used to listen to each child object's events, and make that notification known to the whole application.  For instance, below are two of the property definitions:

Listing 11

public DocumentWindowCollection DocumentWindows
{
  get
  {
    if (_documentWindows == null)
    {
      _documentWindows = new DocumentWindowCollection();
      AttachToDocumentWindowEvents(_documentWindows);
    }
    return _documentWindows;
  }
}
public MenuItemCollection Menus
{
  get
  {
    if (_menus == null)
    {
      _menus = new MenuItemCollection();
      AttachToMenuEvents(_menus);
    }
    return _menus;
  }
}

The attach method attaches to the collection events, which are fired everytime an object is manipulated within the collection.  The following illustrates the broadcasting of those events; when these events fire, they are thrown up to a parent through another set of events, which I will not show for brevity purposes.  You'll be able to see that in the code attached though.

Listing 12

private void AttachToDocumentWindowEvents(DocumentWindowCollection
  documentWindows)
{
  documentWindows.ItemAdded += OnDocumentWindowAdded;
  documentWindows.ItemAdding += OnDocumentWindowAdding;
  documentWindows.ItemInserted += OnDocumentWindowInserted;
  documentWindows.ItemInserting += OnDocumentWindowInserting;
  documentWindows.ItemRemoved += OnDocumentWindowRemoved;
  documentWindows.ItemRemoving += OnDocumentWindowRemoving;
}

Furthermore, the application model object can house other properties, such as a status, as shown below.  This root object is a good place to put certain properties.

Listing 13

public string Status
{
  get
  {
    return _status;
  }
  set
  {
    if (string.Compare(_status, value, StringComparison.CurrentCulture) != 0)
    {
      _status = value;
      OnStatusChanged(EventArgs.Empty);
    }
  }
}
Downloads

This code can be downloaded from the Nucleo open source framework I am creating.

Conclusion

One more thought about this; the application will be as flexible as the object model.  When it comes to newer technologies like WPF, the object model has to be flexible to handle some of this.  It doesn't have to handle every feature, but needs to be useful enough to work in a variety of situations.


Product Spotlight
Product Spotlight 

©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-05-01 10:44:55 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search