Delphi WPF Sample

The Delphi WPF sample demonstrates the interaction between a Delphi host and a WPF plugin. This sample shows how to load and show a WPF plugin inside a Delphi host, and how to use custom interfaces to establish communication between host and plugin.

To build this sample, you will require both the Delphi and .NET part of Hydra.

Examine the Plugin

The Visualizer plugin is a WPF plugin that shows a custom bar chart-like graph. If you need more information on how to create a WPF plugin, please refer to this article.

Let's take a look at the plugin declaration:

[Plugin, VisualPluginAttribute, NeedsManagedWrapper(typeof(VisualizerWrapper))]
public partial class Plugin : RemObjects.Hydra.WPF.VisualPlugin, IVisualizerControl

The first two attributes, PluginAttribute and VisualPluginAttribute, are used to indicate that the class is a plugin, and they're also used by the host application to read plugin meta-data. You can use these attributes to set up additional information about the plugin.

The NeedsManagedWrapperAttribute defines a wrapper class that will be used to perform communication between plugin and host, we will discuss this later in this article.

As you can see, the plugin implements an interface, so let's take a look at it.

IVisualizerControl Interface

We won't dig deep into details of custom interface usage (please refer to the article Passing interfaces between Host and Plugins), but we will take a brief look at what it is and how it is used.

IVisualizerControl is located in the Interfaces.cs file and represents a custom interface that will be used by the host application to control plugin behavior.

[Guid("8032a51c-5961-41f5-9582-c77d98ea4d93")]
public interface IVisualizerControl : IHYCrossPlatformInterface
{
  bool BarsVisible { get; set; }
  void Randomize();
  void Sinus(Double aRange);
}

As you can see, IVisualizerControl is inherited from the IHYCrossPlatformInterface and has a Guid attribute. This is a general requirement for all cross-platform interfaces in Hydra.

This interface is implemented by our class and provides a method that controls how the chart is displayed.

Wrapper

In the plugin declaration we saw a NeedsManagedWrapperAttribute attribute; it defines that the plugin requires an additional wrapper class to be able to use custom interfaces. We need this additional class because of WPF limitations that block COM interop inside WPF classes.

So when we have both plugin and interface defined, we need to create a wrapper class; the wrapper is placed in a Wrapper.cs file and looks like this:

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

    #region IVisualizerControl Members
    public bool BarsVisible
    {
      get
      {
        return PluginInstance.BarsVisible;
      }
      set
      {
        PluginInstance.BarsVisible = value;
      }
    }

    public void Randomize()
    {
      PluginInstance.Randomize();
    }

    public void Sinus(Double aRange)
    {
      PluginInstance.Sinus(aRange);
    }
    #endregion
  }

As you can see, it simply forwards calls to the plugin instance (this instance is set automatically by the host application).

Conclusion

Our sample plugin consists of three major parts:

  • Interface - The IVisualizerControl interface contains the declaration of methods and properties. This interface will be exposed to the host application and used to control plugin behavior.
  • Wrapper - The VisualizerWrapper class implements the IVisualizerControl interface and forwards the calls to an instance of the plugin.
  • Plugin - The WPF visual plugin that shows the bar graph. This class connects all the parts. It provides the actual implementation of the IVisualizerControl interface and defines a wrapper class that must be used by the host application.

Now let's take a look at the host application.

Examine the Host

The Visualizer Host is a Delphi application that is able to show our WPF plugin and control its behavior. For more information on how to create a Delphi VCL host application, please refer to this article.

Let's take a look at the major parts:

type
  TMainForm = class(TForm)
    HYModuleManager1: THYModuleManager;
    [...]
  private
    fVisualizer: IHYVCLVisualPlugin;
    [...]
  end;

As you can see, the main form is a regular Delphi VCL form, and can be used in the exact same way as any regular form. We have two key items here:

First we need to load our plugin module, this is done in the form OnCreate event handle:

procedure TMainForm.FormCreate(Sender: TObject);
begin
  HYModuleManager1.LoadModule(ExtractFilePath(Application.ExeName)+'Visualizer.dll');
  HYModuleManager1.CreateVisualPlugin('Visualizer.Plugin', fVisualizer, pnl_Host);
  [...]
end;
  • HYModuleManager1.LoadModule - This method allows you to load a plugin module with a specified path. Module manager will automatically detect the type of the module and will use the appropriate method. Module manager also provides a couple of methods for module loading, such as loading from a list of file names, or loading using a search pattern.
  • HYModuleManager1.CreateVisualPlugin - This method will create an instance of the plugin with the specified name and assign this instance to the fVisualizer variable that we defined above. This method can also show plugin content in a container, which, in this example, is represented by pnl_Host.

Now that we have a loaded module, we can control its behavior, for example tell our plugin to draw a sinus graph:

procedure TMainForm.tb_SinusChange(Sender: TObject);
begin
  if not assigned(fVisualizer) then exit;
  if rb_Sinus.Checked then begin
    with (fVisualizer as IVisualizerControl) do begin
      cb_ShowBars.Checked := true;
      Sinus(tb_Sinus.Position);
    end;
  end;
end;

Here we are casting an instance of the plugin to our own custom interface, IVisualizerControl, and calling its method Sinus(tb_Sinus.Position).

Imported Interface

As you can see, on the Delphi side we are using the interface that we defined in the .NET plugin. To be able to use this interface we need to convert it from C# to Delphi. You don't need to do this manually, as Hydra provides an importer tool that automatically converts the interface from .NET to Delphi and vice versa.

The imported interface is placed in the Visualizer_Import.pas file, and looks like this:

  IVisualizerControl = interface(IHYCrossPlatformInterface)
  ['{8032a51c-5961-41f5-9582-c77d98ea4d93}']
    function get_BarsVisible: WordBool; safecall;
    procedure set_BarsVisible(const value: WordBool); safecall;
    procedure Randomize; safecall;
    procedure Sinus(const aRange: Double); safecall;
    property BarsVisible: WordBool read get_BarsVisible write set_BarsVisible;
  end;

So by casting the plugin instance to this interface, we can get access to the methods exposed by the plugin.

Putting It All Together

Now we've examined all the major parts of the sample, and can now build both plugin and host. And after we launch the sample, we should get the following result:

Concepts Covered