WPF Visual Plugins

From RemObjects Wiki
Jump to: navigation, search

This is an Article about Hydra


(This page is considered "good" on a technical level, but is pending review for grammar and typos)


The foundation of Hydra's extensibility is based on the concept of plugins. Most basically put, a plugin is simply an object that provides certain functionality that your main application (the host) needs.

One of the most commonly used type of plugins is a visual plugins. A visual plugin is a plugin that provides a user interface to be shown within the host application.

WPF is a modern GUI rendering system that utilizes DirectX and provides an ability to create rich user interfaces with 2D/3D rendering, vector graphics, runtime animation, and much more.

In this article we will describe how to create a new WPF visual plugin, and talk about what features it provides and how they can be used.

Contents

Getting Started

Before we start to create our first plugin, let us describe a whole Hydra plugin project.

The Hydra plugin project consist of three parts:

  • Plugin Module - is a Class Library project that acts as a container for a module controller and can hold a set of plugins.
  • Module Controller - this is an entry point of a module, module controller supply's host with information about stored plugins and provides a methods that allows host to instantiate and work with plugins.
  • Plugins - is a special classes that stored inside plugin module.

Now we can describe how to create and setup a Hydra plugin project and add a visual WPF plugin to it.

Working with Wizards

First we need to create a new module, to do this select File -> New -> Project in the Visual Studio menu, choose language you need (we provide templates for C#, VB.NET and Oxygene for .NET) then in the RemObjects Hydra category choose Plugin Module (WPF):

Visual Plugins WPF 01.png

In this dialog you also need to enter project name and destination folder.

After you press OK button, wizard will create a new plugin module and add a module controller to the project. We will describe how to setup module controller later in this article.

Now when we have a plugin module, we can add a plugin to it, select Project -> Add New Item from the VS menu and in the appeared dialog select Visual Plugin (WPF) item:

Visual Plugins WPF 02.png

Select a name for the new plugin and press Add button. And we are done, wizard will add a new visual plugin to the project.

Plugin module can contain as much plugins as you want, also there is no problem to have visual and non-visual plugins in the same project.

Review the resulting project

Now let us review the resulting project. We have a three part:

Plugin Module

Plugin module is a Class Library project that will produce an assembly file that can be used by any of the supported host platforms.

Wizard automatically adds reference to the RemObject.Hydra.dll assembly that contains Hydra core classes, if you will use this plugin module with Delphi (VCL or FireMonkey) hosts, you will need this file to be present in the same folder with plugin, so you may want to tune your project so it will automatically copy core dll into the destination folder. To do so select Properties of the RemObjects.Hydra.dll:

Visual Plugins WinForms 03.png

And in the appeared Properties page set CopyLocal to true:

Visual Plugins WinForms 04.png

Also in the AssemblyInfo.cs we set following assembly attribute:

[assembly: ComVisible(true)]

Please do not change this attribute, it will allow host application to access to the interfaces exposed by the plugin.

Module Controller

Module Controller is a class that acts as an entry point of a plugin module. Host will instantiate this class on module load to get the information about plugins.

Wizard generates following code for the module controller:

[ModuleController]
  public partial class TheModuleController : ModuleController
  {
    public TheModuleController()
    {
      InitializeComponent();
    }
  }

Module controller is inherited from the ModuleController that itself inherits the System.ComponentModule.Component. By default controller holds two image lists that can be shared with the host application.

Also please note the ModuleControllerAttribute, it allows you to set additional meta-data, for example:

[ModuleController(Name = "Controller", Description = "My module controller", UserData = "Data")]
  public partial class TheModuleController : ModuleController
  [..]

Visual Plugin

Visual Plugin is the control that you will use to embed into host application. It inherited from the VisualPlugin class that allows it to be used in the cross platform environment, this control is inherited from the System.Windows.Controls.UserControl, so it provides the same design surface as regular UserControl does.

Code that was generated by the wizard looks like this:

[Plugin, VisualPlugin]
  public partial class Plugin1 : VisualPlugin
  {
    public Plugin1()
    {
      InitializeComponent();
    }
  }

Please do not remove two attributes PluginAttribute and VisualPluginAttribute they used by the host application to indicate that class is indeed a plugin and also used to read plugin meta-data.

Like in the module controller you can set the meta-data manually:

[Plugin(Name = "SamplePlugin", Description = "This is sample plugin", UserData = "Data"), VisualPlugin]
  public partial class Plugin1 : VisualPlugin
  [..]

Using the plugin

By now you will have a complete project that without any additional work can be loaded by all supported host platforms.

Both module controller and visual plugin provides a set of internal methods that is used by the Hydra framework to perform s specific tasks, however they also provides some useful methods/properties which can be used in your own application.

Below we will describe a most common way to use module controller and plugin, but first let us make a note. One of the major part of the Hydra framework is a custom interfaces. Custom interfaces is a user defined interfaces that can be used in the cross-platform environment (for example they can be shared between .NET plugin and a Delphi host application) to receive access to a data or to call host or plugin methods. When we will talk about things like "accessing host methods" we will refer to the custom interfaces, but since this is large topic we can't cover it in this article, so if you feel like you need to use them, please take a look at these two articles:

Before we start to describe visual plugin itself let us show how you can use a module controller.

How to use Module Controller

While the main purpose of the module controller is to provide information about a plugins to a host, it can also be useful with some specific task.

Host will instantiate module controller only once on a module load, so it can be used to initialize some global data or allow host to gain access to a global methods via custom interfaces.

By default module controller holds two ImageList controls, that is assigned to SmallImages and LargeImagess properties. These image lists will be passed to a host application, so you can use them to store shared images.

Also ModuleController has an event called HostChanged this is an important event if you need to be able to access host methods or data. Because controller is initialized on module load it doesn't have access to a Host property at the time when its constructor is executed. When host assigns its reference to a module controller, the HostChanged event is fired and so you can safely get access to host. One thing that you need to consider is that this event can be called with null host reference (when host unloads a module) so you need to check this before accessing host members.

The last thing that is available to the module controller is the Host property, this property holds the reference to the host object, and can be used to access to host methods or data.

How to use Visual Plugin

WPF visual plugin is derived from the UserControl and provides exactly the same design surface. So you can use them in a same way as you do with regular user controls. Biggest difference is of course that you can load and embed these plugins into any supported host applications.

Like module controller VisualPlugin provides access to the Host property and HostChanged event, so everything that was said above for module controller also applies to the visual plugin.

One important topic when working with WPF plugins is tab key navigation, unlike other supported plugins it requires some additional work. Unfortunately WPF doesn't provides access to they internal methods to deal with tab order, currently it only allows to work with TabIndex to determine proper tab order. But most controls that supports TabIndex will set it to the default value that doesn't allows us to properly detect tab order of a control. So in order to add proper tab support you will need to manually adjust controls TabIndex property.

The other important thing is how WPF plugins works with custom interfaces, by default WPF block COM interop in their UserControl and this limits you to only access to host methods and properties but won't allow you to expose plugin functionality to a host. So in order to deal with this problem we have a special wrapper class, but this will require some work, for example, we need our plugin to implement following interface:

[Guid("8032a51c-5961-41f5-9582-c77d98ea4d93")]
public interface ICustomPlugin: IHYCrossPlatformInterface
{
  string UserData { get; set; }
}

To do so, first we need to implement this interface in a plugin:

[Plugin, VisualPlugin, NeedsManagedWrapper(typeof(CustomPluginWrapper))]
public partial class Plugin : VisualPlugin, ICustomPlugin
{
 [..]
    public string UserData
    {
      get { return MyCustomData; }
      set { MyCustomData = value; }      
    } 
}

Please note that we not only implemented interface member but also added a new attribute called NeedsManagedWrapper that point to a special wrapper class. Host will access to this wrapper instead of direct access to plugin members, let us take a look how this wrapper if deined:

public class CustomPluginWrapper: VisualPluginWrapper, ICustomPlugin
  {
    private new Plugin PluginInstance { get { return base.PluginInstance as Plugin; } }

    public string UserData
    {
      get { return PluginInstance.UserData; }
      set { PluginInstance.UserData= value; }      
    }
  }

As you can see, only this that it does is access to actual implementation of the property in the plugin, this allows us to overcome WPF limitations.

Other Articles

Product Articles Data Abstract RemObjects SDK  


Product: RemObjects Hydra
Current version: Hydra 4.0

GlossaryArchitectureArticlesLibrarySamples