
Introduction
BBInterfaceNET is a visual designer application that can be used to build Blackberry user interfaces more easily. A user can create a project in order to manage
the screens and can then generate the code files that are to be imported in the Blackberry project.
I'm really happy about this article because this is my first Open Source project. You can download the app installer
and the source code from the project's page on CodePlex.
This article will focus on the application architecture and usage. The application the article talks about is meant to gauge the potential usefulness of a visual designer tool for Blackberry user interfaces. Your input will be invaluable to making this project grow into a full featured designer, so please share your thoughts on the matter.
Article contents
The application architecture
BBInterfaceNET is a WPF application that uses PRISM. The application is composed of 6 modules.
- Explorer Module - used to manage the project and project files
- Toolbox Module - presents all the controls available to build the interfaces
- Properties Module - used to edit the properties of the selected interface element
- Layout Module - used to present a hierarchical view of the current screen document
- Designer Module - used to present the documents that will be edited
- Controls Module - this module holds the initial collection of controls used to build the BB UI
All these modules are loaded into the shell when the application
starts. The module discovery is done by using the application
configuration file. The listing below presents the definition for the
Toolbox module.
<modules>
<module assemblyFile="ModuleDefinitions\BBInterfaceNET.Toolbox.dll"
moduleType="BBInterfaceNET.Toolbox.ModuleDefinition.ToolboxModule,
BBInterfaceNET.Toolbox, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"
moduleName="ToolboxModule" startupLoaded="True" >
<dependencies>
<dependency moduleName="BaseTypesModule"/>
</dependencies>
</module>
</modules>
The image below presents the main application screen as well as the containing regions.

The application was designed to be extensible but in its current
version this is not possible. This extensibility will be represented by
the user's
possibility to add new controls that can be used to build the user
interfaces. I will talk about how to do this later in the article.
The application modules
The ProjectExplorer module is used to add the functionality
that is necessary to create and manage the files the application works
with. The module functionality is in its single view-model,
represented by the ExplorerViewModel class. This class
contains the necessary code to create and delete the project files
and folders. The ExplorerViewModel constructor offers some hints about
how the project explorer communicates with the other parts of the
application. The code can be seen in the listing below.
public ExplorerViewModel()
{
addNewItem = new DelegateCommand<ProjectNodeBase>(OnAddNewItemCommand);
ExplorerCommands.NewFileCommand.RegisterCommand(addNewItem);
addExistingItem = new DelegateCommand<ProjectNodeBase>(OnAddExistingItemCommand);
ExplorerCommands.ExistingFileCommand.RegisterCommand(addExistingItem);
addDirectory = new DelegateCommand<ProjectNodeBase>(OnAddDirectoryCommand);
ExplorerCommands.NewDirectoryCommand.RegisterCommand(addDirectory);
openFile = new DelegateCommand<ProjectNodeBase>(OnOpenFile);
ExplorerCommands.OpenFileCommand.RegisterCommand(openFile);
deleteCmd = new DelegateCommand<ProjectNodeBase>(OnDeleteItem);
ExplorerCommands.DeleteCommand.RegisterCommand(deleteCmd);
renameCmd = new DelegateCommand<ProjectNodeBase>(OnRenameItem);
ExplorerCommands.RenameCommand.RegisterCommand(renameCmd);
}
The class constructor registers various local scoped commands
with some module scoped commands. These commands will be
triggered when the users use the project explorer’s context
menus to create and edit files. While implementing the
functionality, I discovered that only defining local commands
isn’t enough. Apparently the context menu can’t bind to commands
in the corresponding view model. My guess is that the context
menu dropdown is in a different control tree. Anyway, you can
still attach the menu items in the context menu to commands if
these are globally accessible. I thought that the best way to
do this will be to define the commands in a static class so
that they can be accessible from anywhere in the project. The
commands will, of course, need to be composite commands
because the logic that will be executed should not be globally
exposed.
In this way we will have our local commands that can be data
bound to the context menu items. In normal conditions the
commands registered with a composite command will need to be
deregistered. Considering the fact that there is only one project
explorer instance and that this instance will expire when the
application closes I felt like there was no need to deregister
them. The listing below presents the definition of a single
composite command.
internal static class ExplorerCommands
{
private static CompositeCommand newFileCmd = new CompositeCommand();
public static CompositeCommand NewFileCommand
{
get { return newFileCmd; }
}
}
The code below shows this command being data bound the the
context menu option.
<MenuItem Header="Add New Item" Command="{x:Static cmd:ExplorerCommands.NewFileCommand}" CommandParameter="{Binding}" ></MenuItem>
The command parameter in the above code represents the
current element in the tree that was clicked.
The other way in which the ExplorerViewModel class
communicates is through normal events. Events are triggered
in the following situations: when an item is created, added
(existing item), opened, deleted or renamed and when the
project is closed. I chose this implementation because I
thought the functionality could be reused in projects
that do not use PRISM. The other option would have been to
publish CompositePresentationEvents.
Since there will be a need to access the explorer
functionality from other modules the explorer class implements
the IProjectExplorer interface. This interface
definition can be seen below.
public interface IProjectExplorer
{
ProjectInfo OpenProject(string projectFilePath);
void CloseProject();
void CreateNewProject(ProjectInfo projectInfo);
bool IsOpened();
string ProjectName { get; }
string ProjectPath { get; }
event EventHandler<FileEventArgs> ItemCreated;
event EventHandler<FileEventArgs> ItemAdded;
event EventHandler<FileEventArgs> ItemOpened;
event EventHandler<FileEventArgs> ItemDeleted;
event EventHandler<FileRenamedEventArgs> ItemRenamed;
event EventHandler<FileEventArgs> ProjectClosed;
}
As you can see, the interface exposes a few basic methods
and all the events I previously discussed. The last thing
to discuss regarding this module is how the view is
registered and how the module communicates with the other
parts of the application. This is all done in the module
definition class. This class can be seen below.
public class ProjectExplorerModule:IModule
{
IUnityContainer container;
IEventAggregator eventAggregator;
public ProjectExplorerModule(IUnityContainer container, IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
this.container = container;
}
public void Initialize()
{
container.RegisterInstance<IProjectExplorer>(new ExplorerViewModel());
IRegionManager regionManager=container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ExplorerRegion", typeof(ExplorerView));
SubscribeToExplorerEvents();
}
private void SubscribeToExplorerEvents()
{
IProjectExplorer explorer = container.Resolve<IProjectExplorer>();
explorer.ItemCreated += (s, args) =>
{
eventAggregator.GetEvent<FileCreatedEvent>().Publish(args.FilePath);
};
}
}
The Initialize method first registers an
instance of the explorer because this will need to be
accessed from the shell when projects are created, opened
or closed. The method than registers the view with the
explorer region. When the region will be displayed, a
view instance will be created and the registered
IProjectExplorer instance will be injected in the
view’s constructor like in the code below.
public ExplorerView(IProjectExplorer viewModel)
{
InitializeComponent();
this.Loaded += (s, e) => { DataContext = viewModel; };
}
After the view is registered, the module subscribes to
the normal events and triggers composite presentation
events that will be used to notify other parts of the
application.
The Toolbox module is used to display all the
controls that are loaded into the application and that
can be used to build the Blackberry interfaces. The
module definition file can be seen below.
public class ToolboxModule:IModule
{
IUnityContainer container;
public ToolboxModule(IUnityContainer container)
{
this.container = container;
}
public void Initialize()
{
container.RegisterInstance<IToolboxService>(new ToolboxService(container));
IRegionManager regionManager = container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ToolboxRegion", typeof(ToolboxView));
}
}
The module first registers a local implementation of
the IToolboxService. This implementation retrieves all
the relevant controls that are registered with the unity
container. Every module that supplies new components
will need to register those components with the
UnityContainer. This will make all the new
components available for use. After this the view is
registered with its corresponding region. The view
model is injected into the view's constructor when the
view is created. All the view-model does is to expose
the collection of controls it gets from the service.
This can be seen below.
public List<ToolboxItem> Items
{
get
{
if (items == null)
{
items = toolboxService.GetItems().OrderBy(p => p.Description).ToList();
CollectionView cv = CollectionViewSource.GetDefaultView(items) as CollectionView;
if (cv != null)
cv.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
}
return items;
}
}
The PropertiesWindow module is used to edit
the selected control properties. When a control is
selected in the designer, a composite presentation event
is raised with the corresponding control. The
PropertiesViewModel registers for this event and
executes the required logic. This can be seen in the
code below.
public PropertiesViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
FieldSelectedEvent evt = eventAggregator.GetEvent<FieldSelectedEvent>();
evt.Subscribe(OnSelectedFieldChanged, ThreadOption.UIThread);
}
public void OnSelectedFieldChanged(Field selField)
{
SelectedField = selField;
}
The view-model also publishes an event when the
value changes. This is in order to mark the current
document as dirty. The code that does this can be
seen below.
public void RaiseFieldPropertyChanged(object newValue, object oldValue)
{
FieldChangedEvent evt = eventAggregator.GetEvent<FieldChangedEvent>();
evt.Publish(new Events.Model.FieldChangedData() { NewValue = newValue, OldValue = oldValue });
}
This method is triggered from a behavior when
the PropertyGrid’s PropertyValueChanged
event is fired. The Module definition can be seen below.
public void Initialize()
{
regionManager.RegisterViewWithRegion("PropertiesRegion", typeof(PropertiesView));
}
The view-model is injected into the view’s constructor
when this is created.
The LayoutWindow module is used to show a
hierarchical view of the current document. There will
be some instances in which the user will not be able to
edit the current document by just using the designer
window. In these cases the user can use the layout
window for more control. This layout window is also
the only way the user can add a title, banner and a
status to the screens. Seeing that this is just
another view for the designer, there are no view-models
in this module. There is only the view. The code below
presents the code that registers the view with the
region. This code uses the second overload of the
RegisterViewWithRegion method (not exactly sure why).
public void Initialize()
{
LayoutView view=new LayoutView() { DataContext = null };
IRegionManager regionManager = container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("LayoutRegion", () => { return view; });
}
The last module is the Designer module. This
module presents the application designer, the view
that will display the list of active documents. The
module initialization method can be seen below.
public void Initialize()
{
container.RegisterInstance<IAddFieldService>(new AddFieldService());
IRegionManager regionManager=container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("DesignerRegion", typeof(DesignerView));
}
The module first registers a service. This service
is an UI interaction service that is used to present
the AddFieldDialog window. The user can use this
window to add new fields to the screen without using
the toolbox drag and drop operation. After this, the
Designer view is registered with the corresponding
region. The more I think about it the more I feel I
should have used the InteractionRequest pattern
instead of the UI service. I used the service
because PRISM doesn’t support the InteractionRequest
pattern for WPF. In the meantime I replicated the
Silverlight functionality. This will be replaced in
a future version.
The designer view-model will be injected into the
view when this is created. The designer view-model
registers for all the composite presentation events
that are published by the explorer module. This can
be seen below.
public DesignerViewModel(IEventAggregator eventAggregator, IUnityContainer container,
IDesignerPersistenceService persistenceService)
{
this.eventAggregator = eventAggregator;
this.container = container;
files = new ObservableCollection<DocumentViewModel>();
this.persistenceService = persistenceService;
CloseProjectEvent clProjEvt = eventAggregator.GetEvent<CloseProjectEvent>();
clProjEvt.Subscribe(OnProjectClosed, ThreadOption.UIThread);
FileOpenedEvent openFilesEvt = eventAggregator.GetEvent<FileOpenedEvent>();
openFilesEvt.Subscribe(OnFileOpened, ThreadOption.UIThread);
FileDeletedEvent delFilesEvt = eventAggregator.GetEvent<FileDeletedEvent>();
delFilesEvt.Subscribe(OnFileDeleted, ThreadOption.UIThread);
FileRenamedEvent renFileEvt = eventAggregator.GetEvent<FileRenamedEvent>();
renFileEvt.Subscribe(OnFileRenamed, ThreadOption.UIThread);
FileCreatedEvent createdEvt = eventAggregator.GetEvent<FileCreatedEvent>();
createdEvt.Subscribe(OnFileCreated, ThreadOption.UIThread);
this.PropertyChanged += (s, a) =>
{
if (a.PropertyName == "SelectedFile")
{
IRegionManager rm = container.Resolve<IRegionManager>();
IRegion reg = rm.Regions["LayoutRegion"];
if (reg == null) return;
IView view = reg.Views.FirstOrDefault() as IView;
if (view != null)
{
view.DataContext = SelectedFile;
}
}
};
}
The constructor also creates the list of documents
and listens for the PropertyChanged event on one of
its own properties. When the selected document is
changed the LayoutView data context is changed. I
don’t really like this code, but at this time I
can’t see any other solution to update the LayoutView.
In the designer view I used a couple of automatic
data templates in order to display the 2 supported
designer views: the screen view and the image view.
The xaml can be seen below.
<DataTemplate DataType="{x:Type vm:FileDesignerViewModel}">
<v:FileDesignerView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ImageDesignerViewModel}">
<v:ImageDesignerView />
</DataTemplate>
<TabControl ItemsSource="{Binding Path=Files}" BorderThickness="0"
SelectedItem="{Binding Path=SelectedFile, Mode=TwoWay}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Background="Transparent" Padding="0"
>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Background" Value="WhiteSmoke"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
This will allow the designer code to add new
elements to the document collection and these will
be automatically displayed because of the data
templates. I think a more elegant solution would
have been to have another region here and then
use the new PRISM navigation feature. At the time
I developed the designer I still didn’t have a
grasp on the navigation but I will certainly
change this part to use navigation as this is
a perfect candidate.
When a file opened event is triggered the
designer will create a new view-model and will
add that to the document collection.
The rest of the modules are modules that
contain the controls used to build the Blackberry
UIs. At the moment the application contains only
one such module. Users can add others if they wish
to extend the application. There is some
interesting code in this module that I will like
to talk about. The module initialization method
can be seen below.
public void Initialize()
{
IDictionaryMergingService mergingService = container.Resolve<IDictionaryMergingService>();
mergingService.MergeDictionary(new BaseFieldsRS());
var types = Assembly.GetExecutingAssembly().GetTypes().Where(
p => !p.IsAbstract && p.IsSubclassOf(typeof(Field))).ToList();
foreach (var type in types)
{
container.RegisterType(typeof(Field), type, type.AssemblyQualifiedName);
}
}
Besides registering the new controls that the
module wants to add, the module does something
else. It uses the IDictionaryMergingService to
merge the controls’ data templates into the shell
application’s resource dictionary. This is what
makes it possible to show controls added by new
modules without the original app knowing about
how they should look. The IDictionaryMergingService
is registered in the shell. The control data
templates are applied automatically by type and
they are applied recursively when there is a
manager that needs to display its children.
Regions and view Registration
Regions act as placeholders for one or more
views that will be displayed at run time.
Modules can locate and add content to regions
in the layout without knowing how and where
the regions are displayed. This allows the
layout to change without affecting the modules
that add the content to the layout.
Regions are sometimes used to define
locations for multiple views that are logically
related. In this scenario, the region control
is typically an ItemsControl-derived control
that will display the views according to the
layout strategy that it implements, such as
in a stacked or tabbed layout arrangement.
Regions can also be used to define a
location for a single view; for example, by
using a ContentControl. In this scenario,
the region control displays only one view
at a time, even if more than one view is
mapped to that region location.
The BBInterfaceNET shell contains 5 regions.
In PRISM regions can be defined in xaml or
in code. The application uses the first option.
The xaml code below shows a stripped down
version of the region declarations.
<ContentControl regions:RegionManager.RegionName="ExplorerRegion" />
<ContentControl regions:RegionManager.RegionName="LayoutRegion" Grid.Row="2" />
<ContentControl Grid.Column="2" regions:RegionManager.RegionName="DesignerRegion" />
<ContentControl regions:RegionManager.RegionName="ToolboxRegion" />
<ContentControl Grid.Row="2" regions:RegionManager.RegionName="PropertiesRegion"/>
As soon as the RegionName attached property
is set on a ContentControl or on an
ItemsControl a region is created in the
default RegionManager. When the application
starts and the modules are loaded, these
modules register views with these regions.
In PRISM there are 2 ways to register a
view with a region: view discovery and view
injection. View discovery is used to register
views that don’t change very often. View
Injection is used to add views dynamically
and is appropriate when the views in a
region change often. The application uses
view discovery to register views with the
5 regions in the shell. By using view
discovery, when a particular region is shown,
the views registered with that region are
automatically loaded. Each module registers
a corresponding view when it is initialized,
guaranteeing that when the application’s
main window is shown so are the 5 views. The
code below presents the registration for
the designer view.
public void Initialize()
{
container.RegisterInstance<IAddFieldService>(new AddFieldService());
IRegionManager regionManager=container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("DesignerRegion", typeof(DesignerView));
}
As can be seen from the above code, the
current implementation registers a single
view with the designer view by using view
discovery. I kept thinking about this. In
the designer the number of views changes
frequently as the user opens and closes
the documents.
A better implementation
would be to use view injection or even
navigation. I think view injection would
work best here instead of view discovery
or navigation though. Navigation should be
used when there are multiple views to
navigate and only one can be shown at a
time. Also navigation should be used if the
user might need to validate or save the
current step before moving to the next. A
next version of the application will use
view injection.
Using commands to communicate between modules
Inter-module communication can be done in a number of
ways in PRISM. We can communicate by using commands, the
region context, event aggregators and shared services.
The application presents a few scenarios where communicating
by using commands is the best choice. These situations
are: saving files, closing files and closing the application.
Saving files
The commands used to save the files are triggered from
the shell. The code that actually saves the files is
implemented in separate module (the DesignerModule). In
order to link the two we will need to use some sort of
global commands. Using a DelegateCommand in this case and
making it globally accessible is not the right choice
because the user may want to save all the files at once.
Luckily PRISM offers the CompositeCommand class. The
CompositeCommand class is a collection of commands that
are executed in order. When a CompositeCommand is
triggered all the registered commands are executed in
sequence. Using CompositeCommands is the best way to
implement the save functionality for the application.
The application has two save commands: save and save
all. Save all will save all the opened documents while
the save command will only save the active document.
There is an interesting thing to metion here about the
save functionality. The save command will be inactive
for a document that is not dirty. This presents a
problem with the save all command. By default a
CompositeCommand can execute only if all registered
commands can be executed. This means that by default
if a document in the list of active documents is left
unchanged the other documents can't be saved by using
the save all command.
In order to change this i overrode the default
CompositeCommand implementation. In particular I
derived from the CompositeCommand class and changed
the CanExecute implementation to allow the
CompositeCommand to be triggered if at least one of
the registered commands could be executed. The code
can be seen below.
public class CustomCompositeCommand:CompositeCommand
{
public CustomCompositeCommand():base()
{}
public CustomCompositeCommand(bool monitorCommandActivity)
: base(monitorCommandActivity)
{}
public override bool CanExecute(object parameter)
{
ICommand[] commandList;
bool hasEnabledCommandsThatShouldBeExecuted = false;
lock (this.RegisteredCommands)
{
commandList = this.RegisteredCommands.ToArray();
}
foreach (ICommand command in commandList)
{
if (this.ShouldExecute(command))
{
if (command.CanExecute(parameter))
{
hasEnabledCommandsThatShouldBeExecuted = true;
}
}
}
return hasEnabledCommandsThatShouldBeExecuted;
}
}
The interesting part of the code happens in the
for loop. The loop iterates over all the registered
commands and if at least one of then can be executed
the CanExecute method for the CompositeCommand
returns true. This is somewhat different from the
original implementation in that the method returned
true only if all commands could have been executed.
My implementation presents a risk. If the user
does nothing else command that shouldn't execut may
execute. In order to fix this every view-model that
registers commands with this type of CompositeCommand
sould add a check in the execute handler and execute
the method only if the current command can execute.
This can be seen below.
private void OnSaveCommand()
{
if (CanSaveOverride)
SaveOverride();
}
In order to make the CompositeCommands available
between modules they were defined as static members in
a publicly accessible class.
public static class InfrastructureCommands
{
private static CompositeCommand saveAllCmd, saveCmd, shutdownCmd;
static InfrastructureCommands()
{
saveAllCmd = new CustomCompositeCommand();
saveCmd = new CustomCompositeCommand(true);
shutdownCmd = new CompositeCommand();
}
public static CompositeCommand SaveAllCommand
{
get { return saveAllCmd; }
}
public static CompositeCommand SaveCommand
{
get { return saveCmd; }
}
public static CompositeCommand ShutdownCommand
{
get { return shutdownCmd; }
}
}
View-models can then register their own commands
with these. The code below presents how the save and
save all commands for the documents were registered.
saveCmd = new DelegateCommand(OnSaveCommand, () => CanSaveOverride);
InfrastructureCommands.SaveAllCommand.RegisterCommand(saveCmd);
InfrastructureCommands.SaveCommand.RegisterCommand(saveCmd);
You can see from the code above that we register the
same command with both the save and save all composite
commands. These commands differ in one way. The save
CompositeCommand sets the monitorCommnadActivity
constructor parameter to true. This means that this
CompositeCommand will only execute the registered
commands that are active and that can be executed.
This can be determined because the CompositeCommand
class implements the IActiveAware interface.
When the monitorCommandActivity parameter is true,
the CompositeCommand class exhibits the following behavior:
CanExecute. Returns true only when all active
commands can be executed. Child commands that are inactive
will not be considered at all.
Execute. Executes all active commands. Child commands
that are inactive will not be considered at all.
To support this the view-models should implement the
IActiveAware interface. The interface is primarily used
to track the active state of a child view in a region.
Whether or not a view is active is determined by the
region adapter that coordinates the views in the specific
region control. For example, the Tab control uses a
region adapter that sets the view in the currently
selected tab to active. When the view-model's IsActive
property changes you can change the corresponding
commands' IsActive property.
The way the designer is currently implemented doesn't
seem to work very well with the automatic setting of
the IsActive property by the region manager. Apparently
the region manager doesn't set the IsActive property
if you work with data templates (the current implementation
does this). In order to fix this i set the IsActive
property manually when the current document changes. The
code for this can be seen in the DesignerViewModel.
public DocumentViewModel SelectedFile
{
get { return selFile; }
set
{
if (selFile != value)
{
if (selFile != null) selFile.IsActive = false;
selFile = value;
if (selFile != null) selFile.IsActive = true;
RaisePropertyChanged(() => SelectedFile);
}
}
}
Now when the current document changes i also set the
IsActive property for all the commands in that view-model.
This can be seen below.
protected override void OnIsActiveChanged()
{
base.OnIsActiveChanged();
SaveCommand.IsActive = IsActive;
if (IsActive)
{
if (Screen != null)
eventAggregator.GetEvent<DocumentChangedEvent>().Publish(Screen as MainScreen);
eventAggregator.GetEvent<FieldSelectedEvent>().Publish(selectedField);
}
}
Application shutdown
One of the problems i faced when designing the
application was to decide what would be the best way
to handle the application shutdown. For aplications
that don't need to save documents this is easy. All
you need to do is trigger an event when the user
triggers the close command from the menu. Then you
would call the window Close method.
When you have an application that works with documents
the shutdown operation is a little harder. You need to
account for all the ways the application can be closed.
If you have unsaved data you also need to ask the user
how he/she wants to handle the unsaved documents.
For the current application all these problems need to
be solved. The application can close if the user triggers
the Exit command or if he presses the x button on the
main window. To handle the Exit command case, the
ShellViewModel class exposes the Exit command and the
Shutdown event. If the command is triggered, so is the
event. This can be seen below.
private void OnShutDown()
{
if (Shutdown != null)
Shutdown(this, EventArgs.Empty);
}
Than in the Bootstrapper's InitializeShell method
i subscribe to the event and call the Close method on
the view.
Shell shell = (Shell)this.Shell;
ShellViewModel vm = Container.Resolve<ShellViewModel>();
shell.DataContext = vm;
vm.Shutdown += (s, e) => {
shell.Close();
};
The problem now is to handle the unsaved documents.
You have to consider here that the documents are
implemented in another module and that the shell doesn't
have access to that code. To gather the required
information i used a global composite command. In the
main view's Closing event handler this command is executed.
shell.Closing += (s, e) => {
if (InfrastructureCommands.ShutdownCommand.CanExecute(e))
InfrastructureCommands.ShutdownCommand.Execute(e);
}
As you can see, the command CanExexute and Execute
mothods are passed the CancelEventArgs instance of the
main view's Closing event. All the opened documents will
subscribe to this command and will modify the Cancel
property if they need to be saved. This can be seen below.
shutdownCmd = new DelegateCommand<CancelEventArgs>(ShutdownOverride);
InfrastructureCommands.ShutdownCommand.RegisterCommand(shutdownCmd);
protected override void ShutdownOverride(CancelEventArgs args)
{
if (IsDirty) args.Cancel = true;
}
In the bootstrapper we than check the Cancel property
value. The rest of the Closing event handler can be
seen below.
if (e.Cancel)
{
IInteractionService intService = Container.Resolve<IInteractionService>();
intService.ShowConfirmationDialog("Shutdown", "There are still some unsaved documents. Do you want to save them before closing?",
(res) => {
if (res!=null && res.Value)
{ if (InfrastructureCommands.SaveAllCommand.CanExecute(null))
InfrastructureCommands.SaveAllCommand.Execute(null);
e.Cancel = false;
}
else if (res!=null && !res.Value)
{ e.Cancel = false;
}
});
}
If the shutdown operation was cancelled it means we
have unsaved documents. In this case we present the user
with a dialog that asks them how to proceed. The user
can now save the changes before closing, ignore the
changes or cancel the shutdown. If the user decides to
save, the Save All command is triggered. This, in
turn, will trigger the Save command in each open
document. The Cancel property will than be set to false
in order to allow the application to close.
Closing documents
To close a document the user will press the X button
for the corresponding tab item. This button is bound
to the CloseCommand of the base DocumentViewModel class.
All documents will derive from this class. The close
command is a regular DelegateCommand that can be
executed at anytime. The implementation for the execute
handler can be seen below.
private void OnClose()
{
CancelEventArgs args = new CancelEventArgs();
CloseOverride(args);
if (!args.Cancel)
{
InfrastructureCommands.SaveAllCommand.UnregisterCommand(SaveCommand);
InfrastructureCommands.SaveCommand.UnregisterCommand(SaveCommand);
InfrastructureCommands.ShutdownCommand.UnregisterCommand(ShutdownCommand);
FileClosed(this, EventArgs.Empty);
}
}
The method first creates a CancelEventArgs instance
and passes it to the CloseOverride method. This method
is a virtual method that can be overridden in the
derived classes. In the derived classes, documents that
can't be closed at that moment will cancel the close
operation by setting the argument Cancel property to
true. After the method returns this property is
analyzed. If the Cancel property is false the view-model
will unregister the save and shutdown commands and
trigger the FileClosed event.
In the existing derived DocumentViewModels only the
FileDesignerViewModel has the option of cancelling
the file close operation. This is because only this
type of document can be modified. The CloseOverride
implementation in this view-model looks like the code
below.
protected override void CloseOverride(CancelEventArgs args)
{
if (IsDirty)
{
IConfirmationService service = container.Resolve<IConfirmationService>();
service.ShowConfirmationDialog("Close File",
"The file has been modified. Do you want to save before closing?",
res => {
if (res == null)
args.Cancel = true;
else if (res != null && res.Value)
{ SaveOverride();
}
else{}
});
}
}
You can see that we run code only if the document
is dirty. If the document has been modified and the
user chooses to close, an interaction service presents
a confirmation dialog. Based on the response, the
document is saved before closing, the changes are
discarded or the close operation in cancelled.
The current implementation uses a custom interaction
service. Another alternative would be to use an
InteractionRequest pattern. In this situation we will
need to use a custom window type. This is because
the default confirmation window only has 2 buttons.
For this interaction we will need 3 (yes, no, and cancel).
Using the application
The application has two primary usage scenarios: creating and editing UI screens and generating
Java code.
Creating and editing UI screens
In order to start using the application we need to create a project.
This project will be used to manage the screen files. The image below
presents the New Project dialog.

As can be seen from the previous image, you can use this window to
specify the project storage path, the BB operating system version and
the BB device model. These last two settings are necessary in order to
set the screen size and the default font size when editing the
documents.
Once we click ok, the project will be created and the project file
can be seen in the explorer window. Now the user will have the
possibility to add files to the project in order to create the BB
screens. This can be done by right clicking the project name in the
explorer and choosing the Add New Item option.
As soon as a file is created, that file will be visible in the
explorer window and it will also be automatically opened in the main
region of the application (in the designer). This can be seen in the
image below.

The designer surface size is dependent on the device model. This is
why we needed to specify it when we created the project. After the file
is created the user can start adding controls to the designer. This can
be done either by dragging and dropping the controls from the toolbox
window or by using the layout window. The image below presents a screen
after a few elements have been dropped from the toolbox.

The other option we have for adding controls to the screen is to use the Layout Window.
In this window we can add and remove controls to and from any manager.
The window can also be used to set the title, banner and status for the
screen. In fact, this is the only way, at the moment, in which you can
change these screen properties.
To add elements to a manager you use the plus icon. Pressing this opens up the Add New Field
dialog. If we click ok the field is added either as a sibling or as a
child of the currently selected control, depending on the control type
(non manager or manager respectively). The image below presents this
window.

In order to change the selected element's properties we can use the properties window. The image below presents how to edit the background color of a label field.

Saving changes and generating code
Once the screens have been designed we can save them in order to
start the code generation. In order to generate the code we use the Generate menu option of the Project
menu. The files are saved in XML format. Not only that but the XML has a
very simple structure. This is in order to support a future version
that will allow the user to add controls to the screen by writing XML.
The image below presents the structure of such a file.

The application will generate the corresponding java code for our
project files. The generated code is MVC code. The application will use
the file names as the names of the view classes (the classes that derive
from the
MainScreen class). If the file names don't end if "View"
the app will automatically
suffix the file names. The app also generate one controller class for
each view class. This can be seen in the image below. The image presents
the list of project files, the generated views and the generated
controllers.

The listing below presents the code generated for one of the designed
views. We can see from this listing that the view has a reference to
the corresponding controller. This will help us delegate the tasks when
user events are triggered.
public class HomeView extends MainScreen{
public HomeView(HomeViewController controller){
super();
this.controller=controller;
initComponents();
}
public HomeView(HomeViewController controller, long style){
super(style);
this.controller=controller;
initComponents();
}
private void initComponents(){
labelField1 = new LabelField();
labelField1.setText("Click to go the the settings page");
labelField1.setBackground(BackgroundFactory
.createSolidTransparentBackground(0x00C8C800, 200));
this.add(labelField1);
buttonField1 = new ButtonField(Field.FIELD_RIGHT);
buttonField1.setLabel("Start");
this.add(buttonField1);
}
public LabelField labelField1;
public ButtonField buttonField1;
private HomeViewController controller;
}
The listing below presents the code for the corresponding controller.
public class HomeViewController {
private MainScreen view;
public HomeViewController(){
}
public MainScreen getView(){
if(view==null)
view=new HomeView(this);
return view;
}
public void showView(){
UiApplication.getUiApplication().pushScreen(getView());
}
}
Integrating the generated files into a Blackberry project
All that is necessary in order to build our Blackberry app at this point is to copy the files to the
Blackberry project and to import them by using the JDE. This job is even easier considering the fact that the
application generate the correct folder structure. The image below shows a Blackberry project.

The code listing below presents the application class code that is used to start the app.
public class App extends UiApplication {
public App() {
HomeViewController ctrl=new HomeViewController();
ctrl.showView();
}
public static void main(String[] args) {
App app=new App();
app.enterEventDispatcher();
}
}
The code below uses one of the generated controller classes in order
to show the first application screen. Next we need to add some
navigation code in order to change the screen when the user presses a
button. This is a very easy task because of the MVC architecture. To
move to the next screen we will add a button handler in the HomeView view class and in this handler we will delegate to the controller class.
This will be done in the initComponents method.
buttonField1.setChangeListener(new FieldChangeListener() {
public void fieldChanged(Field arg0, int arg1) {
controller.moveToNextPage();
}
});
The code for the moveToNextPage method can be seen in the listing below.
public void moveToNextPage(){
SettingsViewController ctrl= new SettingsViewController();
ctrl.showView();
}
Running the Blackberry application
The image below presents the two screens as they appear in the Blackberry emulator.

The image below presents the designed screens as they appear in the BBInterfaceNET application.

The UIs are a bit different but this will be solved by adjusting the control styles.
Known issues
This application is far from finished. I decided to make it public in
order to see if there is a real need in the industry for a Blackberry
visual designer. I always wandered why there isn't a Blackberry designer
available even though every other modern mobile technology has one
(WP7, Android and iOS all have visual designers. The WP7 designer rules
by the way).
Below are some of the known issues. I hope I can fix them as soon as I can.
- Not all standard Blackberry controls are implemented.
- The existing control implementations don't take into account the OS
version. The SDK controls behave differently from version to version.
- The control styles don't exactly match the BB styles.
- The application is not really extensible at this point. The app will
allow the user to add custom control libraries in order to support a
larger number of components. This will be done by developing new modules
and because the module discovery is done using a configuration file,
the module integration will be easy.
- The T4 templates that generate the Java code are hardcoded to
translate only a small number of controls. At this stage even in the
user developed a new module with new controls and used it, those
controls will not be used for the code generation (they will be saved
though). Some sort of mapping files will need to be used here in order
to make the T4 templates
truly generic.
- There are also some architectural problems. This is my first PRISM
application. Even though
I learned a lot by building it I know there are a lot of things that I
could have done better. I plan to correct these in future releases.
Points of Interest
Even though the application is by no means finished I think it has
great potential. It can be especially
useful for beginning Blackberry developers by helping them write well
structured code fast. The application can also be
useful to experienced developers by shifting their focus from tweaking
the UI to the actual business logic they need to implement.
I had lots of fun writing this application especially considering I did it in order to learn PRISM.
If you like the article and if you think the application will be
useful to you please take a moment to vote and post your comments
or sugestions.
History
- 6/17/2012 - Initial release.
- 6/19/2012 - Added module description.
- 6/20/2012 - Added the region and view registration section
- 6/24/2012 - Added the command communication section