OberonPlace.com Forums  

Go Back   OberonPlace.com Forums > Developer Forums > VBA > CorelDRAW/Corel DESIGNER VBA

Reply
 
Thread Tools Search this Thread Display Modes
  #1  
Old 20-11-2006, 08:54
shelbym's Avatar
shelbym shelbym is offline
Senior Member
 
Join Date: Nov 2002
Location: Cheyenne, WY
Posts: 1,769
Blog Entries: 9
Send a message via ICQ to shelbym Send a message via AIM to shelbym Send a message via MSN to shelbym Send a message via Yahoo to shelbym
Default CorelDRAW Plugin

On Unleash.com they have a product called EZ Metrics were they state it is: "EZ Metrics 3 is the first ever plug-in for CorelDRAW and works in CorelDRAW X3!"

Anyone know how to make a "plugin" for CorelDRAW. What is the advantages / disadantages a plugin has? The comment just peaked my interest.

Thanks,

Shelby
Reply With Quote
  #2  
Old 21-11-2006, 06:57
Lev
Guest
 
Posts: n/a
Default

The 1-st plugin was Alex's sample plugin (add-in) - CropMarks
The 2-nd plugin is PlotCalc
There could be more plugins. So the statement that "EZ Metrics 3 is the first ever plug-in for CorelDRAW..." is not true.

how to make a "plugin"
or search forum for "addin" word.
Reply With Quote
  #3  
Old 21-11-2006, 20:52
Alex's Avatar
Alex Alex is offline
Administrator
 
Join Date: Nov 2002
Posts: 1,940
Blog Entries: 4
Default

Well, frankly the statement is correct. As of CorelDRAW X3 you can create DLL plugins which are loaded by the application at startup. The plugins should have extension of .cpg and should reside in the Draw\Plugins subfolder (e.g. C:\Program Files\Corel\CorelDRAW Graphics Suite 13\Draw\Plugins). The .CPG file is a DLL that has a few exported functions and implementing a standard communication interface CorelDRAW uses to load and initialize the plugin. From then on, the plugin can use the standard automation interface (object model) to communicate with CorelDRAW as any macro or COM-Addin would...

That's in a nutshell. When I have some free time, I will post more information about how to create a plugin for CorelDRAW with a sample C++ project.

As to advantages, since it's a DLL you would normally create with, say, C++, you are not bound by limitations of Visual Basic. You can create the whole solution in your plugin module, including functionality, user interface (with resources such as localizable strings, icons, bitmaps, etc). For some overly paranoid developers, it also provides more piece of mind that their solution would be more difficult to reverse-engineer, since it is a regular compiled exectuable binary.

However in my mind, the biggest advantage of the plugin is that it doesn't really require VBA itself. Even if you don't have VBA installed, plugins will still function, while macros won't...

Last edited by Alex; 21-11-2006 at 20:56.
Reply With Quote
  #4  
Old 21-11-2006, 21:05
shelbym's Avatar
shelbym shelbym is offline
Senior Member
 
Join Date: Nov 2002
Location: Cheyenne, WY
Posts: 1,769
Blog Entries: 9
Send a message via ICQ to shelbym Send a message via AIM to shelbym Send a message via MSN to shelbym Send a message via Yahoo to shelbym
Default Plug-In

Wow, sounds cool. Look forward to the C++ Example when you get the time. Good thing I am taking C++ yet again next semester. At least this will give me some motivation to use C++. Thanks again Alex,

Shelby
Reply With Quote
  #5  
Old 06-12-2006, 16:33
jemmyell jemmyell is offline
Senior Member
 
Join Date: Jan 2005
Location: Orange County, California, USA, Earth, Solar System, Milky Way Galaxy
Posts: 157
Default

Quote:
Originally Posted by Alex
Well, frankly the statement is correct. As of CorelDRAW X3 you can create DLL plugins which are loaded by the application at startup. The plugins should have extension of .cpg and should reside in the Draw\Plugins subfolder (e.g. C:\Program Files\Corel\CorelDRAW Graphics Suite 13\Draw\Plugins). The .CPG file is a DLL that has a few exported functions and implementing a standard communication interface CorelDRAW uses to load and initialize the plugin. From then on, the plugin can use the standard automation interface (object model) to communicate with CorelDRAW as any macro or COM-Addin would...

That's in a nutshell. When I have some free time, I will post more information about how to create a plugin for CorelDRAW with a sample C++ project.

As to advantages, since it's a DLL you would normally create with, say, C++, you are not bound by limitations of Visual Basic. You can create the whole solution in your plugin module, including functionality, user interface (with resources such as localizable strings, icons, bitmaps, etc). For some overly paranoid developers, it also provides more piece of mind that their solution would be more difficult to reverse-engineer, since it is a regular compiled exectuable binary.

However in my mind, the biggest advantage of the plugin is that it doesn't really require VBA itself. Even if you don't have VBA installed, plugins will still function, while macros won't...
Alex, how about a simple sample plugin project for a Christmas present to the C++ programmers on the group? I assume you can use the Draw object model just the same as from a DLL called from VBA?

-James
__________________
-James Leonard
CNC Inlay Guy - www.CorelDRAWCadCam.com
Reply With Quote
  #6  
Old 06-12-2006, 21:02
Alex's Avatar
Alex Alex is offline
Administrator
 
Join Date: Nov 2002
Posts: 1,940
Blog Entries: 4
Default

Ok, thanks for the reminder. Let me come up with something. Stay tuned for a holiday present....

Any suggestions on what the plugin should do? Any wild ideas?
Reply With Quote
  #7  
Old 06-12-2006, 22:42
Alex's Avatar
Alex Alex is offline
Administrator
 
Join Date: Nov 2002
Posts: 1,940
Blog Entries: 4
Default

Ok, here is one. This is a very simple plugin that replaces the standard welcome screen (the one with the New/RecentlyUsed/Open/New From Template/etc buttons) and presents a simple dialog with two buttons - one to create a new document and one to open a last document.

The main file of this Visual Studio solution is WelcomeScreen.cpp. Which starts as any other C++ file with a few #include statement to include the precompiled header and resource IDs for the dialog and buttons:

Code:
#include "stdafx.h"
#include "resource.h"
Then we need to import type library information for CorelDRAW. In fact, all we need is the intermediate library - VGCore which is common between CorelDRAW and CorelDESIGNER (this way, you can use the same plugin in CorelDRAW X3 and Corel DESIGNER 12). Normally you would import the typelibrary by its GUID like so:

Code:
#import "libid:95E23C91-BC5A-49F3-8CD1-1FC515597048" version("d.0") \
      rename("GetCommandLine", "VGGetCommandLine") \
      rename("CopyFile", "VGCore") \
      rename("FindWindow", "VGFindWindow")
(I have to rename some of CorelDRAW object model methods such as CopyFile which collide with standard C++ function definitions from Windows API).

However it appears there is a bug in Visual Studio 2003 which causes the compiler to hang when trying to compile this statement (seems to work fine with VS 2005 though). So, for VS2003 you will need to import the TLB file directly. You either need to provide the full path to the TLB on your machine (normally that would be "C:\Program Files\Corel\CorelDRAW Graphics Suite 13\Programs\VGCoreAuto.tlb"), or better yet just copy the TLB file from CorelDRAW's Program folder into your VS2003 project folder (where the rest of .CPP and .H files are). This way you will be able to omit the path to the file:

Code:
#import "VGCoreAuto.tlb" \
      rename("GetCommandLine", "VGGetCommandLine") \
      rename("CopyFile", "VGCore") \
      rename("FindWindow", "VGFindWindow")
Then you need to provide the standard entry point function in your DLL, the DllMain as for any other standard Windows DLL:

Code:
static HINSTANCE g_hResource = NULL;

BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
  if(fdwReason == DLL_PROCESS_ATTACH)
  {
    g_hResource = (HINSTANCE)hinstDLL;
  }
  return TRUE;
}
Here I'm just preserving the handle to the DLL module in a global variable so I can access the resources embedded into it later on when I need to show the dialog.

Now the key moment. You need to provide another exported function called AttachPlugin which will be called by CorelDRAW when the plugin is loaded. This function should have the following prototype:

Code:
extern "C" __declspec(dllexport) DWORD APIENTRY AttachPlugin(VGCore::IVGAppPlugin **ppIPlugin)
The function should return a pointer to IVGAppPlugin interface of an object you create to handle all plugin requests. CorelDRAW will use this interface to communicate with your plugin. The interface itself is described in the VGCore type library, so you can look it up. Also AttachPlugin should return the version of plugin you are providing. Currently only version 1.0 is supported, so you should return a value of 0x100.

Here is generally how you implement this function:

Code:
extern "C" __declspec(dllexport) DWORD APIENTRY AttachPlugin(VGCore::IVGAppPlugin **ppIPlugin)
{
  *ppIPlugin = new CWelcomeScreenPlugin;
  return 0x100;
}
The CWelcomeScreenPlugin is a class that implements IVGAppPlugin interface and provide the actual plugin functionality.

Here is how this class is defined:

Code:
class CWelcomeScreenPlugin : public VGCore::IVGAppPlugin
{
private:
  VGCore::IVGApplication *m_pApp;
  volatile ULONG m_ulRefCount;
  long m_lCookie;

  static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
  void OnAppStart();

public:
  CWelcomeScreenPlugin();

// IUnknown
public:
  STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject);
  STDMETHOD_(ULONG, AddRef)(void) { return ++m_ulRefCount; }
  STDMETHOD_(ULONG, Release)(void) 
  {
    ULONG ulCount = --m_ulRefCount; 
    if(ulCount == 0)
    {
      delete this;
    }
    return ulCount;
  }

// IDispatch
public:
  STDMETHOD(GetTypeInfoCount)(UINT *pctinfo) { return E_NOTIMPL; }
  STDMETHOD(GetTypeInfo)(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; }
  STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { return E_NOTIMPL; }
  STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);

// IVGAppPlugin
public:
  STDMETHOD(raw_OnLoad)(VGCore::IVGApplication *Application);
  STDMETHOD(raw_StartSession)();
  STDMETHOD(raw_StopSession)();
  STDMETHOD(raw_OnUnload)();
};
Apart from the standard implementation to the IUnknown and IDispatch interfaces we have an implementation of the four functions of IVGAppPlugin interface which CorelDRAW calls when the plugin is first loaded, unloaded, etc.

The load sequence is as follows:

1. CorelDRAW loads all internal components and initializes them
2. It proceeds to load all the plugins one by one.
3. For each plugin loaded successfully, it calls the OnLoad method of IVGAppPlugin inteface. In this methods you should initialize some internals of your plugin (allocating memory, opening files, etc). If everything is Ok, return S_OK. Any E_... failure returned causes your plugin to be unload and no further communication occurs..
4. After each plugin's OnLoad is called, the application is ready to transfer the input to the user. Just before this is done, each plugin's StartSession is called. This is the time you can modify the UI (add buttons to toolbars, change menu items if you need, etc). This is also the time when you need to register any application event handlers.
5. After that the user has contol over the application. Your plugin will be called in response to various events you set up handlers for.
6. Before shutting down the application, CorelDRAW first calls StopSession in each plugin. You need to unregister any event handlers, etc. Note that after StopSession is called, the session could be restarted and StartSession could be called again (in case shutdown sequence has been interrupted for whatever reason). So do not assume that the plugin is really being unloaded when you receive StopSession.
7. After this, OnUnload is called to finally free any allocated resources and the plugin is unloaded from the application address space.

Here is the implementation of those 4 methods in Welcome Screen plugin:

Code:
STDMETHODIMP CWelcomeScreenPlugin::raw_OnLoad(VGCore::IVGApplication *Application)
{
  m_pApp = Application;
  if(m_pApp)
  {
    m_pApp->AddRef();
  }
  return S_OK;
}
In OnLoad, we just store the reference to the main Application object which we will use later to communicate with CorelDRAW (this is the same Application object as in VBA).

In Start session we will add an event handler for the application events. For this, you can use standard COM connection point mechanism or as in my case, you can use Application.AdviseEvents method and pass in the IDispatch implementation of an event handler. In my case, I use the same CWelcomeScreenPlugin object to implement both IVGAppPlugin interface and the even sink through its IDispatch:

Code:
STDMETHODIMP CWelcomeScreenPlugin::raw_StartSession()
{
  try
  {
    m_lCookie = m_pApp->AdviseEvents(this);
  }
  catch(_com_error &e)
  {
    MessageBox(NULL, e.Description(), "Error", MB_ICONSTOP);
  }
  return S_OK;
}
When an application even occurs, IDispatch::Invoke will be called with method ID corresponding to the event method ID. I'm interested in Application.Start even which has ID of 18 (0x0012) and has no parameters. Here is how I implement the Application.Start even handler:

Code:
STDMETHODIMP CWelcomeScreenPlugin::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
  switch(dispIdMember)
  {
    case 0x0012: //DISPID_APP_START
      OnAppStart();
      break;
  }
  return S_OK;
}
So, it just calls OnAppStart() method when the event occurs.

The StopSession method implementation just removes the even handler:

Code:
STDMETHODIMP CWelcomeScreenPlugin::raw_StopSession()
{
  try
  {
    m_pApp->UnadviseEvents(m_lCookie);
  }
  catch(_com_error &e)
  {
    MessageBox(NULL, e.Description(), "Error", MB_ICONSTOP);
  }
  return S_OK;
}
And OnUnload just releases the Application object reference:

Code:
STDMETHODIMP CWelcomeScreenPlugin::raw_OnUnload()
{
  if(m_pApp)
  {
    m_pApp->Release();
    m_pApp = NULL;
  }
  return S_OK;
}
When Application.Start event comes in, I disable the standard welcome screen by setting Application.StartupMode property to cdrStartupDoNothing.

Then I show my own dialog. And when "New Document" button is pressed I create a new document. If "Last Document" button is pressed, I load the last used document by getting its name from Application.RecentFiles collection.

Here is the full implementation of OnAppStartup method:

Code:
void CWelcomeScreenPlugin::OnAppStart()
{
  try
  {
    m_pApp->StartupMode = VGCore::cdrStartupDoNothing;
  // To avoid 64 bit portability warning, store the long handle value into an INT_PTR
  // before casting it to HWND:
  INT_PTR nHandle = m_pApp->AppWindow->Handle;
  HWND hAppWnd = reinterpret_cast<HWND>(nHandle);
    INT_PTR nRet = DialogBoxParam(g_hResource, MAKEINTRESOURCE(IDD_WELCOME), hAppWnd, DlgProc, (LPARAM)this);
    switch(nRet)
    {
    case IDC_NEWDOC:
      m_pApp->CreateDocument();
      break;

    case IDC_LASTDOC:
      if(m_pApp->RecentFiles->Count > 0)
      {
        m_pApp->OpenDocument(m_pApp->RecentFiles->Item[1]->FullName, 0);
      }
      else
      {
        MessageBox(NULL, "No documents were editied yet.", "Error", MB_ICONSTOP);
      }
      break;
    }
  }
  catch(_com_error &e)
  {
    MessageBox(NULL, e.Description(), "Error", MB_ICONSTOP);
  }
}
That's pretty much it. I have omitted a few non-essential functions here. You can find the full Visual Studio solution files in the attached file WelcomeScreen_Src.zip.

A compiled version of the plugin can be found in WelcomeScreen_cpg.zip

Just a little note, when you compile your project, rename the produced DLL and give it another extension - ".cpg". Then copy this module into "C:\Program Files\Corel\CorelDRAW Graphics Suite 13\Draw\Plugins" folder and start CorelDRAW. You should see the custom welcome screen like in the attached picture.
Attached Images
 
Attached Files
File Type: zip WelcomeScreen_Src.zip (5.3 KB, 1598 views)
File Type: zip WelcomeScreen_cpg.zip (28.2 KB, 1282 views)
Reply With Quote
  #8  
Old 06-12-2006, 23:54
Alex's Avatar
Alex Alex is offline
Administrator
 
Join Date: Nov 2002
Posts: 1,940
Blog Entries: 4
Default

Here is another one that shows slightly different side of the thing. This plugin has no dialogs or any resources of that matter. However it adds a new button to a toolbar and then responds to the mouse click. Another feature of the plugin is that it responds to the selection changed event and the button gets enabled or disabled depending on whether something is selected in the document. So, when there is no selection, the toolbar button will be disabled.

The action of the command isn't anything to brag about - it just removes the fill from the selected objects. However it still should be useful to illustrate how to create plugins of this sort.

The general framework of the plugin is the same. The DllMain function which in this case doesn't do anything at all. The AttachPlugin which creates an instance of plugin object class which implements the IVGAppPlugin interface. The plugin class implements the four methods of that interface. And event sink is established on the Application object to listen to application events.

OnLoad and OnUnload methods are exactly the same as in the previous example. StartSession and Stop session have a few differences:

StartSession adds a new plugin command called "ClearFill" by using Application.AddPluginCommand. Once this is done, a new command button can be added to toolbars/menus. The plugin adds a button to the end of the standard toolbar and associates a new icon with the command. It also adds an event handler to the application object's events:

Code:
STDMETHODIMP CVGAppPlugin::raw_StartSession()
{
  try
  {
    m_pApp->AddPluginCommand(_bstr_t("ClearFill"), _bstr_t("Clear Fill"), _bstr_t("Clears fill from selected objects"));
    VGCore::CommandBarControlPtr ctl = m_pApp->CommandBars->Item[_bstr_t("Standard")]->Controls->AddCustomButton(VGCore::cdrCmdCategoryPlugins, _bstr_t("ClearFill"), 0, VARIANT_TRUE);
    _bstr_t bstrPath(m_pApp->Path + _bstr_t("Plugins\\ClearFill.bmp"));
    ctl->SetCustomIcon(bstrPath);
    m_lCookie = m_pApp->AdviseEvents(this);
  }
  catch(_com_error &e)
  {
    MessageBox(NULL, e.Description(), _T("Error"), MB_ICONSTOP);
  }
  return S_OK;
}
The StopSession function just unregisters the event handler and removes the custom plugin command:

Code:
STDMETHODIMP CVGAppPlugin::raw_StopSession()
{
  try
  {
    m_pApp->UnadviseEvents(m_lCookie);
    m_pApp->RemovePluginCommand(_bstr_t("ClearFill"));
  }
  catch(_com_error &e)
  {
    MessageBox(NULL, e.Description(), _T("Error"), MB_ICONSTOP);
  }
  return S_OK;
}
The IDispatch::Invoke method which is used when Application events are fired now handles three different events:

- Application.SelectionChanged() [id: 0x0011]
- Application.OnUpdatePluginCommand(CommandID As String, Enabled As Boolean, Checked As cdrCommandCheckState) [id: 0x0015]
- Application.OnPluginCommand(CommandID As String) [id: 0x0014]

Code:
STDMETHODIMP CVGAppPlugin::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
  switch(dispIdMember)
  {
    case 0x0011: //DISPID_APP_SELCHANGE
      m_bEnabled = CheckSelection();
      break;

    case 0x0014: // DISPID_APP_ONPLUGINCMD
      if(pDispParams != NULL && pDispParams->cArgs == 1)
      {
        _bstr_t strCmd(pDispParams->rgvarg[0].bstrVal);
        if(strCmd == _bstr_t("ClearFill"))
        {
          OnClearFill();
        }
      }
      break;

    case 0x0015: // DISPID_APP_ONPLUGINCMDSTATE
      if(pDispParams != NULL && pDispParams->cArgs == 3)
      {
        _bstr_t strCmd(pDispParams->rgvarg[2].bstrVal);
        if(strCmd == _bstr_t("ClearFill"))
        {
          *pDispParams->rgvarg[1].pboolVal = m_bEnabled ? VARIANT_TRUE : VARIANT_FALSE;
        }
      }
      break;
  }
  return S_OK;
}
When SelectionChanged event is received we analyze the new selection to see if any valid objects are selected. We cache the state of the command being enabled/disabled into m_bEnabled variable.

OnUpdatePluginCommand is called periodically to update the state of the custom command (enabled/disabled, depressed/released, etc). CommandID parameter specified the command ID string we used when we created the plugin command. In our case it will be "ClearFill". We handle this event to enable/disable the button based on the cached value of m_bEnabled variable.

OnPluginCommand is called when the command button is pressed. Again, CommandID would indicate the command id of the button pressed. We should only react to the commands we handle (those that we created ourselves). Any other commands should be ignored and delegated to their respective owners/handers. In our implemenation we just call OnClearFill() method when our button is pressed.

The OnClearFill method is a rather simple code to apply no fill to the current selection:

Code:
void CVGAppPlugin::OnClearFill()
{
  if(CheckSelection())
  {
    m_pApp->ActiveSelection->Fill->ApplyNoFill();
  }
}
That is pretty much it.

As before, you can find attached the full sources of the plugin (this time, it is for Visual Studio 2005) in ClearFill_src.zip. ClearFill_cpg.zip contains the compiled version of the plugin (the ClearFill.cpg file) along with the ClearFill.bmp - the custom icon used for the Clear Fill command button. You should place both the .cpg and .bmp file in the Draw\Plugins folder.

If the plugin is loaded correctly, you should see a button as pictured on the attached image...

Good luck and have fun
Attached Images
 
Attached Files
File Type: zip ClearFill_src.zip (4.4 KB, 1252 views)
File Type: zip ClearFill_cpg.zip (9.5 KB, 1120 views)
Reply With Quote
  #9  
Old 07-12-2006, 10:24
jemmyell jemmyell is offline
Senior Member
 
Join Date: Jan 2005
Location: Orange County, California, USA, Earth, Solar System, Milky Way Galaxy
Posts: 157
Default Thank you so much!

I will load your samples onto my laptop and have at it!

Thanks again, you are truly the driving force for CorelDraw customization!

-James
__________________
-James Leonard
CNC Inlay Guy - www.CorelDRAWCadCam.com
Reply With Quote
  #10  
Old 07-12-2006, 12:00
shelbym's Avatar
shelbym shelbym is offline
Senior Member
 
Join Date: Nov 2002
Location: Cheyenne, WY
Posts: 1,769
Blog Entries: 9
Send a message via ICQ to shelbym Send a message via AIM to shelbym Send a message via MSN to shelbym Send a message via Yahoo to shelbym
Default Plug-In

Alex you give the BEST Christmas presents. THANKS! I am sure it will take me a while to make sense of all this, but really really cool!

Shelby
Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Where to begin with CorelDRAW and VBA shelbym CorelDRAW/Corel DESIGNER VBA 2 03-04-2007 09:15
CorelDRAW 12 .PDF filter keytecstaff General 9 18-05-2006 20:18
How to import a picture (bitmap) from clipboard or stream to CorelDraw? midavik CorelDRAW/Corel DESIGNER VBA 2 08-12-2005 09:48
CorelDRAW 12 SP1 is available Alex CorelDRAW/Corel DESIGNER VBA 1 04-08-2004 01:43
Creating macros for CorelDraw 10 in CorelDraw 11 Rick Randall CorelDRAW/Corel DESIGNER VBA 1 14-03-2003 08:00


All times are GMT -5. The time now is 05:25.


Powered by vBulletin® Version 3.8.1
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.
Copyright © 2011, Oberonplace.com