Archive for the 'Articles in English' Category

WindowRegionAdapter at CompositeWPF (Prism) Contrib

Finally, I got the time to make the acceptance tests and the documentation for the WindowRegionAdapter!

 

Now, with the help of my workmate Ezequiel Jadib, it is live at CompositeWPF Contrib!

WRAatCompositeWPFContrib

Figure 1. The WindowRegionAdapter in the News section at the CompositeWPF Contrib site.

 

Download

You can get it by downloading the latest change set (you’ll find it in the %ContribFolder%Trunk\Samples\WindowsRegionAdapter folder).

 

Documentation

For more information on the WindowRegionAdapter, you may check the Window Region Adapter article in the Documentation section and my previous post on the WindowRegionAdaptrer: WindowRegionAdapter for CompositeWPF (Prism).

For more information on the latest additions to CompositeWPF Contrib, you may have a look at the following link: CompositeWPF (Prism) Contrib Latest Additions (2008-10-26).

 

Cheers!

Nacho

WindowRegionAdapter for CompositeWPF (Prism)

With the help of my dear workmate Julian Dominguez, I’ve been developing a new region adapter: the WindowRegionAdapter. It provides a way to show views in separate windows and has the following features to take into account:

  • It uses a SingleActiveRegion, so that only one (or no) view (one window) is active at a time.
  • It provides a WindowStyle property (of type Style) which allows the developer to specify a custom style to use as a template for all the windows shown in the region.

How to use the WindowRegionAdapter

Step 1 - Register the adapter mapping

The first thing you will need to do is to register the mapping for the Region Adapter for the type Window. To accomplish this, you must override the ConfigureRegionAdapterMappings() method of the Bootstrapper, retrieving the RegionAdapterMappings from the container, and calling its RegisterMapping() method, sending the Window type and a new instance of the WindowRegionAdapter, as follows:

protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
    RegionAdapterMappings regionAdapterMappings = Container.TryResolve<RegionAdapterMappings>();

    if (regionAdapterMappings != null)
    {
        regionAdapterMappings.RegisterMapping(typeof(Window), new WindowRegionAdapter());
    }

    return base.ConfigureRegionAdapterMappings();
}

[Recommended]: Alternatively, you may provide a custom style to use as template for all the windows shown in the region by setting its WindowStyle property on initialization (as shown in the sample application):

regionAdapterMappings.RegisterMapping(typeof(Window), new WindowRegionAdapter() { WindowStyle = (Style)Application.Current.FindResource(“WindowTemplate”) });

 

Step 2 - Set the owner for the child windows

To use the WindowRegionAdapter, you must add the cal:RegionManager.RegionName attribute to a Window (for example, the Shell window), as follows:

<Window x:Class=”Example.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”
Title=”MainWindow” Height=”400″ Width=”500″ WindowStartupLocation=”CenterScreen” cal:RegionManager.RegionName=”MyWindowRegion”>

The WindowRegionAdapter will get this window and will use it to set the value of the Owner property of each child window to this window.

 

Screenshots

WRAViewDesign

Figure 1. View at design time.

 

WRAViewRun

Figure 2. View displayed in a new window (by the Window Region Adapter) at run time.

 

WRAExampleRunning

Figure 3. The sample application running with several views displayed.

 

Disclaimer

This code is provided “AS IS” with no warranties. You can use it freely. It includes the binaries of Composite Application Library and Unity Application Block.

 

Download the WRAExample sample application here!

 

EDITED 10/26/2008: Now, it’s available at CompositeWPF Contrib! Get the latest change set here!

 

Enjoy! :D

As always, any feedback is welcome! :)

 

Related posts:

Cheers!

Nacho

A close view on the SmartPartPlaceholder

It’s been a time since I was wondering about posting about the SmartPartPlaceholder. It’s a useful control, yet it’s another concept that we have little information about. So, I decided to take my time and see what could be done with this control, and how it was done.

Basically, the way in which we can use the placeholder is quite simple:

We add the placeholder in a view or form and set its SmartPartName property with some ID. Then, we make sure that we have added a view with that ID in a WorkItem before adding the view of the placeholder, so that the placeholder gets it and displays it.

As simple as it gets, right? :P

But, how does that happen? Is that everything we can do with the placeholder? Why is the view with the placeholder scoped into a WorkItem in the SmartPart Quickstart?

All these questions have an answer, and that’s what this article will deal with.

The Actors

First of all, I’ll start describing the different classes that work together to perform “the magic behind the scenes”.

The ISmartPartPlaceholder interface

SmartPartPlaceholder-SPPHInterface

The ISmartPartPlaceholder interface contains only two properties which are implemented in the SmartPartPlaceholder control class. These properties are:

  • SmartPartName. This property gets or sets the default name for the SmartPart contained in the current WorkItem.
  • SmartPart. This property gets or sets the SmartPart contained by the placeholder.

The SmartPartPlaceholder control class

SmartPartPlaceholder-SPPHClass

Here’s the main character! The class that has the main role in this article’s play! :P

Luckily, it’s not a complex class at all. It consists of one constructor, a method that draws its border at design-time, the two properties implemented from the ISmartPartPlaceholder interface, and a method that fires an event when a SmartPart is shown.

So, let’s see them in detail:

  • The SmartPartPlaceholder constructor sets the style of the placeholder to support transparent color as its background color. Then, it sets the background color of the placeholder to transparent.
  • The protected OnPaint(PaintEventArgs e) method draws the dashed rectangle that forms the border of the SmartPartPlaceholder at design-time.
  • The SmartPartName property is used by the ControlSmartPartStrategy builder strategy to get the SmartPart (whose ID matches the SmartPartName property) from the current WorkItem in order to display it in the placeholder. NOTE: The value of this property cannot be null, otherwise an exception will be thrown.
  • The SmartPart property displays the SmartPart it gets as value in the placeholder. It checks that the set value is not null and that it is type of Control (otherwise, an exception is thrown). Once this is done, it casts the value to Control, sets its Dock property to DockStyle.Fill and calls its Show() method. Then, the Controls collection of the placeholder is cleared and the casted SmartPart is added to this collection. Finally, the OnSmartPartShown method is called, sending the SmartPart as argument.
  • The OnSmartPartShown private method receives an object as argument (in fact, the SmartPart that has just been displayed in the placeholder). It checks whether the SmartPartShown event has any handler attached, and, if so, fires the event with the placeholder as sender and with a new instance of the SmartPartPlaceHolderEventArgs class as event arguments (this instance contains the SmartPart in its SmartPart read-only property which was sent as argument in its constructor).

The SmartPartPlaceHolderEventArgs class

The SmartPartPlaceHolderEventArgs class has a constructor that receives an object as argument (the SmartPart recently shown in the placeholder). This constructor checks that the argument it’s receiving is not null (otherwise, it throws an exception) and sets the value of a private member (named "smartPart") to this argument. This class also has a read-only property named SmartPart (of type object) that can be used to retrieve the SmartPart that has just been displayed in the placeholder.

The ControlSmartPartStrategy class

The ControlSmartPartStrategy class is a builder strategy that is in charge of detecting the SmartPartPlaceholder whenever a control is run through the pipeline of ObjectBuilder (NOTE: the strategy also looks for Workspaces and SmartParts). Once the placeholder is detected, the strategy checks its SmartPartName property, so it can retrieve the SmartPart whose ID matches SmartPartName property from the Items collection of the current WorkItem. Having retrieved the SmartPart, it sets the value of the SmartPart property of the placeholder to the retrieved value. If the retrieved value is null, the SmartPart property of the placeholder is not set at all; this is why no exception is thrown when no matching SmartPart is found. The strategy also adds the placeholder to the Items collection of the current WorkItem, with the name of the placeholder as its ID (if the placeholder has no name, a GUID is set instead).

Here’s the code of the ReplaceIfPlaceHolder(WorkItem workItem, Control control) method that performs what was described previously:

private void ReplaceIfPlaceHolder(WorkItem workItem, Control control)

{

ISmartPartPlaceholder placeholder = control as ISmartPartPlaceholder;

if (placeholder != null)

{

Control replacement = workItem.Items.Get<Control>(placeholder.SmartPartName);

if (replacement != null)

placeholder.SmartPart = replacement;

}

}

The Play

The following sequence diagram depicts the interactions described in the "The Actors" section. For the sake of simplicity, some interactions are commented as notes.

smartpartplaceholder-interactions

WorkItem-scope for placeholders?

As it was shown in the "The Actors" section, the ControlSmartPartStrategy class added the placeholder to the current WorkItem with its name as ID. So, if you add two instances of the view that contains that placeholder to the same WorkItem, you will get an exception stating that there’s already an object with that ID. This is one of the reasons why placeholders are scoped in different child WorkItems.

The other reason deals with the view that will be displayed in the placeholder. As I said before, the SmartPartName property of the placeholder cannot be null (or you will get an exception). To get the placeholder work with this property, you must add a view with some ID to the WorkItem before you add the view with the placeholder. But, if you add two views with the same ID, you will get an exception because there’s already an object with the same ID. This is why, in the SmartPart Quickstart, the view displayed in the placeholder and the view with the placeholder are scoped into a child WorkItem.

Nevertheless, this kind of scope is necessary if any of the reasons described above may happen.

Tip

One useful way to use the SmartPartPlaceholder is like a "reduced DeckWorkspace" or a "lightweight DeckWorkspace". Before I start describing this, let me state that a placeholder can be put in forms too, not only in views! Now, let’s move on. As I said, the placeholder is added to the WorkItem with some ID, so you may put it in the ShellForm with some ID and access it in your modules. For example:

SmartPartPlaceholder placeholder = WorkItem.RootWorkItem.Items.Get<SmartPartPlaceholder>("Placeholder");
MyView view = WorkItem.SmartParts.AddNew<MyView>();
placeholder.SmartPart = view;

In that way, you may display a view in the placeholder, and when you add another, the first view will automatically be removed from the placeholder.

For this purpose, you should keep in mind that:

  • Calling the Presenter.OnCloseView() does nothing as it searches for a container workspace and calls its IWorkspace.Close(object view) method and, in this case, the view is contained by a SmartPartPlaceholder.
  • If you want to remove the view from the placeholder, you should explicitly remove the view from the Controls collection.
  • The placeholder does not contain a SmartPartClosing event.
  • The placeholder does not contain a collection of SmartParts.

I hope this article gives you a clearer concept of the SmartPartPlaceholder class.

As always: any feedback is welcome! :)

Cheers!

- Nacho

Ver este post en español.

CAB Quickie – The Plug-in Application: The Message Plug-in (Part 4)

 

In this part, you will finish the quickie by adding a CAB module to the project: the Message plug-in. It is called like that as it will only display a message entered by the user. The input will be performed on a view (a SmartPart), which will be a UserControl, so, you will add a Windows Control Library.

1. In Visual Studio, point to Add on the File menu, and then click New Project.

templateMessageProject

Figure. Creating the MessagePlugin module project.

2. In the New Project dialog box, expand the Visual C# node. Click the Windows project type.

3. In the Templates window, select Windows Control Library.

4. Change the Name to MessagePlugin.

5. Change the location for the solution to the source folder located inside the PluginQuickie folder.

6. Click OK.

SEMessagePlugin

Figure. The MessagePlugin module project added to the solution.

As you have already done, you will change the output path of the MessagePlugin project to bin\Debug folder. To accomplish this, right-click the MessagePlugin project (in the Solution Explorer) and select Properties. In the Build tab, go to the Output section and edit the Output path to %PluginQuickieFolder%\bin\Debug.

Moreover, add only the CAB assemblies located in the Lib folder in the same way you did at the CAB Quickie – The Plug-in Application: The CAB Plug-in System Application post.

Once you have done this, you will start setting up the view:

1. In the Solution Explorer, right-click the UserControl1.cs file, select Rename, and change the name to MessageView.cs.

2. Set its Size property to 210, 80.

3. Add a Label to the MessageView with the following values set to the corresponding properties:

 

Property Value
(Name) titleLabel
AutoSize False
BackColor LightBlueSky
Dock Top
Font Arial, 9,75pt, style=Bold
ForeColor White
Location 210, 19
Text Write a message:

4. Add a TextBox to the MessageView with the following values set to the corresponding properties:

 

Property Value
(Name) messageTextBox
Location 3, 23
Size 202, 20

5. Add a Button to the MessageView with the following values set to the corresponding properties:

 

Property Value
(Name) displayButton
Location 70, 49
Size 75, 23
Text Display

6. Double-click the displayButton to generate the code for the handler for its click and add the following code line inside:

MessageBox.Show(messageTextBox.Text, “Showing message:”);

Done with the View! :)

By now you may be wondering: “Wait a minute! No presenter or controller for this view?” Well, the MVC and the MVP patterns are intended for complex UI logic and to separate it from a complex model. None of these points are present, so none of the patterns (MVC and MVP) are present. :)

Let’s keep going! The MessagePlugin module needs two things to get set up: a WorkItem, to get a container for the module, and a ModuleInit class, to get the module initialized and added to the CAB application.

Firstly, let’s start by adding the WorkItem class:

  1. In the Solution Explorer, right-click the MessagePlugin module project, select Add, and then select Class.
  2. Change the Name to MessagePluginWorkItem.cs.
  3. Click OK.

This class will be used as a container only for the whole MessagePlugin module. It will add a menu item to the Plugins menu item located at the main menu strip of the MainForm, which, on click, will display the MessageView in a WindowWorkspace (that will be added in the ModuleInit class later on). So, now, you’ll configure for that purpose:

1. Add the following using statements:

using System.Drawing;

using System.Windows.Forms;

using Microsoft.Practices.CompositeUI;

using Microsoft.Practices.CompositeUI.WinForms;

2. Replace the signature of the class with this one:

public class MessagePluginWorkItem : WorkItem

3. Add the following code to the class, which overrides the OnRunStarted() method (that is called when the WorkItem gets initializated):

protected override void OnRunStarted()

{

    base.OnRunStarted();

    ToolStripMenuItem item = new ToolStripMenuItem(“Message Window”);

    item.Click += new EventHandler(item_Click);

    RootWorkItem.UIExtensionSites["PluginsMenuBar"].Add<ToolStripMenuItem>(item);

}

void item_Click(object sender, EventArgs e)

{

    MessageView view = SmartParts.AddNew<MessageView>();

    WindowSmartPartInfo wspi = new WindowSmartPartInfo();

    wspi.MaximizeBox = false;

    wspi.Title = “Message Plugin!”;

    wspi.Location = new Point(((Screen.PrimaryScreen.WorkingArea.Width - view.Width) / 2), ((Screen.PrimaryScreen.WorkingArea.Height - view.Height) / 2));

    Workspaces["WinWork"].Show(view, wspi);

}

NOTE: As you can see from this code, you’re adding a new ToolStripMenuItem to the PluginsMenuBar UIExtensionSite (that is the menu located in the main menu strip of the form with the text “Plugins”), plus a handler for its click event which creates and adds a new MessageView to the WorkItem and displays it in the “WinWork” WindowWorkspace with a SmartPartInfo (that locates it at centre screen).

Now, let’s add the ModuleInit class:

  1. In the Solution Explorer, right-click the MessagePlugin module project, select Add, and then select Class.
  2. Change the Name to MessagePluginModuleInit.cs.
  3. Click OK.

Now you will configure the class to get the module plugged in to the CAB application:

1. Add the following using statement:

using Microsoft.Practices.CompositeUI;

using Microsoft.Practices.CompositeUI.WinForms;

2. Replace the signature of the class with this one:

public class MessagePluginModuleInit : ModuleInit

3. Add the following code to get the RootWorkItem injected to the class:

private WorkItem _rootWorkItem;

[ServiceDependency]

public WorkItem RootWorkItem

{

    get { return _rootWorkItem; }

    set { _rootWorkItem = value; }

}

NOTE: When CAB gets this class (the ModuleInit class), it searches for dependencies and inject them. The WorkItem that gets injected in this code is, indeed, the RootWorkItem, as it is where the ModuleInit class gets created and added.

4. Add the following code to the class, which overrides the Load() method (that is called in order to load the module):

public override void Load()

{

    base.Load();

   MessagePluginWorkItem workItem = RootWorkItem.WorkItems.AddNew<MessagePluginWorkItem>();

   workItem.Workspaces.AddNew<WindowWorkspace>(“WinWork”);

    workItem.Run();

}

NOTE: This code adds a new instance of the MessagePluginWorkItem to the RootWorkItem and a WindowWorkspace to the MessagePluginWorkItem with the id “WinWork”.

There’s only one thing left to do! How does the CAB Application know that it has to load the MessagePlugin? Simple: by looking at the ProfileCatalog.xml! But the CAB Application does not have one, so let’s add it!

  1. In the Solution Explorer, right-click the CABPluginApplication project, select Add, and then select New Item.
  2. From the Templates dialog box, select XML File.
  3. Change the Name to ProfileCatalog.xml.
  4. Click OK.

Once it’s added, set its Copy to Output Directory property to Copy always (this is very important, otherwise CAB won’t find it and will not load the plug-ins!), and then add this code to the file, which represents the collection of modules the application must initialize:

<SolutionProfile xmlns=http://schemas.microsoft.com/pag/cab-profile>

    <Modules>

    <ModuleInfo AssemblyFile=MessagePlugin.dll/>

    </Modules>

</SolutionProfile>

Having done all these steps, just save, compile and run the project. You will see that the “Hello World!” application has a new menu item under the “Plugins” menu item saying “Message Window”. When you click on it the view is displayed as a new window, thanks to the WindowWorkspace.

The final solution will look like this:

SEFinalSolution

Figure. The final solution.

And, I leave you with some screenshots:

mainApplicationFinal

mainApplicationFinal2

mainApplicationFinal3

 

Conclusion

This Quickie dealt with one possible way to use the ApplicationContextApplication class provided by CAB. There are much more usages than this, for example, displaying more than one form, or a console application. Moreover, the ApplicationContext class that you add to the ApplicationContextApplication class can have dependencies injected, as it is created by CAB, so you may perform several checks before showing a form or start your program.

I hope that this short example (that’s why it’s called a quickie ;)) may help you have an overview on this class and its usage.

As always: any feedback is welcome! :)

Cheers,

Nacho

CAB Quickie – The Plug-in Application: The CAB Plug-in System Application (Part 3)

 

In this part, you will add the CAB plug-in application. It will be a Windows Application for two reasons:

  1. You will create a class that inherits from the ApplicationContext class, so you need the System.Windows.Forms assembly.
  2. The CAB plug-in application will become the start up project.

Let’s start by adding the new project.

1. In Visual Studio, point to Add on the File menu, and then click New Project.

templateCABProject

Figure. Creating the CABPluginApplication project.

2. In the New Project dialog box, expand the Visual C# node. Click the Windows project type.

3. In the Templates window, select Windows Application.

4. Change the Name to CABPluginApplication.

5. Change the location for the solution to the source folder located inside the PluginQuickie folder.

6. Click OK.

SECABPluginApplication

Figure. The CABPluginApplication project added to the solution.

Now, you need to change the output path of the CABPluginApplication project to bin\Debug folder, in the same way as you did in the CAB Quickie – The Plug-in Application: The Main Application post. To accomplish this, right-click the CABPluginApplication project (in the Solution Explorer) and select Properties. In the Build tab, go to the Output section and edit the Output path to %PluginQuickieFolder%\bin\Debug.

As I wrote in the first post, the CABPluginApplication will not contain a form to display; instead, it will use the MainForm of the MainApplication in an ApplicationContext class. So, you don’t need the Form1.cs file, so just delete it. To do this, right-click the Form1.cs file, in the Solution Explorer, and select Delete.

 

It’s time to get CAB started! Time to add the references!

1. In the Solution Explorer, right-click the References folder and select Add Reference.

2. In the dialog box, go to the Browse tab, browse to the %PluginQuickieFolder%\Lib folder, and select the assemblies you’ve previously copied there:

  • Microsoft.Practices.CompositeUI
  • Microsoft.Practices.CompositeUI.WinForms
  • Microsoft.Practices.ObjectBuilder

Finally, as the CAB application will use the MainForm class of the “Hello World!“ application, you need to add a reference to this project, so in the Projects tab, select the MainApplication project.

After you have done this, the solution should look like this:

SECABReferencesAdded

Figure. CAB and MainApplication references added to the CABPluginApplication project.

The ApplicationContextApplication<TWorkItem, TShell> class calls the Application.Run(ApplicationContext context) method in its overridden Start() method. This is because TShell is type of ApplicationContext. So, you need to add a class that inherits from the ApplicationContext class.

Let’s do that!

  1. In the Solution Explorer, right-click the CABPluginApplication project, select Add, and then select Class.
  2. Change the Name to PluginContext.cs.
  3. Click OK.

This class will inherit from the ApplicationContext class and will use the MainForm class of the “Hello World!” application, so you need to add the following using statements:

using System.Windows.Forms;

using MainApplication;

Then, make it inherit from ApplicationContext by replacing the signature of the class with this one:

public class PluginContext : ApplicationContext

Finally, you will add the necessary code to set its MainForm property with a new instance of the MainApplication class of the “Hello World!” application in its constructor, as follows:

public PluginContext()

{

       this.MainForm = new MainForm();

}

Alright! Almost done! :)

Ok, now that you have set all the necessary things, it’s time to finish configuring the CAB project to make it the new start-up project:

1. In the Solution Explorer, right-click the Program.cs file, select Rename, and change the name to PluginApplication.

2. Right-click the Program.cs file again, and select View Code.

3. Add the following using statements:

using Microsoft.Practices.CompositeUI;

using Microsoft.Practices.CompositeUI.WinForms;

using Microsoft.Practices.CompositeUI.WinForms.UIElements;

4. Replace the signature of the class with this one:

public class PluginApplication : ApplicationContextApplication<WorkItem, PluginContext>

NOTE: Here, you set a WorkItem type to act as the RootWorkItem and a PluginContext type to act as the ApplicationContext of the CABApplication class.

5. Replace the body of the Main() method with the following code:

new PluginApplication().Run();

NOTE: This line will create a new instance of the PluginApplication class and call the CABApplication.Run() method.

6. Override the AfterShellCreated() method, and place the following code:

protected override void AfterShellCreated()

{

    base.AfterShellCreated();

    ToolStripMenuItem item = new ToolStripMenuItem(“Plugins”);

    this.Shell.MainForm.MainMenuStrip.Items.Add(item);

    ToolStripItemCollectionUIAdapter adapter = new ToolStripItemCollectionUIAdapter(item.DropDownItems);

    RootWorkItem.UIExtensionSites.RegisterSite(“PluginsMenuBar”, adapter);

}

NOTE: This code will add a new menu item in the main menu strip of the application, with the “Plugins” text, and then add a UIElementAdapter for the DropDownItems colletion of the Plugins menu item, which will be available throughout the whole application.

Finally, right-click the CABPluginApplication project and select Set as StartUp Project.

Once you have done all these steps, just save, compile and run the project. You will see the same “Hello World!” application plus the “Plugins” menu item and, now, in an application context running on CAB! :)

mainApplicationOnCAB

Figure. Outcome: The MainApplication running on CAB.

Next Steps

In the next part, you will create the Message plug-in and get it to work with the CAB plug-in system in a loosely coupled way.

Next Page »