Archive for the 'Composite Application Block' Category

Una mirada cercana al SmartPartPlaceholder

Ya hace un tiempo que venía esbozando la idea de postear sobre el SmartPartPlaceholder. Es un control útil, pero es otro concepto acerca del cual tenemos poca información. Así que decidí tomarme mi tiempo para ver que se podía hacer con este control, y como eso sucedía.

Basicamente, la forma en que podemos usar el placeholder es bastante simple:

Agregamos el placeholder en una vista o formulario y le ponemos algun ID a la propiedad SmartPartName. Luego, nos aseguramos de haber agregado una vista con dicho ID en un WorkItem antes de agregar la vista del placeholder, de modo tal que el placeholder la encuentre y la muestre.

Tan simple como parece, no? :P

Pero, como sucede eso? Eso es todo lo que podemos hacer con el placeholeder? Por qué la vista con el placeholder está encapsulada en un WorkItem en el SmartPart Quickstart?

Todas estas preguntas tienen una respuesta, y de eso tratará este artículo.

Los Actores

Primero, comenzaré describiendo las diferentes clases que trabajan juntas para lograr "la mágia detrás de escenas".

La interfaz ISmartPartPlaceholder

SmartPartPlaceholder-SPPHInterface

La interfaz ISmartPartPlaceholder contiene solo dos propiedades que son implementadas por la clase del control SmartPartPlaceholder. Estas propiedades son:

  • SmartPartName. Esta propiedad obtiene o establece el nombre por defecto de la SmartPart contenida en el WorkItem actual.
  • SmartPart. Esta propiedad obtiene o establec la SmartPart contenida por el placeholder.

La clase SmartPartPlaceholder

SmartPartPlaceholder-SPPHClass

Aquí está el personaje principal! La clase que tiene el papel principal en la obra de este artículo! :P

Afortunadamente, no es una clase compleja. Consiste de un constructor, un método que dibuja el borde en tiempo de diseño, las dos propiedades implementadas de la interfaz  ISmartPartPlaceholder, y un método que dispara un evento cuando la SmartPart se muestra.

Entonces, veámoslos en detalle:

  • El constructor del SmartPartPlaceholder establece el estilo del placeholder para soportar color de fondo transparente. Luego, establece el fondo del placeholder en transparente.
  • El método protegido OnPaint(PaintEventArgs e) dibuja el rectángulo intermintente que forma el borde del SmartPartPlaceholder en tiempo de diseño.
  • La propiedad SmartPartName es usada por la estrategia ControlSmartPartStrategy para obtener la SmartPart (cuyo ID concuerda con la propiedad SmartPartName) del WorkItem actual para mostrarla en el placeholder. NOTA: El valor de esta propiedad no puede ser nulo, de lo contrario se lanzará una excepción.
  • La propiedad SmartPart muestra la SmartPart, que obtiene como valor, en el placeholder. Verifica que el valor establecido no sea nulo y que sea del tipo Control(de lo contrario se lanzará una excepción). Una vez hecho ésto, castea el valor a Control, establece su propiedad Dock en DockStyle.Fill y llama a su método Show(). Luego, la colección Controls del placeholder es vaciada y la SmartPart casteada es agregada a esta colección. Finalmente, se llama al método OnSmartPartShown, enviando la SmartPart como argumento.
  • El método privado OnSmartPartShown recibe un objetco como argumento (en efecto, la SmartPart que recién ha sido mostrada en el placeholder). Verifica si el evento SmartPartShown tiene algun manejador (handler) adherido, y, si es así, dispara el evento con el placeholder como sender y una nueva instancia de la clase SmartPartPlaceHolderEventArgs como argumento del evento (esta instancia contiene la SmartPart, en su propiedad SmartPart de solo lectura, que fue enviada como argumento en su constructor).

La clase SmartPartPlaceHolderEventArgs

La clase SmartPartPlaceHolderEventArgs tiene un constructor que recibe un objeto como argumento (la SmartPart recientemente mostrada en el placeholder). Este constructor verifica que el argumento que recibe no sea nulo (de lo contrario se lanzará una excepción) y establece el valor de un miembro privado (llamado “smartPart”) con este argumento. Esta clase tambien contiene una propiedad de solo lectura llamada SmartPart (del tipo object) que puede ser utilizada para obtener la SmartPart que recién fue mostrada en el placeholder.

La clase ControlSmartPartStrategy

La clase ControlSmartPartStrategy es una estrategia de constructor (builder strategy) que se encarga de detectar el SmartPartPlaceholder cuando un control pasa a través del pipeline de ObjectBuilder (NOTA: la estrategia también busca Workspaces y SmartParts). Una vez que se detecta el placeholder, la estrategia verifica su propiedad SmartPartName, de modo tal que obtenga la SmartPart, cuyo ID coincide con la propiedad SmartPartName, de la colección Items del WorkItem actual. Habiendo obtenido la SmartPart, establece el valor de la propiedad SmartPart del placeholder con el valor obtenido. Si el valor obtenido es nulo, no se establece el valor de la propiedad SmartPart; es por ésto que ninguna excepción se dispará cuando no se encuentra ninguna SmartPart. La estrategia también agrega el placeholder a la coleción Items del WorkItem actual, con el nombre del placeholder como su ID (si el placeholder no tiene nombre, se establece una GUID en su lugar).

Aquí está el código del método ReplaceIfPlaceHolder(WorkItem workItem, Control control) que realiza lo que se descrivió anteriormente:

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;

    }

}

La Obra

El siguiente diagrama de secuencia muestra las interacciones descriptas en la sección “Los Actores”. Por razones de simplicidad, algunas intreacciones están comentadas como notas.

smartpartplaceholder-interactions-esp

Encapsulando placeholders en WorkItems?

Como se mostró en la sección “Los Actores”, la clase ControlSmartPartStrategy agregaba el placeholder al WorkItem actual con su nombre como ID. Por ende, si uno agrega dos instancias de la vista que contiene el placeholder al mismo WorkItem, se obtendrá una excepción indicando que ya hay un objeto con dicho ID. Ésta es una de las razones por las que los placeholders son encapsulados en diferentes WorkItems hijo.

La otra razón se relaciona con la vista que será mostrada en el placeholder. Como dije antes, la propiedad SmartPartName del placeholder no puede ser nula (o se obtendrá una excepción). Para lograr que el placeholder trabaje con esta propiedad, se debe agregar una vista con algún ID al WorkItem antes de agregar la vista del placeholder. Pero, si se agregan dos vistas con el mismo ID, se obtendrá una excepción debido a que ya hay un objeto con el mismo ID. Es por ésto que, en el SmartPart Quickstart, la vista mostrada en el placeholder y la vista con placeholder son encapsuladas en un WorkItem hijo.

Sin embargo, este tipo de encapsulamiento es necesario si alguna de estas razones descriptas arriba pueden suceder.

Tip

Una forma útil de utilizar el SmartPartPlaceholder es como un “DeckWorkspace reducido” o un “DeckWorkspace ligero”. Antes de comenzar a describir ésto, permitanme aclarar que un placeholder puede ser agregado en formularios también, no sólo en vistas! Ahora, continuemos. Como dije, el placeholder es agregado al WorkItem con algún ID, por lo tanto, se puede agregar en el ShellForm con un ID y accederlo en los modules. Por ejemplo:

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

De esta forma, se puede mostrar una vista en el placeholder, y cuando se agregue otra, la primera se removerá automáticamente del placeholder.

Para este propósito, debe tener en mente que:

  • Llamar al método Presenter.OnCloseView() no realiza nada, ya que busca un workspace contenedor y llama a su método IWorkspace.Close(object view) y, en estes caso, la vista se encuentra en un SmartPartPlaceholder.
  • Si se desea remover la vista del placeholder, se debe remover la vista explicitamente de la colección Controls.
  • El placeholder no contiene un evento SmartPartClosing.
  • El placeholder no contiene una colección de SmartParts.

Espero que este artículo les haya dejado un concepto más claro de la clase SmartPartPlaceholder.

Como siempre: cualquier feedback es bienvenido! :D

Saludos!

- Nacho

See this post in English.

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.

CAB Quickie – The Plug-in Application: The Main Application (Part 2)

 

In this part, you will add the “main application”. It will be a “Hello World!” application that will act as the program you are about to extend using a plug-in system based on CAB.

First of all, you will create a new Windows Application project.

1. Return to the PluginQuickie blank solution and, in Visual Studio, point to Add on the File menu, and then click New Project.

templateProject

Figure. Creating the MainApplication 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 MainApplication.

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

6. Click OK.

SEMainApplication

Figure. The MainApplication project.

As I wrote in the CAB Quickie – The Plug-in Application: Setting up the development environment post, the bin\Debug folder was created to contain all the built projects, so you need to change the output path of the MainApplication project to this folder. To accomplish this, right-click the MainApplication 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.

projectProperties

Figure. Output path in the Output section located at Build tab in the Project properties. Note: you might not see a relative path until you close the properties window and open it again.

After doing this, you’ll start setting up the MainForm:

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

2. In the Designer, right-click the MainForm and select Properties.

3. Set the following values to the corresponding properties:

 

Property Value
Size 300, 300
StartPosition CenterScreen
Text MainForm

4. Add a Label to the MainForm with the following values set to the corresponding properties:

 

Property Value
(Name) HelloWorldLabel
Font Arial, 20,25pt, style=Bold
Location 61, 115
Text Hello World!

5. Add a MenuStrip to the MainForm and name it HelloWorldMenuStrip.

6. Add a ToolStripMenuItem in the HelloWorldMenuStrip by typing “&File” in the “Type Here” box, and another one inside the fileToolStripMenuItem (this name is given when the item is added) by typing “&Exit” (which will be named exitToolStripMenuItem).

7. Double-click the exitToolStripMenuItem to generate the code for the handler for the click event of the menu item and add the following code line inside:

Application.Exit();

NOTE: This will close the application when clicking on the exitToolStripMenuItem.

8. Once you have done this, in the Designer, right-click the MainForm, select Properties, and set the MainMenuStrip property to the HelloWorldMenuStrip menu strip.

When all these steps are done, just save, compile and run the project. You will see an ordinary Hello World application. So far, we haven’t seen anything about CAB yet.

mainApplication

Figure. Outcome: The MainApplication.

Next Steps

In the next part, you will add the CAB plug-in application (that will be another Windows Application with references to the CAB assemblies).

Next Page »