Continuing with the Prototype
I’ve been slowly adding my functionality to my CCTray replacement prototype. It’s slowly approaching the stage where I think it will be ready for people to try playing with it, but it’s not quite there yet. In this post, I cover a couple more areas of functionality, so some quick refactoring and discover an issue (resource conflict?)
The original CCTray offers two possible transports – .Net Remoting and HTTP (I have added a modification to allow it to handle any number, but it hasn’t made it into the trunk yet). In contrast my new replacement doesn’t have any transports – they need to be added as server monitors (thus allowing any number of possibilities). Currently I’ve already built a .Net Remoting implementation, now it’s time to implement HTTP.
.Net Remoting was very simple – just needed to instantiate a remoting instance of ICruiseManager and then call the relevant methods. Sure I needed to do some translations between their objects and mine, but still very simple.
HTTP is a bit more complicated – everything needs to be converted into HTTP requests and the responses are just plain text (well, actually XML, but close enough). Therefore there’s a lot more work!
But the good news is it has already been done – in the original CCTray application So all I did was copy and paste, and then make a couple of modifications for my application.
So, since I’m making this a modular application I started a new project for the monitor, added the basic classes (the IServerMonitor implementation, plus some configuration settings). Then I copied the following classes/interfaces from the original:
These classes I copied over unchanged. I also copied over code from HttpCruiseProjectManager and HttpCruiseServerManager into my IServerMonitor implement and then modified them to work with my code.
I did discover one oversight along the way – the dashboard needs the server as well as the project. But this information is not stored anywhere! Instead it is assumed that the server is part of the web URL (I say assumed because this is not always the case!) Thus the HTTP transport needs to parse the URL, retrieve the server name and then pass it in the web request. This would probably be a good area to tidy up in a future patch (but it will need to be at the server side, not the client side).
Otherwise the HTTP transport is now finished
In IServerMonitor the calls to ForceBuild(), AbortBuild(), StartProject() and StopProject() all took in a string for the project name. When I discovered the above issue I changed this to take in a ProjectDetails instance.
Initially I made the change with the hope of extracting the server name from the full details, but I discovered that the name isn’t passed down from the server. Instead I now use it for getting the web URL. Since I modified the interface I also needed to modify the .Net Remoting instance, but this was a simple matter of changing it to use the name from the instance.
In the future when the server side is modified to pass the server name (or alias) then this can be used as the information will already be in place.
In the configuration window I had added a tab for general settings. This currently has two settings, show in tray and hide when minimised, which currently do nothing.
So I added a NotifyIcon instance to the main window, with an icon and application name, and added some code to show/hide this based on the configuration settings. I also added an event handler to the SizeChanged event – this detects when the window is minimised and if necessary hides the window (very straight-forward stuff).
To the NotifyIcon instance I added a ContentMenuStrip which has a show and an exit command. The exit command just calls Exit() on the controller (thus shutting down the application). If the window is hidden, then the show command restores it to it’s previous state, otherwise it brings the window to the front of the z-order.
While I was adding the general settings I also discovered an issue where one of the monitor processes was crashing. I think this was due to a conflict in writing to the log file, so I added a retry loop (in a try/catch block) with a 10ms delay. After this change the error went away, but it could possibly recur some point – especially if there are lots of server monitors!
My resolution? Currently none I don’t know enough about multi-threading and cross-process resource locks. So this will need to a topic for me to research later (unless somebody else know how to do it and can tell me.)
Enough for Now
That’s enough changes for now. I had hoped to work on security, but the HTTP changes took a bit more work than planned (yes, even though I copied the other code). But for my next post I’m planning on covering security (once I’ve thought about it some more).
Informing the User
One of the key elements of the new prototype is something called an event listener. An event listener is a separate class that implements IStatusListener, that is instantiated within a server monitor and does just that – listens for events. When it hears an event that it is interested in, then it can do whatever it likes!
This is important because in the current CCTray, this type of functionality was hard-coded into the application. Even if the user doesn’t want speech or X10, the functionality is still there (yes, they can disable it, but they can’t remove it). In contrast, event listeners can sit in their own libraries and so are independant of the main application.
There are two parts to an event listener, first I need to develop the event listener and add it to the bin folder. Second the listener needs to be configured. In the long term a developer will only need to worry about the first point, but since I’m still working on the prototype I need to do both parts
How To Listen
The first part is building an actual listener. I’m going to build a listener that adds a little pop-up window, similar to the MSN notification window. Therefore I’m going to call my event listener PopUpNotification (yeah, not very original!)
The only interface that I need to implement is IStatusListener – this has two methods: Register() and Unregister(). Register() is called when the server monitor starts up and it allows the event listeners to add handlers to any of the events (ServerChanged, QueueChanged, ProjectChanged – I’ll add some more later). Unregister() is called when the server monitor is stopping and it allows the event listener to gracefully remove any handlers.
For my pop-up notification I am only interested when a project changes, so I added an event handler for the ProjectChanged event.
Next, I built a small pop-up window that has an icon, the name of the project and a message:
When a ProjectChanged event is received I display this window, and populate the project name and status. I added a method to the window to do this automatically (including a pop-up effect). And that’s all there is to it.
Configuring the Listener
Configuring the listener should just be an easy process of the user selecting the listener and then setting any configuration options, so my task is to make it that easy.
On the project configuration tab there is a button labelled “Event Listeners”. All I need to do is wire up some functionality. So, the first thing to do is add a new form, called ListenersWindow:
This window displays the name of the server monitor (just so the user can always see where they are) and a list of event listeners. To add a listener to the monitor the user checks the event listener.
Since event listeners can have additional configuration settings there is a nice empty pane next to the list. When the user selects a checked listener it will check to see if there are any configuration options for the listener. If there are no options then it will tell the user, otherwise it will show a PropertyGrid with the options in it:
These settings come from an IConfigurable instance – if the event listener implements this interface then it has configuration options.
The code behind this form is very simple – it just lists all the possible event listeners it can find, then it goes through the list and instantiates each instance that is already in the configuration. When the user selects an event listener it checks to see if it has configuration and if so displays it, otherwise it displays a message saying there is no configuration. When the user clicks on “Save” it goes through and generates a list of PluginConfiguration instances that contains the type name and any custom configuration.
What’s in a Name
One item I didn’t mention earlier is the name of the plug-in. The full type name is something like ThoughtWorks.Leo.PopUpNotification.EventListener,…, but in the screenshots above it has the name “Pop-up Notifications”.
This is handled by adding a custom attribute to the class called PlugInName. This allows a developer to associate a friendly-name with any classes that appear in lists (e.g. event listeners, transport protocols and display tabs). When the application loads the classes for these lists it checks to see if this attribute exists, if so then the friendly-name is displayed, otherwise the full type name is displayed.
Another Item Down
That’s yet another item on my to-do list completed. Next I’m planning on doing some tidying up, which will include an HTTP transport protocol, some general settings and maybe even security.
Return to the Brave New CCTray
I’ve finally managed to make some time to return to my CCTray replacement prototype (I’ve had a busy month just past).
One of the areas that I had left undone was the configuration – especially the selection of which projects to display. Currently the display will show all projects for a server – whether or not people want to see them. In this post I look at adding project selection to the configuration.
I’ve also had a couple of other ideas, so I decided to add them in. First I thought it would be nice to give the server a “friendly” name, so I’ve added this in. And secondly it would be nice to change how projects are added to the display list.
In order to allow the user to select a list of projects, I first needed to show a list of projects. Now this involved a bit of work as I needed to get the list of projects from the server. Since the new prototype doesn’t directly connect to any servers, this needed to be done via IServerMonitor. So, my first step was to add a new method called ListProjects(). This will connect to the remote server, list all the projects and return them in a List<ProjectDetails>. After this everything was easy.
Firs, in the project configuration I instantiated an instance of the new IServerMonitor (this was after the connection properties had been validated.) Then using this new instance I retrieved the projects list and passed it onto a new window called ProjectSelectionWindow:
This has a ListView to display the projects, a couple of buttons and a label telling the user which server the list has come from. The input parameters include the list of projects, the currently selected projects and the server name – these are all self-explanatory for how they are used.
When the user clicks on “Save” this raises an event, which is monitored by the configuration window. All this does is stored the list of selected projects into the configuration.
Configuration was the first half, the second half was to modify the project display. To reduce the number of comms messages I implemented the project selection at the IServerMonitor level. This means the monitor is responsible for filtering which projects get sent on.
This was a simple matter of adding a check everytime a project was changed. If the project was on the allowed list the event was raised, otherwise nothing happens.
With this simple change there was no need to modify the main window, instead it just displays everything it receives.
More Server Configuration
As I stated in the introduction, I have decided to add two more configuration options:
I have added a display name for the server. This will display a “friendly” name for the server, instead of the server address. If this name is omitted then the address will still be displayed. As well as modifying the configuration I also had to modify the main window to display the new name.
Second I have added a check box allowing the user to “Exclude Projects by Default”. The default setting is new projects on a server will automatically be included in the projects list – instead the user chooses which projects to exclude. This check box allows this to be swapped so it works the same way as the current CCTray (projects must be selected in order to be displayed.) Again, the actual selection was implemented at the IServerMonitor level.
I’ve almost finished the server configuration now (at least my current plan of configuration), the only outstanding item is to configure event listeners. So for my next post, it’ll be time to bit the bullet and finally implement event listeners.
After talking with one of the other contributors for the CruiseControl.Net project, we have decided to try an experiment. Currently CruiseControl.Net is stored in a Subversion version control system (VCS) – which means it has a central repository for all the code.
Instead we have been looking at Mercurial, which is an open source Distributed VCS (DVCS). This means everybody has a VCS running on their own machine, with an instance for the main source. Rather than hassle the project owner to get a new Subversion branch added for the CCTray replacement prototype we thought we’d try Mercurial instead.
So I’ve set up an instance at FreeHg.org – http://freehg.org/u/csut017/leo/. This has the latest copy of all the source code for the CCTray replacement.
I’ll update the code as I add more over time, and then we’ll see how the experiment goes
While I’ve done a lot of work on a replacement for CCTray, it’s not there yet While this is a reasonable simple application, there’s a lot of work for it! Especially to try and achieve my goals around robustness and stability.
So, what still needs to be done? The big items are:
This is adding the ability to exclude projects from the list. Currently the project displays will include all the projects for a server – whether or not people want to see them. In contrast to CCTray, I’ve set up this application to automatically display projects – although I’m also thinking I might leave that choice up to the user.
The main reason I haven’t gotten around to this is the need to configure it. When the configuration opens it needs to display the list of projects on the server – which of course needs a call to the server. Now this should be nice and easy, but I’ve been focused on the basic functionality and so haven’t done it yet.
I haven’t done any work on event listeners yet. An event listener is a component that sits within the server monitor process and does something when an event is received – which could be either a user-triggered event (e.g. force build) or a server triggered event (e.g. project changed). This will be the entry points for items like speech or X10.
While this is required functionality, it’s been low on my to-do list (after getting the monitors to actually work!)
I have added a little server monitor status window that tells the status of each server monitor (e.g. running or stopped). But it doesn’t tell why a monitor has stopped (especially when there has been an error). It would be nice to show an indicator if a monitor has crashed and the reason why (e.g. server does not exist, etc.)
The main reason for not doing this is I can’t figure out how to pass error information from the monitor process to the main UI. This is something to do with IPC calls or exit codes or sterr. I’ve tried a couple of approaches without any success, so it’s back to the drawing board on this one.
Despite having added the configuration settings for general configuration I haven’t actually done anything with them. This involves adding the systray icon and hiding the main window when minimized.
Again, this is due to a lack of time.
The only monitor I’ve added so far is the .Net Remoting monitor. In order to truly get the application accepted I need to build the HTTP monitor. This will be very similar to the .Net Remoting monitor, just it will send and receive information over HTTP.
Having modified the rest of CruiseControl.Net to use security, I haven’t built security into the new CCTray replacement. While I did the security work I made an initial decision for this project to exclude security until later. the only reason for this was to simplify things – I knew it would take a lot of work to build the base application, without adding in the extras.
But don’t worry, security is on the to-do list.
Yes, the new application has bugs in it! It even has bugs in it that I’m aware of!
Some of these bugs are because I have no idea how to fix them yet (I’m mainly a web developer, this project was to test my WinForms skills), others are because they are in tricky spots to fix (there’s a bug with the reloading of configuration where it adds a new server in the explorer) and there are bugs that I’m not sure where they are coming from (e.g. the monitor or the UI). Finally there are bugs that I don’t know about yet!
While I’ve tried to make the application look nice and clean, with some eye candy in it for the artistic amoung us, I know it still needs some polish. It still has a few rough edges (e.g. no window persistance, message history can be improved, etc.) I’ll work on making it look nicer and work smoother as I resolve the other issues.
Now that I’ve listed what I see as incomplete work, what sort of future direction do I see for the new application?
First and foremost I want to get it finished, tested and then added to the official release process. While I’ve built this as a replacement for CCTray, I really foresee it as an alternative to CCTray. CCTray in its initial concept was nice and simple, it’s only as extra things were added over time that it became so messy. As such I’d like to see CCTray reverted back to a simplier version (e.g. just the project monitor) with the new replacement enhanced to do all the fancy stuff that CCTray has collected (perhaps that’s just a dream though!)
Next I’m planning on some more server monitors, display tabs and maybe event listeners. My initial work for CruiseControl.Net involved building a WCF transport extension, which also needs a monitor in the new application. People have also been asking for a display tab that can be customised as to which projects it displays (so they can set up tabs for different project groupings), plus I’ve been thinking of some more graphical displays for the tabs.
Another area I’m thinking of is some more UI front-ends – a console version and maybe WPF. Theoritically this should be simple enough (considering all the hard work has already been done), but it’s probably going to involve a lot of work building a new UI (i.e. for WPF). However it still would be nice to try
So that’s what I’m thinking of for now, my next steps will be tidying everything up and getting it ready for testing.
The one area I haven’t talked about yet is the communications between the various components. The following diagram shows the components that make up the new application:
In my past posts I’ve talked about the bootstrap, UI and monitor components. Now it’s time to investigate the comms. channel, what it does and how.
Note: I should mention I’m not a WinForms expert – most of my work is in web developement. As such this component was a challenge and I’m sure there are better ways of doing it. However, for the moment it works so I’m happy with it.
The first thing to note is the UI and each monitor runs within its own Windows process. As such it cannot communicate directly with the other components – instead it needs to marshal data across the process boundry. There are a number of different ways of doing this, so I have taken my usual approach and implemented it as an interface, with a default implementation. Other people can write their own implementations, including if required implementations for other systems (e.g. Mono). This interface is called IInterProcessCommunictions:
This is a reasonably simple interface – a couple of properties, three methods and an event.
Start() and Stop() are very simple – they either start or stop listening on the channel. Start() has two arguments – a boolean and the configuration. The configuration is required for the default URI – if the ChannelUri property is set then the configuration is not needed. If a channel URI has been passed in via the args of the process then it will be set using ChannelUri, otherwise it will be left as null and the default used.
Now, the boolean is required so the channel knows whether it is the primary channel or not. Some communications processes (e.g. .Net Remoting) requires a listener to be set up, and then subsequent instances just connect to the listener. The main instance of the channel therefore needs this set to true and all other instances have it set to false. In the application the main instance is assumed to be the UI process and the client instances are the monitors.
When the channel is started then the IsActive property should return true, otherwise (i.e. before starting or after it has been stopped) it should return false.
The SendMessage() method and the MessageReceived event are tied together. When SendMessage() is called, MessageReceived will be triggered on the target client. The basic version of SendMessage() takes in two arguments – a message type and message data – which get returned to the client in a CommunicationsMessageEventArgs instance. If a message needs to be sent to a specific client then a target Id must also be passed, otherwise it is assumed the message will be sent to all the clients.
When a client (either UI or server) starts up, it creates an instance of IInterProcessCommunictions, adds an event handler for MessageReceived and calls Start(). As it needs to communicate with the other processes it calls SendMessage(), and all messages from the other clients come in via MessageReceived. Finally, when a shut-down is received it calls Stop() to cleanly shut down the channel. Nice and simple!
A Remoting Implementation
While the concept is fairly simple, I found it a challenge to actually implement the channel. I had decided to use .Net Remoting as this processes an IPC version – which is supposed to be a nice fast way of communicating between processes on the same machine. My initial intention was to add the classes and uses events to pass messages between the processes. However .Net Remoting didn’t allow me to pass events, so I had to find another way.
The following is my implementation of the channel:
The first thing to note is there is two classes – RemotingInterProcessCommuncations and RemotingChannel. RemotingInterProcessCommuncations is the implementation of IInterProcessCommunictions, while RemotingChannel is the class that is registered with .Net Remoting and is used internally to perform the actual communications.
When Start() is called for the primary instance, an instance of RemotingChannel is created and registered with .Net Remoting on the channel URI. All subsequent instances then retrieve this instance and call AddQueue() to register themselves. When they close down they call RemoveQueue() to unregister themselves, then release the reference to the instance of RemotingChannel.
When a message needs to be sent to a client, RemotingInterProcessCommuncations calls Enqueue() or EnqueueAll() to add the message to the specified queue(s).
For receiving message a separate thread is started (when Start() is called). This monitors the queue (by calling Dequeue()), when a message is sitting in the queue it then fires the MessageReceived event. Calling Stop() terminates the monitor thread.
To try and show how this works I put together the following diagram:
Each process is completely separate – but a reference to the channel is stored by multiple processes (the actual instance is in the UI process, other processes merely reference it). Within each process is the main thread and the monitor thread – the main thread is what sends the messages and the monitor thread detects these messages and raised the events.
The only issue I’ve had to be careful about is updating the UI – WinForms requires that only the main thread can update any controls, so I need to marshal events across a thread boundary.
That now covers all the major components in my new prototype, which means I’m nearly done for this series of posts. In my next post I’ll go over what still needs to be done to get it up and working and some ideas for future improvements.
Once that’s done I’ll get the code added to Subversion and then people can begin playing with it
Continuing to Configuration
Previously, in my posts on building a replacement for CCTray, I’ve talked about the different components and how they work together, and just recently how the UI works. In my posts I’ve been constantly putting off one important area – configuration!
In this post I’ll talk about configuration – the classes that make up configuration, the user interface to access it and how new components can be built to use it.
Just to help clarify matters, before I begin on how configuration is handled, I thought I should recap the different types of configuration. For the application there are three types of configuration:
- Application configuration: this is the core configuration on how the application runs. It includes configuring things like the commands that can be run from the bootstrap, the communications channel to use, location of the user configuration, etc. This configuration is stored in the app.config file and will very rarely change (if ever).
- User configuration: this is how the user wants to customise the application. This configuration includes which servers (and projects) should be monitored, which display tabs are included and general settings about how the application runs (e.g. on top, in the system tray, etc.) These settings are stored in an XML file in the user’s application directory (since they will be specific to each user). Since the user will want to change these, I need an interface to expose them.
- Display settings: these are how the user configures the interfaces – things like which panes are open, where the window is positioned, etc. These are not so much configuration settings, as persisted information. As such I haven’t done anything with these, but I do plan on persisting them sometime.
With this basic overview of the configuration, I’m going to cover the second type – user configuration (I’ve already covered the application configuration previously).
Starting from the Core
Most of the configuration uses a set of classes in the core project. These classes use XML serialisation to handle the serialisation and deserialisation. However, rather than force the application to know about this, I’ve added a gateway class to provide access to the configuration. The class that provides this services is XmlFileConfigurationService, which implements IConfigurationService. This exposes two methods – Save() and Load().
These two methods work with MonitorConfiguration, which is the root class that ties into the XML serialisation. These classes are as below:
XmlFileConfiguration loads an instance of MonitorConfiguration. This then exposes the other configuration classes. Most of these are straight-forward – they use XmlElement and XmlAttribute attributes to handle the serialisation.
The only exception to this is PluginConfiguration. This has a property called Configuration, which uses the XmlAnyElement. This causes the XML serialisation process to store all the non-recognised elements here, as plain-old XmlElement instances. The advantage of this is that it allows any configuration elements to be added, the down-side is the application needs to know how to understand them!
To handle this scenario I added an interface to the core project called IConfigurable. This interface converts an XmlElement array into a .Net class (and back again). Thus, if a class in the application also implements IConfigurable we know that it also uses PluginConfiguration.
The other point about PluginConfiguration is it has a Type property – this is the type that will be instantiated from the configuration. If the type also implements IConfigurable then the Configuration property will be passed in. The DisplayTabs property on UserInterfaceConfiguration and Transport property on ServerConfiguration are both examples where PluginConfiguration is used.
The User Interface
Now that I’ve talked about how the configuration is stored, the next question is how does the user access it? Before I continue, I should mention this is a work in progress, and I still haven’t finished the user interface for configuration yet
In the main window there is a command to open the configuration. This makes available the following set of dialogs:
This is the same tab analogue used in the current CCTray, but with slightly different tabs. Since I’ve changed the way servers are monitored, I’ve also changed the configuration for a server. The important information for every server is the address and the transport mechanism. Other than these two settings, everything else can have defaults – which I’ll talk more about soon.
The tabs configuration is similar – but the only required detail is the type of the display tab. Clicking on the Tab Configuration button brings up a dialog that uses a PropertyGrid to display the custom properties – which have been converted from the XML by IConfigurable.
Finally the General tab will display settings that apply to the whole application – at the moment there’s only two options but I’ll expand on these later.
Configuring a Server
Back to the server configuration. As well as the two required properties, the server also has a range of optional settings. These are accessed by clicking on the various buttons underneath the required settings. I haven’t implemented Excluded Projects or Event Listeners yet, so I can’t show you what they will look like yet.
Unlike the current CCTray, the new version will automatically include all projects on a server (I may make this configurable later). To reduce the number of projects the user will have the option of excluding projects – so the dialog will need to bring up a list of projects from the server and display them to the user. From here the user will select which projects to exclude.
The event listeners will provide listeners for processing different events (e.g. server/queue/project changed, etc.) on a per server basis. Currently the CCTray application has listeners like system tray pop-ups, speech and X10. As I haven’t done any work on this area yet, I can’t do anything on the configuration side But I plan on covering it soon!
As I mentioned earlier, each transport mechanism can implement IConfigurable - which means it can have its own custom configuration. Since the application won’t know what the configuration is, I can’t hard-code it in. To handle this I implemented a PropertyGrid and display the settings in there. This means the configuration can use any of the standard ComponentModel attributes and it will work normally.
Finally, I’ve added some advanced options to the server monitors. These options are to increase the robustness of the processes. Since the monitors are in their own Windows processes I can do some clever things like process recycling and failure handling.
Process recycling is similar to IIS – this allows the server monitor to be stopped and restarted automatically every so often. This is useful for machines that always have the monitor running, especially as it allows the application to clean up any potential memory leaks in the server monitor.
Failure handling is what to do if the process fails to start (or if it crashes for some reason). At the moment the only option is to retry after a certain amount of time (with a certain number of retry attempts), but later I might expand this. This will allow the application to handle scenarios like the remote server going down or there is a network issue. Rather than forcing each server monitor to add its own failure handling, this global location simplifies development of the monitors (of course, there’s nothing stopping the monitors from having it as well).
That’s what I’ve done on configuration so far, hopefully it shows where I’m planning on heading with both the application in general and with configuration. There are a few unfinished areas (e.g. excluding projects and listeners) which is mainly due to a lack of time on my part. I have a couple of fancy ideas for these areas, hence the requirement for some more time.
In my next post in this series I’m going to cover the communications channel. This is the last major area I need to cover in the new prototype and then I’ll look at adding it to Subversion for everyone to see and play with.
A Quick Recap
In my last post I started talking about the implementation of the UI for the CCTray replacement. I covered the main window, plus the splash and about windows. In this post I’ll start to delve into the details of the main window – especially how the various parts interact and how they are populated with data.
Data From the Source
All the data to be displayed comes from the controller, which in turn receives them from the communications channel. The channel has a MessageReceived event, which passes a CommunicationsMessageEventArgs instance. This contains a string telling the type of message, the id of the source and a object instance containing any additional data. To facilitate the understanding of messages I’ve also added a class called ServerCommands which contains a number of string constants for the different types of messages:
This class contains commands that will be passed in both directions on the channel. Since the controller is interested in what has changed on the server, it waits for the following commands:
When any of these commands are received, it triggers the event of the same name on the controller class. It also converts the data object into an instance of the changed type (e.g. server/queue/project) and adds a status message so the user can know the item has changed.
Updating the Main Window
When the main window started up, it added listeners to the three change events on the controller. This handler class InvokeOnUIThread() to handle any cross-thread marshalling and then passes on the require to a method called UpdateItemDisplay(). This is where I need to side-track a bit.
When an item is first loaded, it generates an instance of an IItemDisplay. This interface has a method called UpdateDisplay(), which is responsible for actually updating what the user sees. This way the main window doesn’t need to know about all the various controls and components in itself. Additionally, each node in the server explorer is also associated with an IItemDisplay, which allows the same update mechanism to be used.
Each data object has a list of these IItemDisplay instance associated with it (these are held in a dictionary), and the main window provides access to these lists via the GetItemDisplayList(). When a new item is added, the class doing the add calls GetItemDisplayList() to retrieve the correct list and then adds the new item. The same process is also followed for removing an item. Then, when a update event is received and UpdateItemDisplay() is called, all it needs to do is iterate through this list and call UpdateDisplay() on each IItemDisplay.
To implement this, there are a number of different implementations of this interface. Some of these implementations are:
- ItemDisplayBase: an abstract implementation that provides some base details
- TreeNodeItemDisplay: a default implementation for a tree node, used mainly by server explorer implementations
- ServerTreeNodeItemDisplay: an updater for servers in the server explorer
- QueueTreeNodeItemDisplay: an updater for queues in the server explorer
- ProjectTreeNodeItemDisplay: an updater for projects in the server explorer
- ProjectListViewItemDisplay: an updater for for the projects display tab (more on this later)
- PropertyGridItemDisplay: an updater for the property grid display tab (more on this later)
Each implementation know the details of the item it is updating, and it knows which details to display – thus simplifying the code in the main window.
Selecting an Item
Since the display has been abstracted away, there needs to be a way to detect which item is currently selected. To complicate matters, my design currently allows for multiple items to be selected – thus raising the possibility that items on different servers could be selected.
To work around these issues I’ve added another interface called ISelectionControl:
This interface has two methods – one for first determining which processes have been selected, and then a second one for determining which projects have been selected for a process.
When a control on the form is activated (receives focus) the ISelectionControl instance is stored in the main window. Then, when a command is activated that requires a process/project selection the implementation is used.
The server explorer has a implementation called TreeViewSelection, which returns the process id and project name of the currently selected node. If the node is a display-only node (e.g. the holder node for Projects or Queues) then it goes up the tree until it finds a node that is a non-display-only node. Some of the display tabs also have their own implementations.
That covers the displaying of data and the selection of projects, the final area I’ll cover in this post is the display tabs.
Customising the Details
In the main window I have added an area for display tabs. These will replace the projects view area in the current CCTray. While this will offer similar functionality, it also allows the replacement to display a lot more details. For example, as well as a projects list display I have implemented a details display (using the PropertyGrid control) and a queues list.
All display tabs must implement IDisplayTab:
The Initialise() method is called when the display tab is first added to the window. This will generate and return the actual control to be added (the main window then adds it to the tab control and associated any required event handlers). When the selected item in the server explorer changes, the DisplayItem() or ClearItem() methods are called – these are resposible for changed the display (and generating any IItemDisplays as required).
The Text property is the title of the tab – this is only used when the tab is first added. The MainImageList provides a way of hooking the ImageList on the main window into the display tab (an attempt to reduce memory usage and allow common images). Finally the Selector property an ISelectionControl - which can then be used to select processes and projects. If the display tab doesn’t allow selections, then this property should return null.
I’ve already produced three implementations:
- DetailsDisplayTab: displays the details on the currently selected item using a PropertyGrid
- ProjectListDisplayTab: displays a list of projects for the currently selected item
- QueueListDisplayTab: displays a list of queues for the currently selected item
These have their own associated implementations of IItemDisplay and ISelectionControl.
This post and the previous have covered most of the details on the main user interface. The next area I’ll cover is how to configure the application. This will also involve some UI windows, but also require examining classes in the other components of the applications.
A couple of weeks ago I started on my design for a replacement for CCTray. Since then I got sidetracked with merging security changes, but that’s now on hold again. So, it’s time to return to the replacement for CCTray.
In this post I’ll start to cover the user interface (UI). One of my goals for the interface is to make it extensible. I’m intending on supplying some initial implementations of components, but it will also allow other people to add their own components.
In a previous post I mocked up how I thought the interface would look. The mock-up looked like the following:
This provides the main window for the application. I’m also planning on adding a splash window, an about window, plus some windows for the configuration (the configuration windows will be covered in a future post). The splash and about windows will be very similar – but display slightly different data. The splash window will show the load progress, while the about information will display information on the application (and in the future the plug-ins).
With the above design in mind, here are my first versions of the main window and the about window:
The splash window is very similar to the about window, except without the version, copyright and company labels, also the Ok button is omitted and the grey box displays the status history (you’ll have to take my word on this as I can’t screen shot it!).
The first attempt is very similar to my mock-up – I just changed the menus around a bit. The actions have been included in the monitor menu, but I’ll probably change this later.
When the application first starts, it displays the splash screen. This screen then moves through the following steps:
- Loading main window
- Loading configuration
- Initialising interprocess communications
- Starting server monitors
At the end of these steps it then displays the main window (as above). Most of these steps are self-explanatory. Initialising doesn’t actually do anything yet, it’s just a place holder.
The start-up process uses the same bootstrap process as the monitors – but it uses a WinApplicationCommand. This does the normal Windows application start-up steps. During the start-up it generates a controller and a context. The context is a forms context – it just handles the switching of the windows and also adds a slight delay in starting the initialisation.
The controller is what does most of the work. It co-ordinates all actions within the application – including the initialisation. The splash and main windows both have message history hooks that the controller calls to add messages. It also holds references to the interprocess communications and the server monitors. As such it acts as the gateway between the actual UI components and the other parts of the system.
Part of this gateway actions involves listening for messages from the communications channel. When a message is received it fires a relevant event on itself.
The main window contains the tool bar, menu bar, server explorer, tab holder, message history and status bar. However, most of these are just pass-through items.
The tool and menu bars just call through to the correct method on the controller. The code-behind will automatically detect which server/queue/project the action is for, but after this the controller is responsible. If the action fails then an error will be displayed.
The server explorer contains a tree of all the servers, plus their queues and projects. This is populated when a server/queue/project changed event is triggered on the controller. Most of the time this just involves making sure the display is correctly updated. Clicking on an item in the explorer will change the item in the tabs. Basically it just fires a change event on all of the display tabs.
The tab holder can hold any number of display tabs. These are set in the configuration. The main window doesn’t really care about these, it just expects them to implement IDisplayTab. This interface allows the window to interact with the tabs – but I’ll talk more on this in my next post.
The message history is a list view that is populated whenever the controller adds a status message. It displays the time and the status message.
Finally the status bar is not being used yet. Eventually I’m thinking of displaying the last status message, plus the currently selected server/queue/project in it.
The main window is also resposible for handling all thread invokes – this is to ensure the correct thread is being used for updating the window.
Finally, the different parts of the window can be hidden. Either the server explorer or the tab holder can be hidden (but not both at the same time) and the message history can also be hidden. At the moment these settings are not persisted, but I’ll add them in sometime.
Time for a Break
That concludes this post on the user interface – this post has mainly been about how it looks, with a little bit on how it works. In my next post I’ll cover the coupling mechanism between the explorer, the controller and the display tabs.
In my last post (here) I started work on a replacement for CCTray. My initial work involved building a bootstrap application that would start an application command. In this post I’ll build an application command that uses .Net remoting to monitor a remote server.
In my original design (see the figure below), the application consists of four main parts – the bootstrap, the UI, the comms channel and a series of monitor.
Since the bootstrap is working, it’s now time to implement a server monitor so I can view some remote servers.
The basic goal behind a monitor is that it is an independant process that monitors a server (remote or local) and tells the application when things have changed. To increase the robustness and stability of the application, these will run in their own process – hence the requirement for a comms channel.
The monitor will wait for status changes on the server and then generate events on the channel telling the application. This does make things more interesting considering the current .Net remoting (and HTTP) does not tell the clients when things have changed, instead it just has a snapshot of the current state.
So, to get around this the monitor will poll the server on a regular basis (similar to the current CCTray implementation). When it first polls it stores the snapshot it retrieves. Then every snapshot it compares against the original to see if anything has changed. If things have changed, then it fires the relevant events.
Since most of the monitors will work in the same way, I wrote a component that will host the monitor. This handles things like initialising the actual monitor, starting communications, loading listeners and handling shut-downs.
Since this is a core part of the functionality, I added it to the Core project. The class is called ServerMonitorCommand:
This class implements IApplicationCommand hence has the public Run() method. In order for this approach to work, I added another interface called IServerMonitor, which is the actual server monitor – ServerMoniterCommand just provides the framework for it. This interface is below:
This has a lot more expected methods, properties and events. These allow ServerMonitorCommand to handle the communications.
The first thing the host needs to do is intialise the actual monitor. This is done via a command-line argument – the address of the server. When the server address has been found (using IArgsParser) it then loads the configuration (via IConfigurationService) and attempts to find the server configuration. If any of these steps fail, then the monitor will get an exception and fail to start.
The next step is to initialise the communications channel. The actual communications channel is initialised by the UI, but the server monitor still needs to register with the channel. The channel is a class that implements IInterProcessCommunictions. I’ll write more about this interface in a future post, but for now just know it is the interface for sending messages between processes. This interface has a property for the channel URI, a Start() and Stop for registering and un-registering a process, and finally a method for sending messages and an event for handling received messages.
As part of the initialisation, the type of IInterProcessCommunictions implementation is received from the configuration, the channel URI set (from the command-line args) and then the Start() method called. Since the implementation type is stored in the configuration this can be changed (especially if someone comes up with a better implementation than mine!)
Next step in the initialisation is instantiating the actual monitor (the implementation of IServerMonitor). This involves generating an instance of the implementation class, setting the remote address to monitor and loading any optional configuration settings. Again, the configuration is something I’ll handle in a future post, but for now, the configuration options are set by the implementation – so it can have any required settings. The configuration window will handle the settings (via a PropertyGrid - more to come).
The only remaing part to initialise are the listeners. The only requirement (currently) for a listener is that it implements IStatusListener. In future this may change as I haven’t tried implementing any listeners yet. Basically the implementation has a Register() method that takes in an instance of IServerMonitor. The idea is the listener will then subscribe to the events that it is interested in and will do any processing based on the events.
Once everything is initialised (which is the bulk of the work), all that remains is to call the Start() method and tidy up. I’ve included both of these together because it is up to the monitor to enter its poll loop (or other wait state). This is assumed to keep polling until something happens to break the wait state (either a stop message, an error or some other event).
As such when the Start() method call returns ServerMonitorCommand assumes everything has finished and tidies up. This involves closing down the listeners and the communications channel.
Handling Incoming Requests
The only other job of ServerMonitorCommand (currently) is to handle incoming requests from the communications channel. IInterProcessCommunictions has an event called MessageReceived which is fired whenever a message is received. Because I don’t know all the possible message types I’ve built a generic message class (this inherits from EventArgs):
This class has an identifier for the source process (the Identifier from the Process), the message type and a data holder. The message type is a string, which can be set to any value. I’ve added a static class called ServerCommands which has the currently recognised commands in it – this will probably expand over time. The data holder is just an object which can take any object that can be serialised. However it is expected that the sender and the receiver both know what type it is and handle any differences gracefully.
ServerMonitorCommand subscribes to the MessageReceived event and checks the message type against the commands it knows about (using a switch statement). The known commands then result in the appropriate call to the IServerMonitor instance.
Polling a .Net Remoting Server
That covers the host for a server monitor. Now it’s time to take a very quick look at an implementation of IServerMonitor. Any monitor will consists of two logical parts: a remote monitor and an gateway for passing on commands. For both parts the .Net remoting monitor uses an instance of ICruiseManager that has been created via remoting.
For the .Net remoting monitor, the remote monitor part conists of a poll loop that checks every five seconds. This check involves retrieving the latest snapshot from the remote server and comparing against the last snapshot. If any items (e.g. the server, a queue or a project) have changed, then the relevant event if fired. If nothing changes, then no events are fired. The ServerMonitorCommand handles these events and passes them onto the communications channel (it does this via a special listener called CommunicationsStatusListener).
The reason for using events is to allow for a passive listener in the future. A passive listener is one that registers itself with some sort of listening mechanism and then enters a wait state (no polling). When the remote server triggers the event, then the listener activates, handles the event and returns to the wait state. This involves a lot less work for the server as it is not required to constantly poll the remote server (I’m thinking of implementing this with WCF in the future).
The gateway part involves the implementation of the ForceBuild(), AbortBuild(), StartProject() and StopProject() methods. These method calls are passed onto the ICruiseManager instance.
And that’s all that is needed for a monitor.
In this post I covered my design for listening to remote servers. This involved a generic implementation called ServerMonitorCommand that provides a framework for any type of monitor. This can be replaced with a custom implementation if desired, but it provides most of the glue required to communicate with the rest of the application.
I also talked about the interface for monitors to implement to use ServerMonitorCommand, how it is initialised and called, and quickly talked about how the .Net remoting implementation works.
That completes the second component for the new CCTray – the monitor for listening to remote servers. The application now has the ability to start-up and monitor servers, now it needs to display things to the user. In my next post I’ll start looking at the UI!