In Part2 of the MVVMDemo
application, we will focus on the 2nd function of the MVVM design, namely how
the "WorkSpaces" are handled. This function is handled very similar
to the first function "Commands" within the MVVM system. If we shall remember the startup routine, the
XAML Parser was running the "Commands" method from the
MainWindowViewModel (our DataContext), the code was creating a Collection
(readonly), adding 2 objects (CommandViewModel), creating a RelayCommand
(ICommand derived class) for each object added. Then XAML Parser would assign
the collection to the ItemsControl's ItemSource Property and rendering the
DataTemplate according to the XAML script. The ultimate goal of all this
initial setup was to 1-Create the command structure to handle the commands
coming from our UI (i.e.Click on the hypertext on Control Panel) in a modular
and loosely coupled way and 2-XAML
Parser reading all the details of this (command) structure (Note: XAML
Parser has build in intelligence to record objects involved in the setup run
and retrieve them during activation run).
The "WorkSpaces"
initial setup routine works in a similar fashion. As opposed to the
"Commands" initial setup however, the function (goal#1) is to create
a mechanism for "closing" the workspaces, which were launched and
handle the disposal process (garbage collection) and RequestClose delegate
stack in an orderly fashion, again in a modular and loosely coupled way. Goal#2
remains always the same. Now lets dig deeper into the concept.
If we pay attention to the code
at "Workspaces" method (MainWindowViewModel), the return type is an
"ObservableCollection", which really plays a very important role in a
MVVM design. It is a collection which has build-in notification functionality,
should objects added, removed and modified. This automated functionality and
the fact that WPF framework objects (like ItemsControl) have plug-in
capabilities to this collection, making it an ideal tool for an object
container in WPF and especially in MVVM design.
/// <summary>
/// Returns the collection of available workspaces to display.
/// A 'workspace' is a ViewModel that can request to be closed.
/// </summary>
///
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
_workspaces.CollectionChanged += this.OnWorkspacesChanged;
}
return _workspaces;
}
}
/// Returns the collection of available workspaces to display.
/// A 'workspace' is a ViewModel that can request to be closed.
/// </summary>
///
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
_workspaces.CollectionChanged += this.OnWorkspacesChanged;
}
return _workspaces;
}
}
void OnWorkspacesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (WorkspaceViewModel workspace in e.NewItems)
workspace.RequestClose += this.OnWorkspaceRequestClose;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (WorkspaceViewModel workspace in e.OldItems)
workspace.RequestClose -= this.OnWorkspaceRequestClose;
}
void OnWorkspaceRequestClose(object sender, EventArgs e)
{
WorkspaceViewModel workspace = sender as WorkspaceViewModel;
workspace.Dispose();
this.Workspaces.Remove(workspace);
}
{
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (WorkspaceViewModel workspace in e.NewItems)
workspace.RequestClose += this.OnWorkspaceRequestClose;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (WorkspaceViewModel workspace in e.OldItems)
workspace.RequestClose -= this.OnWorkspaceRequestClose;
}
void OnWorkspaceRequestClose(object sender, EventArgs e)
{
WorkspaceViewModel workspace = sender as WorkspaceViewModel;
workspace.Dispose();
this.Workspaces.Remove(workspace);
}
As we can see the sequence
is almost identical for the commands. Store classes exposing ICommand property
into a WPF collection (ObservableCollection, ReadOnlyCollection), then set the
path to Command Class (Class inherited by ICommand-in this case RelayCommand),
then delegate to methods which are located in the same VIEWMODEL Class (here
MainWindowViewModel), where the command is first received. Here is a simplified
diagram which summarizes this description.
In an MVVM structure, the two
most significant aspects are modular design
and loose coupling (i.e for Unit Testing,
maintainability,expandibility). Here we can see that all of those requirements
are met. To start with, we have a loose coupling thanks to WPF build-in
functionality of binding concept and we further use ICommand and delegate
concept to make the design even more loosely coupled. We can see that with this
decoupled design above, all the different classes can be Unit Tested easily,
without affecting the other.
The summary can be laid down as
follows.
·
Use a ViewModel (#1) to
confront commands coming from the UI,
·
Expose the ICommand
Property via a separate ViewModel (#2) (here CommandViewModel or
WorkSpaceViewModel),
·
Use a single and
separate Command class (ICommand derived class) for handling all the commands
in the MVVM system (here RelayCommand) (Also note:Prism uses a
"DelegateCommand" class, which is basically the same as RelayCommand)
·
And finally execute the
commands in the first ViewModel through event invocation.
At Setup process#2 (workspaces),
a new ObservableCollection (_workspaces) is created to hold the
WorkSpaceViewModel objects (could be either CustomerViewModel or
ALLCustomersViewModel).
When a new workspace is added (by
clicking to one of the hypertext rendered images on the Customer Panel), a new
WorkSpaceViewModel object instance is created and added into the
ObservableCollection WorkSpaces. This will trigger the event handler
OnWorkspacesChanged, which in turn will add a new delegate (method pointer) linked
to the justly added WorkSpaceModel object's RequestClose Event (This Event is
handled at MainWindowViewModel as well).
(workspace.RequestClose += this.OnWorkspaceRequestClose;)
Now, when an already created workspace is clicked
close (Close button on top of the window),
the ObservableCollection (WorkSpaces) will trigger the event handler
OnWorkspacesChanged again, which in turn will remove the old delegate (method
pointer) linked to the current WorkSpaceModel Object.
(workspace.RequestClose -= this.OnWorkspaceRequestClose;)
This will trigger
"OnWorkspaceRequestClose" handler method, which would first dispose
the WorkSpaceModel object (Garbage collection) and then remove it from the
Workspaces collection (visually removing it from the UI).
Some WPF (Non MVVM) Details:
If we look closer to
MainWindowViewModel class, we see a method called
"SetActiveWorkspace". This function is called from within
"CreateNewCustomer" and "ShowAllCustomers" methods (which
launch workspaces). The purpose of this function is to have a focus on the
newly created WorkSpace (created by clicking on either hypertext). What is
going on the background? In order to understand what WPF does in the background,
we need to have some idea about what the "CollectionViewSource" is.
This class has been added with .NET 3 framework and for the sole purpose of
manipulating (adding,sorting and grouping) collections directly from XAML. So
why do we use it in the code? Well, CollectionViewSource can be used in C# code
like many other XAML usable classes, but the good thing is that it has a
property called "DefaultView" which returns the CollectionView of a
collection (refer to Binding to
Collections and Collection Views). Once we create an instance for
the CollectionView for the WorkSpaces collection, we can manipulate the
collection from the code as well. (note that CollectionView class is created by
the WPF automatically whenever certain collections (i.e. ObservableCollection)
are created). Here by code, we are moving CollectionView pointer to current
(newly created) workspace (collectionView.MoveCurrentTo(workspace);).
No comments:
Post a Comment