本系列描述的是如何使用C++/COM来编写PowerPoint插件,使用的开发工具是 Visual Studio 2017。
Step 1:工程中引入DuiLib
将编译好的duilib(包括头文件、lib、dll)拷贝到工程所在目录下
右键NativePPTAddin工程节点,属性->VC++目录->包含目录和库目录中加入duilib所在目录,加入后大概长这样:
由于Duilib的头文件中包含了StdAfx.h,如果工程中没有这个文件,需要新建一个,留空白即可
-
在
pch.h
中引入DuiLib#include "UIlib.h" using namespace DuiLib; #ifdef _DEBUG # ifdef _UNICODE # pragma comment(lib, "DuiLib_d.lib") # else # pragma comment(lib, "DuiLibA_d.lib") # endif #else # ifdef _UNICODE # pragma comment(lib, "DuiLib.lib") # else # pragma comment(lib, "DuiLibA.lib") # endif #endif
Step 2:添加DuiLib的XML界面到资源
我们以登录框为例,添加一个XML到当前工程,取名叫LoginDialog.xml
-
将LoginDialog.xml导入到资源中,资源类型为DUILIB
<?xml version="1.0" encoding="utf-8" ?> <Window size="400,320" caption="0,0,0,60" bktrans="true"> <VerticalLayout> <HorizontalLayout height="52" bkcolor="#FF0288D1"> <Label text="登录测试框" textcolor="0xFFFFFFFF" padding="20,0,0,0" font="1" /> <Control /> <Button name="minBtn" width="10" height="10" padding="0,21,20,0" normalimage="images\minimize.png" /> <Button name="closeBtn" width="10" height="10" padding="0,21,20,0" normalimage="images\close.png" /> </HorizontalLayout> <VerticalLayout bkcolor="0xFFFFFFFF"> <Edit name="usernameEdit" height="48" font="3" padding="20,30,20,0" bordercolor="0xFF808080" bottombordersize="1" /> <Edit name="passwordEdit" height="48" password="true" font="3" padding="20,20,20,0" bordercolor="0xFF808080" bottombordersize="1" /> <Button name="loginBtn" text="登 录" height="50" bkcolor="#FF0277BD" textcolor="0xFFFFFFFF" font="2" padding="20,40,20,0" /> </VerticalLayout> </VerticalLayout> </Window>
添加资源后,资源列表大概长这样:
Step 3:C++实现LoginDialog
- 在NativePPTAddin工程节点,添加->新建项,选择"C++类"
-
修改LoginDialog类,让它继承自WindowImplBase
class LoginDialog : public WindowImplBase
-
实现基类WindowImplBase的几个虚函数
DuiLib::CDuiString LoginDialog::GetSkinType() { return _T("DUILIB"); } CDuiString LoginDialog::GetSkinFile() { DuiLib::CDuiString dsResID; dsResID.Format(_T("%d"), IDR_DUILIB_LOGIN); return dsResID; }
这表示LoginDialog将从资源中获取界面,资源类型为DUILIB,资源ID为IDR_DUILIB_LOGIN。
-
再简单实现最小化、关闭、登录按钮的事件处理
DUI_BEGIN_MESSAGE_MAP(LoginDialog, WindowImplBase) DUI_ON_MSGTYPE_CTRNAME(DUI_MSGTYPE_CLICK, _T("closeBtn"), onCloseBtnClick) DUI_ON_MSGTYPE_CTRNAME(DUI_MSGTYPE_CLICK, _T("minBtn"), onMinBtnClick) DUI_ON_MSGTYPE_CTRNAME(DUI_MSGTYPE_CLICK, _T("loginBtn"), onLoginBtnClick) DUI_END_MESSAGE_MAP() void LoginDialog::onCloseBtnClick(TNotifyUI & msg) { CWindowWnd::SendMessage(WM_CLOSE); } void LoginDialog::onMinBtnClick(TNotifyUI & msg) { CWindowWnd::SendMessage(WM_SYSCOMMAND, SC_MINIMIZE, 0); } void LoginDialog::onLoginBtnClick(TNotifyUI & msg) { CEditUI *usernameEdit = static_cast<CEditUI *>(m_pm.FindControl(_T("usernameEdit"))); CEditUI *passwordEdit = static_cast<CEditUI *>(m_pm.FindControl(_T("passwordEdit"))); if (usernameEdit == nullptr || passwordEdit == nullptr) return; TCHAR tmp[1024] = { 0 }; swprintf_s(tmp, _T("您输入的用户名是 %s, 密码是 %s"), usernameEdit->GetText().GetData(), passwordEdit->GetText().GetData()); ::MessageBox(m_hWnd, tmp, _T("提示"), MB_OK); }
Step 4:添加获取应用实例和窗口句柄的全局变量
-
在dllmain.cpp中定义全局变量
HINSTANCE g_hInstance;
-
在pch.h中添加一个extern声明
extern HINSTANCE g_hInstance;
-
修改DllMain入口函数,给g_hInstance赋值
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { g_hInstance = hInstance; return _AtlModule.DllMain(dwReason, lpReserved); }
-
在dllmain.cpp再定义一个全局变量,表示PowerPoint的窗口句柄
我们可以通过GetCurrentProcessId拿到当前进程ID,再用EnumWindows枚举窗口,结合GetWindowThreadProcessId拿到PowerPoint的窗口句柄。
struct _TempHandleData { unsigned long processId; HWND windowHandle; }; BOOL isMainWindow(HWND handle) { return ::GetWindow(handle, GW_OWNER) == (HWND)0 && ::IsWindowVisible(handle); } BOOL CALLBACK enumWindowsCallback(HWND handle, LPARAM lParam) { _TempHandleData& data = *(_TempHandleData*)lParam; unsigned long process_id = 0; ::GetWindowThreadProcessId(handle, &process_id); if (data.processId != process_id || !isMainWindow(handle)) return TRUE; data.windowHandle = handle; return FALSE; } HWND GetMainWindow(unsigned long process_id) { _TempHandleData data; data.processId = process_id; data.windowHandle = 0; ::EnumWindows(enumWindowsCallback, (LPARAM)&data); return data.windowHandle; } extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { g_hInstance = hInstance; g_hWnd = GetMainWindow(::GetCurrentProcessId()); return _AtlModule.DllMain(dwReason, lpReserved); }
Step 5:在Connect中添加弹出对话框的处理
将Connect.cpp中原登录按钮的代码删除
-
添加弹出登录对话框的函数
void ShowLoginDialog() { DuiLib::CPaintManagerUI::SetInstance(g_hInstance); TCHAR szPath[MAX_PATH] = { 0 }; GetModuleFileName(g_hInstance, szPath, _countof(szPath)); *_tcsrchr(szPath, _T('\\')) = 0; DuiLib::CPaintManagerUI::SetResourcePath(szPath); LoginDialog dialog; dialog.Create(g_hWnd, _T("登录"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE); dialog.CenterWindow(); dialog.ShowModal(); }
-
重新实现Connect的ButtonClicked
STDMETHODIMP_(HRESULT __stdcall) CConnect::ButtonClicked(IDispatch * control) { CComQIPtr<IRibbonControl> ribbonCtl(control); CComBSTR idStr; if (ribbonCtl->get_Id(&idStr) != S_OK) return S_FALSE; if (idStr == OLESTR("loginButton")) { ShowLoginDialog(); } else if (idStr == OLESTR("uploadButton")) { WCHAR msg[64]; swprintf_s(msg, L"I am uploadButton"); MessageBoxW(NULL, msg, L"NativePPTAddin", MB_OK); } return S_OK; }
启动调试,点击登录后,大概长这样:
下一篇我们将介绍如何部署。
完整的代码在这里。