Introduction
There are a lot of great articles about Composite; as an example, the Calcium articles from Daniel Vaughan, which are great, well documented, well implemented, etc... But, are very complex, and I have seen a lot of people that have dropped the study of this amazing technology because of the complexity of the articles available. Well, this is a bit of a problem, right? Great technologies should make our life easier, not bring so much complexity that we have to spend days and nights to get the point.
Composite has a beautiful purpose, and despite what has been painted about it, it is very simple to understand and to put it to work properly; so now is the time for let the small talk behind and look upon the future of WPF and Silverlight applications.
Summary
Since this is a part 1 of n, the content of this article set can vary, but my intension is to follow this sequence:
- Creating a basic Composite WPF application (this article)
- Commands in CAL (fancy name yet to be announced)
- Creating custom region adapters (fancy name yet to be announced)
- Log strategies (fancy name yet to be announced)
- Multiple developer teams, single solution (fancy name yet to be announced)
- Considerations of benefit-cost ratio of a CAL application for an enterprise (fancy name yet to be announced)
With this, I think we will cover the basics that will allow us to understand better the marvelous implementations of the complex articles available.
Pre-requisites
Obviously, we have one thing we need to get, the Composite Application Library. Here is the Composite website.
After the installation of the Composite Application Guidance pack, I strongly recommend you to read the documentation; I know it is enormous, but worthy sometime of study.
Hands-on
Starting up, you have the CAL downloaded and have built it, time to start playing with your newest toy. So let's begin with the basics.
CALient.Core
CALient.Core is the core of our application, it’s the heart and soul of it, is a WPF application that will be the center of our CAL universe.
Let's create our solution. In my opinion, the best way of creating a Composite solution is to create a Visual Studio blank solution. Why? Because you can, and will, organize your solution in solution folders; this way, it becomes simpler to know where everything is on your solution.
As shown in the above image, select from your "Other Project Types", the "Visual Studio Solutions", and choose the "Blank Solution". This will give you, literally, a blank solution as shown below:
Now, inside the solution, create a solution folder called Client; inside of this folder, you will create a new project, a WPF Application Project, and will call it CAL.Core.
Now is the time for some personal preferences, such as the core of the application. I think the core of an application isn't meant to be a program/executable, but a library, so you right click and open the properties of the project, and the following screen will open for you:
On this tab, simply change the output type of your project to a class library. Now, simply delete your window1.xaml and your App.xaml, you won’t need them.
Now, we will add to the project the name of the Regions we will register our modules, like:
namespace CALient.Core
{
public class RegionMapping
{
public static string REGION_Content = "Region_Content",
REGION_Menu = "Region_Menus";
}
}
Inside this project, create a folder called DesktopShell, and inside of it, you add a Window
called Shell.xaml.
Then, you add the references for the Composite Application Library, and in Shell.xaml, you add the following lines:
<Window
x:Class="CALient.Core.DesktopShell.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CAL="http://www.codeplex.com/CompositeWPF"
xmlns:CALient="clr-namespace:CALient.Core"
Title="Shell" Height="300" Width="300">
<DockPanel>
<ItemsControl
CAL:RegionManager.RegionName="{x:Static CALient:RegionMapping.REGION_Menu}"
DockPanel.Dock="Top"/>
<ContentControl
CAL:RegionManager.RegionName="{x:Static CALient:RegionMapping.REGION_Content}"/>
</DockPanel>
</Window>
The Desktop shell is ready for the scope of this article... pretty simple, right? No secrets there.
Now for the application to work, we will need something called a Bootstrapper. This will extend from the UnityBootstrapper
in the Composite Application Library, so let’s do that now:
class Bootstrapper : UnityBootstrapper
{
protected override System.Windows.DependencyObject CreateShell()
{
var shell = new Shell();
shell.Show();
return shell;
}
protected override
Microsoft.Practices.Composite.Modularity.IModuleCatalog GetModuleCatalog()
{
var modules = new DirectoryModuleCatalog() { ModulePath = @"./" };
return modules;
}
}
Now, we create a class to start up everything like this:
public class ApplicationStarter
{
public static void Run()
{
Bootstrapper b = new Bootstrapper();
b.Run();
}
}
Great. Core set up and ready to go.
Now, let’s create an application to start that up.
Create another project inside the Client Solution folder, call it CALient, remove Window1.xaml and remove StartupURI="Window1.XAML"
in App.xaml. Add the reference to the CALient.Core and now go to App.xaml.cs and add the following line in the CTOR method:
public App()
{
ApplicationStarter.Run();
}
Run it... and bam! A blank window =D, all this for a blank window... yep, now is the time to build the interesting part, the Modules.
How do I anyway?????
We will do the following. Let’s add a project for the modules, inside a new solution folder called Modules. In this Module, just add a WPF UserControl Library and call it any name you like; in this project, it is called CALient.One.
Then, you will delete UserControl1.xaml and add a class to this newly created assembly, call it with [name]Module, and you will have the following:
Open the file and add the following code snippet:
[Module(ModuleName = "Module One")]
public class OneModule : IModule
{
IRegionManager regionManager;
public OneModule(IRegionManager regionManager)
{
this.regionManager = regionManager;
}
public void Initialize()
{
regionManager.RegisterViewWithRegion(RegionMapping.REGION_Menu, typeof(MenuView));
}
}
This module will be composed of three Views and a menu that calls the 3 Views, like this:
So the MenuView
will be like:
<UserControl x:Class="CALient.One.MenuView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<StackPanel Orientation="Horizontal">
<Button Margin="10" Width="100" Click="Button1_Click">
<Button.Content>
<Image Source="/CALient.One;component/Images/imgOK.png" />
</Button.Content>
</Button>
<Button Margin="10" Width="100" Click="Button2_Click">
<Button.Content>
<Image Source="/CALient.One;component/Images/imgCancelar.png" />
</Button.Content>
</Button>
</StackPanel>
</UserControl>
In the code behind (since our focus here is to explain this approach and the plan is for using commands only, in the next article, we won't use commands here, but if you like, you can use them just fine =D):
UserControl current;
private IRegionManager regionManager;
public MenuView(IRegionManager regionMgr
{
InitializeComponent();
regionManager = regionMgr;
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
if(current != null)
regionManager.Regions[RegionMapping.REGION_Content].Remove(current);
current = new ModuleView1();
regionManager.Regions[RegionMapping.REGION_Content].Add(current);
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
if (current != null)
regionManager.Regions[RegionMapping.REGION_Content].Remove(current);
current = new ModuleView2();
regionManager.Regions[RegionMapping.REGION_Content].Add(current);
}
Another important issue is that once we build the application, the module won't be deployed in the correct position (in the root folder); for that to happen, you will have to add the following line in the Post-build event commandline in the module project:
xcopy "$(TargetDir)*.*" "$(SolutionDir)CALient\bin\$(ConfigurationName)\" /Y
As you have seen, our Views are just static UserControls containing different background colors and different content text.
Run it again
As you can see now, by running the solution again, our solution has been built successfully, and that by clicking the buttons on our MenuView of the Module, we start up the View, without knowing what is it, or from where it comes from; that’s the beauty of Composite; decoupled development at its maximum.
Finish up
Hope this turns out to be useful and that you like it. See you in the next article (sooner this time I hope).