One of the pieces of software, that I'm working on, is for supporting the lab technicians as they work with the big analyser machines that run a whole bunch of tests on trays of little vials containing samples. See, you can't just run the tests and log the results, you need to do quality control checks on the analyser, also giving it quality control solutions to analyse, where you know what the results are supposed to be, so you can check that the analyser is working ok and that the test results are valid. The rules (e.g. the Westgard rules, which you can google, though this is a sidetrack of a sidetrack...) for checking whether something is valid are pretty complicated, but semi-automatable, which is why it would be handy to have software to assist with the validation.
Problems started when I kept getting pulled away from the project to go and work on other projects (utilities for Biobank - perhaps you have heard of Biobank? Anyway Biobank has big important clout so it keeps getting higher priority), so the GUI development ends up falling behind the facilities provided by the business layer. Not good.
So the problem as originally encountered is "How do we organise the GUI code so that it can be worked on by more than one programmer (so that P can work on it as well as myself)?", but also, we wanted to solve "How do we keep the GUI code well structured and so that it doesn't end up as a tangled mass of spaghetti?"
So the interface is divided up into various sections, typically different panels with associated visual components, and different programmers work on different sections. For example, I might be working on the panel displayed higher up on the first tab; P might be working on what the third tab is showing. Each of these sections has a "view" class and a "controller" class, and communicates with the central "model" class.
P had a neat idea about how to structure the communication between the different sections.
The sections are pretty independent; they don't worry about what the other sections are up to.
Instead, the Model has associated with it a list of components that are listening out for interesting events.
The various sections register themselves as listeners, and then when interesting events happen, they hear that the events have happened, and respond somehow, maybe changing their display.
In turn, depending on what user interaction happens with that section of code, a section can give notification of an event, which will get communicated elsewhere via the list of listeners. So all communication goes through the list of listeners that the Model has.
For example, I'm working on the Snapshot section, which displays all the available test results for all the samples, and all the queued tests and pending test results. There is a method
void SnapshotController::notifySelected(int worklistEntryId) { model->setSelectedWorklistEntry(worklistEntryId); }Here a "worklist entry" represents a test that will be carried on a sample (a queued test), or a test that is currently being carried out on a sample (pending), or a test that has already been done and has a result.
Say I run the program and I click on a worklist entry in the snapshot section, then this will call the SnapshotController::notifySelected method to say this worklist entry has just been selected (the selection of a worklist entry being an event).
Similarly, if a notification is received, then it gets handled by the method
void SnapshotController::notify(int modelEvent, const EventData &eventData) { switch(modelEvent) { case MODEL_EVENT::WORKLIST_ENTRY_SELECTION_CHANGE : snapshotView->changeSelected(eventData); break; ... other cases go here for other events ... } }Here the EventData object will contain the worklistEntryId
Other sections will have similar methods. They will all satisfy the same interface specification ("interface" used in the technical OO sense, e.g. how Java uses it, I don't mean the meaning with a GUI flavour) for receiving notifications.
For example, P has a section for sample runs (a sample run is a group of different tests all performed on one sample), and it too has a
void SampleRunController::notify(int modelEvent, const EventData& eventData) { switch(modelEvent) { case MODEL_EVENT::WORKLIST_ENTRY_SELECTION_CHANGE: ... ... } } void SampleRunController::selectWorklistEntry(TObject* sender) { ... some additional code to do a check on the worklist entry id ... model->setSelectedWorklistEntry(worklistEntryId); }Ok, it's probably not obvious yet why this is cool.
The neat thing that made me think you might like this example:
To illustrate, suppose I click on a test result in the SnapshotView. This generates an event corresponding to selecting that test result, and the test result is highlighted on the screen. But the SampleRunView also got notified of that, and the sample run information also gets displayed for that test result. Similarly if I click on a test result in the sample run view, then the SnapshotView also hears that event, and highlights the test result.
So this nicely modularises: I don't have to know what P's code is up to but my code can send an event and this will be heard by other sections, and they will respond appropriately, without my code having to specify explicitly what is to happen in response.
perdita@inf.ed.ac.uk
)
Informatics Forum, 10 Crichton Street, Edinburgh, EH8 9AB, Scotland, UK
Tel: +44 131 651 5661, Fax: +44 131 651 1426, E-mail: school-office@inf.ed.ac.uk Please contact our webadmin with any comments or corrections. Logging and Cookies Unless explicitly stated otherwise, all material is copyright © The University of Edinburgh |