Using Editor Plugin SDK

PDF-XChange Editor SDK for Developers

Moderators: TrackerSupp-Daniel, Tracker Support, Paul - Tracker Supp, Vasyl-Tracker Dev Team, Chris - Tracker Supp, Sean - Tracker, Ivan - Tracker Software, Tracker Supp-Stefan

Forum rules
DO NOT post your license/serial key, or your activation code - these forums, and all posts within, are public and we will be forced to immediately deactivate your license.

When experiencing some errors, use the IAUX_Inst::FormatHRESULT method to see their description and include it in your post along with the error code.
Post Reply
p0w3r
User
Posts: 3
Joined: Sat Dec 09, 2017 5:16 pm

Using Editor Plugin SDK

Post by p0w3r »

How to add a button to a submenu "Advanced" of the main menuBar?
Sasha - Tracker Dev Team
User
Posts: 5522
Joined: Fri Nov 21, 2014 8:27 am
Contact:

Re: Using Editor Plugin SDK

Post by Sasha - Tracker Dev Team »

Hello p0w3r,

Please search in the Editor SDK forum - there were several topics on how to add a custom/existing command to the needed bar.

Cheers,
Alex
Subscribe at:
https://www.youtube.com/channel/UC-TwAMNi1haxJ1FX3LvB4CQ
p0w3r
User
Posts: 3
Joined: Sat Dec 09, 2017 5:16 pm

Re: Using Editor Plugin SDK

Post by p0w3r »

Hi

Can you give an example of .pvp plugin with a some functions?
How to use the code from FullDemo (https://github.com/tracker-software/PDF ... p/FullDemo) in plugin (https://github.com/tracker-software/PDF ... uginSample)?

Thanks
Sasha - Tracker Dev Team
User
Posts: 5522
Joined: Fri Nov 21, 2014 8:27 am
Contact:

Re: Using Editor Plugin SDK

Post by Sasha - Tracker Dev Team »

Hello p0w3r,

Here's a sample project on this matter:
https://github.com/tracker-software/PDF ... uginSample

Cheers,
Alex
Subscribe at:
https://www.youtube.com/channel/UC-TwAMNi1haxJ1FX3LvB4CQ
p0w3r
User
Posts: 3
Joined: Sat Dec 09, 2017 5:16 pm

Re: Using Editor Plugin SDK

Post by p0w3r »

Hi Alex,
Why am I getting an error "Object reference not set to an instance of an object."?

Code: Select all

public void Setup(PXV_Inst pInstance)
{
	m_Inst = pInstance;
}

public void Init()
{
	//throw new NotImplementedException();
	try
	{
		m_Inst.ActiveMainView.LoadPanesLayout(); 
		// Message: Object reference not set to an instance of an object.
		//StackTrace:    at PluginSample.MyTestPlugin.Setup(PXV_Inst pInstance) in C:\PDFEditorPluginSample - master\PluginSample\Class1.cs:line 55
	}
	catch (Exception ex)
	{

		m_Inst.ShowMsgBox("Message: " + ex.Message + "\nSource: " + ex.Source + "\nTargetSite: " + ex.TargetSite + "\nStackTrace: " + ex.StackTrace + "\nInnerException" + ex.InnerException);
	}
}
Thanks
Sasha - Tracker Dev Team
User
Posts: 5522
Joined: Fri Nov 21, 2014 8:27 am
Contact:

Re: Using Editor Plugin SDK

Post by Sasha - Tracker Dev Team »

Hello ActiveMainView,

Is there an ActiveMainView available (is it a non-null value)? You should check all of that things - plugins can be used without the user interface (IPXV_Inst only) - thus you will need to take that into account.

Cheers,
Alex
Subscribe at:
https://www.youtube.com/channel/UC-TwAMNi1haxJ1FX3LvB4CQ
oknpp
User
Posts: 4
Joined: Wed Feb 10, 2021 6:42 pm

Re: Using Editor Plugin SDK

Post by oknpp »

Hi Support,

I encountered the same issues using the PluginSample.
The Plugin would be really simple: It should just add a button to the ribbon. (Or as last resort: register a custom command that then can be dragged by the user to any ribbon or command bar, from Help -> Customize Toolbars -> Commands).
Every code snippet I found on the forum requires ActiveMainView or MainFrm. The last method that is called when PDF-XChange Editor is started is

Code: Select all

class MyTestPlugin : IPXV_Plugin
{
public void Init()
		{
			var mainFormsCount = m_Inst.MainFrmCount; // always 0
			var mainFrm = m_Inst.ActiveMainFrm; // null
			var mainView = m_Inst.ActiveMainView; // null
		}
}

But there those objects are always null.

I got a non-null MainFrm in m_Inst.DoAsync() after Thread.Sleep(1000) to await loading of the UI. But this can't be the solution?

So my question is:
What is the correct event/callback/way to add Buttons to the ribbon within the scaffold of the PluginSample solution?
How can I add my own command to the Help -> Customize Toolbars -> Commands - Menu?

Thanks and kind regards,
Olli
Sasha - Tracker Dev Team
User
Posts: 5522
Joined: Fri Nov 21, 2014 8:27 am
Contact:

Re: Using Editor Plugin SDK

Post by Sasha - Tracker Dev Team »

Hello oknpp,

The Init method is being called when the MainFrame is not ready yet. Here's a description for all of the interface methods in one batch (by the way - there is a description for them on the help wiki) - all code is C++, but you can easily convert it to the C#:

Code: Select all

	//Unique plugin GUID indentifier
	STDMETHODIMP get__GUID(GUID* pGuid);
	//Plugin name that will be shown in the Editor's plugins list
	STDMETHODIMP get_Name(BSTR* ppName);
	//Copyright information
	STDMETHODIMP get_CopyrightInfo(BSTR* ppCopyrightInfo);
	//Plugin version, that should be increased when new functionality is being added
	STDMETHODIMP get_version(ULONG* pVer);
	//Plugin's developer identifier (obtained when the SDK key is bought)
	STDMETHODIMP get_VendorID(ULONG* pVendorID);
	//The plugin description that will be used in the Editor's plugins array
	STDMETHODIMP get_Description(BSTR* pDescription);
	//Additional information about the usage rights
	STDMETHODIMP get_LegalInfo(BSTR* pLegal);
	//Information about the plugin developer
	STDMETHODIMP get_Publisher(BSTR* pPublisher);
	//Additional flags, that can be used for extended plugin features
	STDMETHODIMP get_Features(ULONG* pFlags);
	//Starting method for plugin initialization - here we get the IPXV_Inst
	STDMETHODIMP Setup(IPXV_Inst* pInstance);
	//Custom extension registration (if needed)
	STDMETHODIMP RegisterExts();
	//Reading the needed extensions that are needed for the plugin
	STDMETHODIMP FinalizeRegistering();
	//Initialization of the plugin with the Editor's UI available (commands, handlers etc)
	STDMETHODIMP Init();
	//Plugin unload
	STDMETHODIMP Unload();
	//Showing the plugin preferences dialog, that can be called from the Editor's plugins list
	STDMETHODIMP ShowPrefsDlg(SIZE_T hWndParent);

	//Importing the unique plugin data when importing the plugin settings
	STDMETHODIMP ImportAdditionalData(IAFS_Name* pPathToAppData, IAFS_Name* pPathToUserData) override;
	//Exporting the unique plugin data when importing the plugin settings
	STDMETHODIMP ExportAdditionalData(IAFS_Name* pPathToAppData, IAFS_Name* pPathToUserData) override;
	//This method is being called when the Editor's settings are being reset
	STDMETHODIMP ResetSettings(ULONG nFlags) override;
Then you will need a custom event listener that will listen to all of the needed events:

Code: Select all

	//Custom listener for the Editor's events
	m_pListener = new PDFXEventListener(this);

	//The event server is responsible for registration of the event listeners
	CComPtr<PXV::IEventServer> es;
	m_pInst->get_EventServer(&es);
	//Registering our listener to handle the e.customizeCmdLayout event (we'll need the numeric ID of the event here that can be obtained by the Str2ID method of the IPXV_Inst)
	es->RegisterNativeEventHandler(id_e_customizeCmdLayout, m_pListener, 0);
	es->RegisterNativeEventHandler(id_e_mainView_customizeCmdBars, m_pListener, 0);
	es->RegisterNativeEventHandler(id_e_document_toolActivated, m_pListener, 0);
	es->RegisterNativeEventHandler(id_e_uiLanguageChanged, m_pListener, 0);
And the class itself that is the event listener should be derived from the IEventHandler interface - here's a C++ declaration for it:

Code: Select all

//Class that implements a listener (IEventHandler) of the registered events that are being sent by the Editor
class PDFXEventListener : public cCoClassDispatchT<PDFXEventListener, IEventHandler> //cCoClassDispatchT - wrapper that implements the AddRef/Release methods for the interface implementation class (in our case - IEventHandler)
{
public:
	PDFXEventListener(PDFXETestPlugin* pPlugin)
	{
		m_pPlugin = pPlugin;
	}
public: //IEventHandler interface method implementation
	virtual STDMETHODIMP OnEvent(LONG nEventID, IEvent* pEvent, IUnknown* pFrom);
public:
	PDFXPlugin* m_pPlugin = nullptr;

};
The e.customizeCmdLayout event is called when the all of the commands are being added/refreshed and then is the time to add your custom commands. For this you will have to implement your own command handler class:

Code: Select all

//Class that implements a custom command handler interface
class PDFXECmdHandler : public cCoClassUnknownT<PDFXECmdHandler, IUIX_CmdHandler>
{
public:
	PDFXECmdHandler(PDFXPlugin* pPlugin)
	{
		m_pPlugin = pPlugin;
	}
public:// IUIX_CmdHandler interface methods implementation
	// Method that returns state for the given command (active, disabled etc)
	virtual STDMETHODIMP OnGetItemState(IUIX_Cmd* pCmd, IUIX_CmdItem* pItem, IUIX_Obj* pOwner, LONG* pState);
	// Method that catches events that happen to our commands (for example it was clicked)
	virtual STDMETHODIMP OnNotify(LONG nCode, IUIX_Cmd* pCmd, IUIX_CmdItem* pItem, IUIX_Obj* pOwner, SIZE_T pNotifyData);

	//Method that returns a custom command implementation if needed (edit, combo, list etc)
	virtual STDMETHODIMP OnCreateNewCtl(IUIX_Cmd* pCmd, IUIX_CmdBar* pParent, IUIX_Obj** ppCtl);
	//Returns the size of the custom command implementation control
	virtual STDMETHODIMP OnGetCtlSizes(IUIX_CmdItem* pItem, SIZE* pSize, SIZE* pMinSize, SIZE* pMaxSize);
	//Here you can manually draw the commands icon in the given rectangle if needed
	virtual STDMETHODIMP OnDrawItemIcon(IUIX_RenderContext* pRC, IUIX_CmdItem* pItem, RECT* pIconRect, RECT* pClip);
	//Returns the command's submenu if it has one
	virtual STDMETHODIMP OnGetItemSubMenu(IUIX_CmdItem* pItem, IUIX_CmdMenu** ppSubMenu);
public:
	PDFXETestPlugin* m_pPlugin = nullptr;
};
Both of the Event listener and Command Handler should be a part of the Plugin's class.

When you have a command handler, you can listen to the e.customizeCmdLayout event and add your commands:

Code: Select all

//////////////////////////////////////////////////////////////////////////
//PDFXEventListener
//////////////////////////////////////////////////////////////////////////

STDMETHODIMP PDFXEventListener::OnEvent(LONG nEventID, IEvent* pEvent, IUnknown* pFrom)
{
	HRESULT hr = S_OK;
	if (!pEvent || !pFrom)
		return hr;
	//This event is being called when the Editor UI commands layout was initialized or modified
	//We'll need this to add our custom commands into the Editor's menus
	if (nEventID == id_e_customizeCmdLayout)	
	{
		CustomizeCmdLayout(pFrom);
	}
	//This is needed for correct language change
	else if (nEventID == id_e_uiLanguageChanged)
	{
		m_pPlugin->UpdateCommandsUILang();
	}
	return hr;
}

void PDFXEventListener::CustomizeCmdLayout(IUnknown* pFrom)
{
	//pFrom - a pointer to the event source - in this case it will be the IPXV_View
	CComPtr<PXV::IPXV_View> pView;
	pFrom->QueryInterface(&pView);
	if (!pView)
		return;

	LONG nViewID = 0;
	pView->get_ID(&nViewID);
	if (nViewID != id_mainView)
		return;

	// Here the commands should be added
	VARIANT_BOOL bIsRibbon = VARIANT_FALSE;
	pView->get_IsRibbonMode(&bIsRibbon);
	if (!bIsRibbon)
		CustomizeClassicCmdLayout(pView);
	else
		CustomizeRibbonCmdLayout(pView);
}

bool PDFXEventListener::CustomizeClassicCmdLayout(PXV::IPXV_View* pView)
{
	CComPtr<IPXV_MainView> pMV;
	if (pView)
		pView->QueryInterface(&pMV);
	if (!pMV)
		return false;
	CComPtr<IUIX_CmdBar> pMB;
	pMV->get_MenuBar(&pMB);
	if (!pMB)
		return false;

	LONG nIdx = -1;
	//Searching whether we have already added the command
	pMB->FlatFindFirstItemByCmdID(id_cmd_pdfx_TEST, &nIdx);
	if (nIdx < 0)
	{
		LONG nHelpIndex = -1;
		pMB->FlatFindFirstItemByCmdID(id_cmd_help, &nHelpIndex);
		CComPtr<IUIX_CmdItem> pItm;
		//We'll add it near the help command
		pMB->FlatInsertItem2(id_cmd_pdfx_TEST, nHelpIndex + 1, 0, 0, &pItm);
		if (pItm)
		{
			CComPtr<IUIX_CmdMenu> pMenu;
			m_pPlugin->m_pUI->CreateCmdMenu(&pMenu);
			if (pMenu)
			{
				pItm->put_SubMenu(pMenu);
				nIdx = nHelpIndex + 1;
			}
				
		}
	}
	//If we did find it, then adding a submenu if it does not exit yet
	if (nIdx >= 0)
	{
		CComPtr<IUIX_CmdItem> pCI;
		pMB->FlatGetItem(nIdx, &pCI);
		if (pCI)
		{
			CComPtr<IUIX_CmdMenu> pSubMenu;
			pCI->get_SubMenu(&pSubMenu);
			if (!pSubMenu)
			{
				m_pPlugin->m_pUI->CreateCmdMenu(&pSubMenu);
				pCI->put_SubMenu(pSubMenu);
			}

			const long aMenuItems[] =
			{
				id_cmd_pdfx_test1,
				0,
				id_cmd_pdfx_test2,
				id_cmd_pdfx_test3,
				0,
				id_cmd_pdfx_test4,
			};

			if (pSubMenu)
			{
				for (const auto& id : aMenuItems)
				{
					if (id > 0)
						pSubMenu->InsertItem2(id, -1, 0, 0, nullptr, nullptr);
					else
						pSubMenu->InsertSeparator(-1);
				}
			}
		}
		return true;
	}
	return false;
}

void PDFXEventListener::CustomizeRibbonCmdLayout(PXV::IPXV_View* pView)
{
	CComPtr<IUIX_CmdPane> pPane;
	pView->get_CmdPaneTop(&pPane);
	if (!pPane)
		return;

	CComPtr<IUIX_Obj> pPaneObj;
	pPane->get_Obj(&pPaneObj);
	if (!pPaneObj)
		return;

	CComPtr<IUIX_CmdRibbonTabs> pTabs;
	pPane->get_Tabs(&pTabs);
	if (!pTabs)
		return;

	LONG nTabInsertPos = -1;
	pTabs->FindByID2(id_rtab_help, &nTabInsertPos);

	CComBSTR t1, t2;
	//Creating new ribbon tab near the help tab
	m_pPlugin->m_pInst->ID2Str(id_pdfx_rtab, &t1);
	m_pPlugin->m_pInst->GetLocalStr2(id_pdfx_rtab, &t2);
	CComPtr<IUIX_CmdRibbonTab> pTab;
	pTabs->InsertNew(t1, t2, PXV::UIX_CmdRibbonTabStyle_Customizable, ++nTabInsertPos, nullptr, &pTab);


	const long aMenuItems[] =
	{
		-id_pdfx_rbar_TEST,

		id_cmd_pdfx_test1,
		0,
		id_cmd_pdfx_test2,
		id_cmd_pdfx_test3,
		0,
		id_cmd_pdfx_test4,
	};

	CComPtr<IUIX_CmdBar> pBar;
	for (auto id : aMenuItems)
	{
		if (id < 0)
		{
			id = -id;
			pBar = nullptr;
			m_pPlugin->m_pInst->ID2Str(id, &t1);
			m_pPlugin->m_pInst->GetLocalStr2(id, &t2);
			m_pPlugin->m_pUI->CreateCmdBar(pPaneObj, nullptr, t1, t2, UIX_CmdBarStyle_Customizable, &pBar);
			if (!pBar)
				break;

			CComPtr<IUIX_Obj> pViewObj;
			pView->get_Obj(&pViewObj);
			pBar->put_Owner(pViewObj);
			pTab->Insert(pBar, -1);
			continue;
		}

		if (!pBar)
			break;

		if (id > 0)
		{
			pBar->FlatInsertItem2(id, -1, 0, 0, nullptr);
		}
		else
			pBar->FlatInsertSeparator(-1, 0, nullptr);
	}
}
Cheers,
Alex
Subscribe at:
https://www.youtube.com/channel/UC-TwAMNi1haxJ1FX3LvB4CQ
oknpp
User
Posts: 4
Joined: Wed Feb 10, 2021 6:42 pm

Re: Using Editor Plugin SDK

Post by oknpp »

Hi Alex,

thanks for the detailed answer. That helped me so much.

Cheers, Olli
Sasha - Tracker Dev Team
User
Posts: 5522
Joined: Fri Nov 21, 2014 8:27 am
Contact:

Using Editor Plugin SDK

Post by Sasha - Tracker Dev Team »

:)
Subscribe at:
https://www.youtube.com/channel/UC-TwAMNi1haxJ1FX3LvB4CQ
Post Reply