Tạo COM Add-In bằng Visual C++ (2 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

  • Tôi tuân thủ nội quy khi đăng bài

    nguyendang95

    Thành viên hoạt động
    Tham gia
    25/5/22
    Bài viết
    128
    Được thích
    114
    Hiện nay để phát triển phần bổ trợ (add-in) cho Excel riêng và các ứng dụng Office khác nói chung, Microsoft cung cấp cho lập trình viên những công cụ sau đây:
    • VSTO: Được xây dựng trên nền tảng .NET Framework, cung cấp một bộ thư viện đồ sộ giúp lập trình viên có thể dễ dàng xây dựng một add-in bằng Visual Basic hoặc Visual C#. Nhược điểm: Do phải truy cập vào mô hình đối tượng của ứng dụng thông qua lớp biên dịch (translation) com interop, cho nên hiệu năng mang lại không cao so với những giải pháp khác. Ngoài ra, giải pháp này chỉ có thể chạy trên Windows.
    • Office Javascript API: Cho phép lập trình viên có thể viết add-in bằng Javascript hoặc Typescript chạy đa nền tảng, đa thiết bị, là giải pháp đang được Microsoft khuyến khích nhất hiện nay. Nhược điểm: Triển khai khá phức tạp, yêu cầu kiến thức chuyên sâu về lập trình web.
    • COM Add-In: Giải pháp cổ điển, trong đó yêu cầu lập trình viên viết một COM DLL có triển khai giao diện (interface) IDTExtensibility2 và IRibbonExtensibility để tương tác với ứng dụng chủ (host) và làm việc với giao diện ribbon. Có thể viết bằng bất cứ ngôn ngữ nào cho phép làm việc với COM, từ C/C++, VB6, Delphi cho đến ngôn ngữ nền .NET Framework (Visual Basic và Visual C#) thông qua com interop. Nhược điểm: Triển khai rất phức tạp, yêu cầu kiến thức sâu rộng về COM.
    Bài viết này trình bày các bước viết một Excel COM Add-In bằng C++.
    Tạo COM DLL:
    ATL Project là một giải pháp gồm một bộ các lớp (class) tiêu bản (template) và macro do Microsoft phát triển giúp đơn giản hoá quá trình phát triển một COM DLL và ActiveX control. Để sử dụng, người dùng có thể sử dụng bản Visual Studio mới nhất hoặc những phiên bản khác có hỗ trợ ATL Project.

    1751784602911.png

    Lưu ý: Tính đến thời điểm hiện tại (6-7-2025) Visual Studio đang tồn tại một số lỗi (bug) nghiêm trọng khiến cho việc phát triển bằng ATL Project trở nên rất khó khăn, cho nên trong bài viết này sử dụng phiên bản Visual Studio 2019 để trình bày code.
    Dưới đây là một trong những lỗi đã được Microsoft ghi nhận:
    "Bad file path: 'project_name.idl'" error when creating ATL Simple Objects in an ATL Project created using VS2022 Version 17.13.7
    Để tạo ATL Project, người dùng khởi chạy Visual Studio, sau đó từ màn hình chính của Visual Studio, chọn tiếp Create a new project và chọn ATL Project.

    1751784623148.png

    Tiến hành đặt tên cho dự án và thiết lập một vài tham số khác (nếu có). Trong bài viết này đặt tên dự án là ExcelAddInDemo.

    1751784630098.png

    Triển khai giao diện IDTExtensibility2 và IRibbonExtensibility:
    Lúc này dự án đang trống rỗng, cho nên để add-in thực sự hoạt động được, người dùng cần triển khai giao diện IDTExtensibility2 và IRibbonExtensibility. IDTExtensibility2 gồm năm phương thức (method) OnConnection, OnDisconnection, OnStartupComplete, OnAddInsUpdate và OnBeginShutdown, giao diện này đóng vai trò định hình COM DLL trở thành add-in mà Excel có thể tương tác được. Còn IRibbonExtensibility chỉ có một phương thức duy nhất, GetCustomUI, đóng vai trò trình bày giao diện ribbon tuỳ chỉnh theo mong muốn của người dùng.
    Để bắt đầu, trước tiên người dùng cần nhập (import) các thư viện cần thiết trong tập tin pch.h.

    C++:
    // pch.h: This is a precompiled header file.
    // Files listed below are compiled only once, improving build performance for future builds.
    // This also affects IntelliSense performance, including code completion and many code browsing features.
    // However, files listed here are ALL re-compiled if any one of them is updated between builds.
    // Do not add files here that you will be updating frequently as this negates the performance advantage.
    
    #ifndef PCH_H
    #define PCH_H
    
    // add headers that you want to pre-compile here
    #include "framework.h"
    #import "libid:AC0714F2-3D04-11D1-AE7D-00A0C90F26F4" raw_interfaces_only, raw_native_types, no_namespace, named_guids, auto_search
    #import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" auto_rename auto_search raw_interfaces_only rename_namespace("Office")
    #import "libid:00020813-0000-0000-C000-000000000046" auto_rename auto_search rename("RGB", "ExcelRGB") rename("DocumentProperties", "ExcelDocumentProperties")
    using namespace Office;
    using namespace Excel;
    #endif //PCH_H

    Tiếp theo, tạo một ATL Simple Object bằng cách, từ cửa sổ Solution Explorer bên tay phải màn hình, nhấp chuột phải vào tên dự án (ở đây là ExcelAddInDemo), sau đó chọn Add – New Item. Cửa sổ Add New Item hiện ra, người dùng chọn tiếp mục ATL trong phần Visual C++ xổ ra và chọn ATL Simple Object, trong bài viết đặt tên là ThisAddIn.

    1751784696862.png

    Lưu ý thông tin ở ô ProgID, đây chính là căn cứ để đặt tên khoá registry giúp Excel nhận diện được add-in.

    1751784706052.png

    Một class mới được tạo ra, người dùng có thể triển khai hai giao diện quan trọng ở trên.

    C++:
    // ThisAddIn.h : Declaration of the CThisAddIn
    
    #pragma once
    #include "resource.h"       // main symbols
    
    
    
    #include "ExcelAddInDemo_i.h"
    
    
    
    #if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
    #error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
    #endif
    
    using namespace ATL;
    
    typedef IDispatchImpl <IRibbonExtensibility, &__uuidof(IRibbonExtensibility), &__uuidof(__Office), 2, 5> RibbonImpl;
    typedef IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &LIBID_AddInDesignerObjects, /* wMajor = */ 1, /* wMinor = */ 0> IDTImpl;
    // CThisAddIn
    
    class ATL_NO_VTABLE CThisAddIn :
        public CComObjectRootEx<CComSingleThreadModel>,
        public CComCoClass<CThisAddIn, &CLSID_ThisAddIn>,
        public IDispatchImpl<IThisAddIn, &IID_IThisAddIn, &LIBID_ExcelAddInDemoLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
        public RibbonImpl,
        public IDTImpl
    {
    public:
        CThisAddIn()
        {
        }
    
    DECLARE_REGISTRY_RESOURCEID(106)
    
    
    BEGIN_COM_MAP(CThisAddIn)
        COM_INTERFACE_ENTRY(IThisAddIn)
        COM_INTERFACE_ENTRY2(IDispatch, IThisAddIn)
        COM_INTERFACE_ENTRY(_IDTExtensibility2)
        COM_INTERFACE_ENTRY(IRibbonExtensibility)
    END_COM_MAP()
    
    
    
        DECLARE_PROTECT_FINAL_CONSTRUCT()
    
        HRESULT FinalConstruct()
        {
            return S_OK;
        }
    
        void FinalRelease()
        {
        }
    
    public:
        STDMETHOD(OnConnection)(IDispatch* Application, ext_ConnectMode ConnectMode, IDispatch* AddInInst, SAFEARRAY** custom);
        STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY** custom);
        STDMETHOD(OnAddInsUpdate)(SAFEARRAY** custom);
        STDMETHOD(OnStartupComplete)(SAFEARRAY** custom);
        STDMETHOD(OnBeginShutdown)(SAFEARRAY** custom);
        STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR* RibbonXml);
    
    
    };
    
    OBJECT_ENTRY_AUTO(__uuidof(ThisAddIn), CThisAddIn)

    C++:
    // ThisAddIn.cpp : Implementation of CThisAddIn
    
    #include "pch.h"
    #include "ThisAddIn.h"
    
    
    // CThisAddIn
    
    STDMETHODIMP CThisAddIn::OnConnection(IDispatch* Application, ext_ConnectMode ConnectMode, IDispatch* AddInInst, SAFEARRAY** custom) {
        if (!Application) return E_POINTER;
        MessageBox(NULL, L"Add-in loaded", L"Add-in Event", MB_OK | MB_ICONINFORMATION);
        return S_OK;
    }
    
    STDMETHODIMP CThisAddIn::OnDisconnection(ext_DisconnectMode RemoveMode, SAFEARRAY** custom) {
        return S_OK;
    }
    
    STDMETHODIMP CThisAddIn::OnAddInsUpdate(SAFEARRAY** custom) {
        return S_OK;
    }
    
    STDMETHODIMP CThisAddIn::OnStartupComplete(SAFEARRAY** custom) {
        return S_OK;
    }
    
    STDMETHODIMP CThisAddIn::OnBeginShutdown(SAFEARRAY** custom) {
        return S_OK;
    }
    
    STDMETHODIMP CThisAddIn::GetCustomUI(BSTR RibbonID, BSTR* RibbonXml) {
        if (!RibbonXml) return E_POINTER;
        *RibbonXml = SysAllocString(LR"(<?xml version="1.0" encoding="UTF-8"?>
                                        <customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="OnRibbonLoaded">
                                          <ribbon>
                                            <tabs>
                                              <tab id="customTab" label="Excel AddIn">
                                                <group id="customGroup" label="Group">
                                                  <button id="customButton"
                                                          label="Click Me"
                                                          getImage="GetImage"
                                                          size="large"
                                                          onAction="ButtonClicked" />
                                                </group>
                                              </tab>
                                            </tabs>
                                          </ribbon>
                                        </customUI>)");
        return *RibbonXml ? S_OK : E_OUTOFMEMORY;
    }

    Đoạn code này nhìn chung khá đơn giản, trong phương thức OnConnection chứa một câu lệnh nhỏ có chức năng hiển thị một hộp thoại, còn phương thức GetCustomUI có tác dụng nạp bố cục (schema) của ribbon theo định dạng XML để Excel có thể trình bày. Để đơn giản, bài viết này chèn trực tiếp schema của ribbon vào code, còn khi viết code thực tế người dùng nên nạp schema vào DLL thông qua mục Resource Files, sau đó lần lượt sử dụng các hàm FindResource, LoadResource và LockResource để đọc nội dung và đưa vào phương thức GetCustomUI sẽ thuận tiện hơn.
    Ngoài ra một tập tin MIDL cũng sẽ được tạo ra. Trong bài viết này không sử dụng tập tin này, tuy nhiên nó đóng vai trò quan trọng khi người dùng muốn định nghĩa các hàm gọi lại (callback function) từ các control trên ribbon.

    C++:
    // ExcelAddInDemo.idl : IDL source for ExcelAddInDemo
    //
    
    // This file will be processed by the MIDL tool to
    // produce the type library (ExcelAddInDemo.tlb) and marshalling code.
    
    import "oaidl.idl";
    import "ocidl.idl";
    
    [
        object,
        uuid(ded9f6b8-b802-4547-97fe-0dabdcd4d205),
        dual,
        nonextensible,
        pointer_default(unique)
    ]
    interface IThisAddIn : IDispatch
    {
    };
    [
        uuid(bd1b5ce2-9b75-49f5-b9a3-c9eb192b1be1),
        version(1.0),
    ]
    library ExcelAddInDemoLib
    {
        importlib("stdole2.tlb");
        [
            uuid(2762b776-5488-4564-8cbd-fb71834332ca)
        ]
        coclass ThisAddIn
        {
            [default] interface IThisAddIn;
        };
    };
    
    import "shobjidl.idl";

    Tiếp theo, người dùng mở tập tin ThisAddIn.rgs trong mục Resource Files trong cửa sổ Solution Explorer và chèn vào đó đoạn mã dưới đây, lưu ý mã ProgID ExcelAddInDemo.ThisAddIn mà người dùng đã thiết lập khi tạo ATL Simple Object:
    Mã:
    HKCR
    {
        ExcelAddInDemo.ThisAddIn.1 = s 'ThisAddIn class'
        {
            CLSID = s '{2762b776-5488-4564-8cbd-fb71834332ca}'
        }
        ExcelAddInDemo.ThisAddIn = s 'ThisAddIn class'
        {    
            CurVer = s 'ExcelAddInDemo.ThisAddIn.1'
        }
        NoRemove CLSID
        {
            ForceRemove {2762b776-5488-4564-8cbd-fb71834332ca} = s 'ThisAddIn class'
            {
                ProgID = s 'ExcelAddInDemo.ThisAddIn.1'
                VersionIndependentProgID = s 'ExcelAddInDemo.ThisAddIn'
                ForceRemove Programmable
                InprocServer32 = s '%MODULE%'
                {
                    val ThreadingModel = s 'Apartment'
                }
                TypeLib = s '{bd1b5ce2-9b75-49f5-b9a3-c9eb192b1be1}'
                Version = s '1.0'
            }
        }
    }
    
    HKCU
    {
        NoRemove Software
        {
            NoRemove Microsoft
            {
                NoRemove Office
                {
                    NoRemove Excel
                    {
                        NoRemove Addins
                        {
                            ExcelAddInDemo.ThisAddIn
                            {
                                val Description = s 'Sample Addin'
                                val FriendlyName = s 'Sample Addin'
                                val LoadBehavior = d 3
                            }
                        }
                    }
                }
            }
        }
    }
    Cuối cùng, tiến hành biên dịch (build) ra DLL hoàn chỉnh, tuỳ thuộc vào phiên bản Excel cài đặt trên máy tính mà người dùng có thể biên dịch ra DLL phiên bản 32bit hoặc 64bit, sau đó cho Excel nạp add-in.
    Kết quả:

    1751785125655.png

    Bố cục ribbon tuỳ chỉnh.

    1751785149545.png
    Người dùng có thể tìm thấy code đính kèm theo bài viết này.
     

    File đính kèm

    Lần chỉnh sửa cuối:
    Tạo Ngăn Tác vụ (Task Pane):
    Để tạo Task Pane, người dùng cần triển khai giao diện ICustomTaskPaneConsumer, kèm theo đó là tạo một ActiveX control đóng vai trò là cửa sổ (window) hiển thị cho Task Pane.
    Trước tiên, người dùng hãy tạo ActiveX control bằng cách, từ cửa sổ Solution Explorer bên phải màn hình, nhấp chuột phải vào tên dự án (trong bài viết là ExcelAddInDemo), sau đó chọn Add - New Item.
    Hộp thoại Add New Item hiện ra, người dùng chọn tiếp ATL - ATL Control trong mục Visual C++.
    1751902512413.png
    Tiến hành đặt tên, trong bài viết này để là MyCustomTaskPane. Người dùng có thể để trống ô ProgID, tuy nhiên người dùng nên nhập giá trị này để sau này có thể lấy làm tham số cho phương thức tạo Task Pane, không cần phải dùng hàm ProgIDFromCLSID để lấy thông tin ProgID của ActiveX control từ mã CLSID.
    1751902646745.png
    ATL Project sẽ tự động tạo ra một class mới tên là CMyCustomTaskPane, người dùng cần sửa một số thứ trong class này để đảm bảo Task Pane có thể sử dụng nó một cách thuận lợi.
    Đầu tiên, sửa phương thức khởi tạo class (constructor) của class, cho phép biến ActiveX control thành dạng cửa sổ (window).
    C++:
        CMyCustomTaskPane()
        {
            m_bWindowOnly = true;
        }
    Tiếp theo, thêm thông điệp (message) cần xử lý riêng thông qua macro BEGIN_MSG_MAP, ở đây người dùng sẽ tạo một nút bấm (button) và một textbox (hay edit control) khi Task Pane hiện ra, cho nên người dùng sẽ cần xử lý thông điệp WM_CREATE thông qua macro MESSAGE_HANDLER, hàm gọi lại để xử lý thông điệp sẽ là OnCreate. Tiếp theo, xử lý thông điệp WM_COMMAND thông qua hàm gọi lại OnClick để thiết lập logic khi người dùng nhấp chuột vào nút bấm thì sẽ hiện ra hộp thoại.
    C++:
    // MyCustomTaskPane.h : Declaration of the CMyCustomTaskPane
    #pragma once
    #include "resource.h"       // main symbols
    #include <atlctl.h>
    #include "ExcelAddInDemo_i.h"
    
    #if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
    #error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
    #endif
    
    using namespace ATL;
    
    
    
    // CMyCustomTaskPane
    class ATL_NO_VTABLE CMyCustomTaskPane :
        public CComObjectRootEx<CComSingleThreadModel>,
        public IDispatchImpl<IMyCustomTaskPane, &IID_IMyCustomTaskPane, &LIBID_ExcelAddInDemoLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
        public IOleControlImpl<CMyCustomTaskPane>,
        public IOleObjectImpl<CMyCustomTaskPane>,
        public IOleInPlaceActiveObjectImpl<CMyCustomTaskPane>,
        public IViewObjectExImpl<CMyCustomTaskPane>,
        public IOleInPlaceObjectWindowlessImpl<CMyCustomTaskPane>,
        public CComCoClass<CMyCustomTaskPane, &CLSID_MyCustomTaskPane>,
        public CComControl<CMyCustomTaskPane>
    {
    public:
    
    
        CMyCustomTaskPane()
        {
            m_bWindowOnly = true;
        }
    
    DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE |
        OLEMISC_CANTLINKINSIDE |
        OLEMISC_INSIDEOUT |
        OLEMISC_ACTIVATEWHENVISIBLE |
        OLEMISC_SETCLIENTSITEFIRST
    )
    
    DECLARE_REGISTRY_RESOURCEID(IDR_MYCUSTOMTASKPANE)
    
    
    BEGIN_COM_MAP(CMyCustomTaskPane)
        COM_INTERFACE_ENTRY(IMyCustomTaskPane)
        COM_INTERFACE_ENTRY(IDispatch)
        COM_INTERFACE_ENTRY(IViewObjectEx)
        COM_INTERFACE_ENTRY(IViewObject2)
        COM_INTERFACE_ENTRY(IViewObject)
        COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)
        COM_INTERFACE_ENTRY(IOleInPlaceObject)
        COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)
        COM_INTERFACE_ENTRY(IOleInPlaceActiveObject)
        COM_INTERFACE_ENTRY(IOleControl)
        COM_INTERFACE_ENTRY(IOleObject)
    END_COM_MAP()
    
    BEGIN_PROP_MAP(CMyCustomTaskPane)
        PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
        PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
        // Example entries
        // PROP_ENTRY_TYPE("Property Name", dispid, clsid, vtType)
        // PROP_PAGE(CLSID_StockColorPage)
    END_PROP_MAP()
    
    
    BEGIN_MSG_MAP(CMyCustomTaskPane)
        CHAIN_MSG_MAP(CComControl<CMyCustomTaskPane>)
        DEFAULT_REFLECTION_HANDLER()
        MESSAGE_HANDLER(WM_CREATE, OnCreate)
        MESSAGE_HANDLER(WM_COMMAND, OnClick)
    END_MSG_MAP()
    // Handler prototypes:
    //  LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
    //  LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
    //  LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);
    
    // IViewObjectEx
        DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE)
    
    // IMyCustomTaskPane
    public:
        LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
        LRESULT OnClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
        HRESULT OnDraw(ATL_DRAWINFO& di)
        {
            RECT& rc = *(RECT*)di.prcBounds;
            // Set Clip region to the rectangle specified by di.prcBounds
            HRGN hRgnOld = nullptr;
            if (GetClipRgn(di.hdcDraw, hRgnOld) != 1)
                hRgnOld = nullptr;
            bool bSelectOldRgn = false;
    
            HRGN hRgnNew = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
    
            if (hRgnNew != nullptr)
            {
                bSelectOldRgn = (SelectClipRgn(di.hdcDraw, hRgnNew) != ERROR);
            }
    
            Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom);
            SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE);
            LPCTSTR pszText = _T("MyCustomTaskPane");
    #ifndef _WIN32_WCE
            TextOut(di.hdcDraw,
                (rc.left + rc.right) / 2,
                (rc.top + rc.bottom) / 2,
                pszText,
                lstrlen(pszText));
    #else
            ExtTextOut(di.hdcDraw,
                (rc.left + rc.right) / 2,
                (rc.top + rc.bottom) / 2,
                ETO_OPAQUE,
                nullptr,
                pszText,
                ATL::lstrlen(pszText),
                nullptr);
    #endif
    
            if (bSelectOldRgn)
                SelectClipRgn(di.hdcDraw, hRgnOld);
    
            DeleteObject(hRgnNew);
    
            return S_OK;
        }
    
    
        DECLARE_PROTECT_FINAL_CONSTRUCT()
    
        HRESULT FinalConstruct()
        {
            return S_OK;
        }
    
        void FinalRelease()
        {
        }
    };
    
    OBJECT_ENTRY_AUTO(__uuidof(MyCustomTaskPane), CMyCustomTaskPane)
    C++:
    // MyCustomTaskPane.cpp : Implementation of CMyCustomTaskPane
    #include "pch.h"
    #include "MyCustomTaskPane.h"
    
    
    // CMyCustomTaskPane
    
    LRESULT CMyCustomTaskPane::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
        HINSTANCE hInstance = (HINSTANCE)GetWindowLongPtr(GWLP_HINSTANCE);
        if (!hInstance) return -1;
        if (!CreateWindow(L"BUTTON", L"OK", WS_VISIBLE | WS_CHILD, 130, 150, 30, 30, m_hWnd, (HMENU)1, hInstance, NULL)) return -1;
        if (!CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"Hello World!", WS_CHILD | WS_VISIBLE | ES_LEFT | ES_UPPERCASE, 130, 200, 50, 50, m_hWnd, (HMENU)2, hInstance, NULL)) return -1;
        return 0;
    }
    
    LRESULT CMyCustomTaskPane::OnClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
        switch (LOWORD(wParam)) {
        case 1:
        {
            MessageBox(L"You clicked button", L"Click Event", MB_OK | MB_ICONINFORMATION);
            break;
        }
        }
        return 0;
    }
    Như vậy là xong phần ActiveX control, tiếp theo người dùng cần triển khai giao diện ICustomTaskPaneConsumer. Để thuận tiện, người dùng nên triển khai ngay trong class ThisAddIn mà người dùng đã tạo trước đó.
    C++:
    // ThisAddIn.h : Declaration of the CThisAddIn
    
    #pragma once
    #include "resource.h"       // main symbols
    
    
    
    #include "ExcelAddInDemo_i.h"
    
    
    
    #if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
    #error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
    #endif
    
    using namespace ATL;
    
    typedef IDispatchImpl <IRibbonExtensibility, &__uuidof(IRibbonExtensibility), &__uuidof(__Office), 2, 5> RibbonImpl;
    typedef IDispatchImpl<_IDTExtensibility2, &__uuidof(_IDTExtensibility2), &LIBID_AddInDesignerObjects, /* wMajor = */ 1, /* wMinor = */ 0> IDTImpl;
    typedef IDispatchImpl<ICustomTaskPaneConsumer, &__uuidof(ICustomTaskPaneConsumer), &__uuidof(__Office), 1, 0> CTPImpl;
    // CThisAddIn
    
    class ATL_NO_VTABLE CThisAddIn :
        public CComObjectRootEx<CComSingleThreadModel>,
        public CComCoClass<CThisAddIn, &CLSID_ThisAddIn>,
        public IDispatchImpl<IThisAddIn, &IID_IThisAddIn, &LIBID_ExcelAddInDemoLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
        public RibbonImpl,
        public IDTImpl,
        public CTPImpl
    {
    public:
        CThisAddIn()
        {
        }
    
    DECLARE_REGISTRY_RESOURCEID(106)
    
    
    BEGIN_COM_MAP(CThisAddIn)
        COM_INTERFACE_ENTRY(IThisAddIn)
        COM_INTERFACE_ENTRY2(IDispatch, IThisAddIn)
        COM_INTERFACE_ENTRY(_IDTExtensibility2)
        COM_INTERFACE_ENTRY(IRibbonExtensibility)
        COM_INTERFACE_ENTRY(ICustomTaskPaneConsumer)
    END_COM_MAP()
    
    
    
        DECLARE_PROTECT_FINAL_CONSTRUCT()
    
        HRESULT FinalConstruct()
        {
            return S_OK;
        }
    
        void FinalRelease()
        {
        }
    
    public:
        STDMETHOD(OnConnection)(IDispatch* Application, ext_ConnectMode ConnectMode, IDispatch* AddInInst, SAFEARRAY** custom);
        STDMETHOD(OnDisconnection)(ext_DisconnectMode RemoveMode, SAFEARRAY** custom);
        STDMETHOD(OnAddInsUpdate)(SAFEARRAY** custom);
        STDMETHOD(OnStartupComplete)(SAFEARRAY** custom);
        STDMETHOD(OnBeginShutdown)(SAFEARRAY** custom);
        STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR* RibbonXml);
        STDMETHOD(CTPFactoryAvailable)(ICTPFactory* CTPFactoryInst);
    private:
        ICTPFactoryPtr m_CTPFactoryInst;
        _CustomTaskPanePtr m_CTP;
    };
    
    OBJECT_ENTRY_AUTO(__uuidof(ThisAddIn), CThisAddIn)
    C++:
    // ThisAddIn.cpp : Implementation of CThisAddIn
    
    #include "pch.h"
    #include "ThisAddIn.h"
    
    
    // CThisAddIn
    
    STDMETHODIMP CThisAddIn::OnConnection(IDispatch* Application, ext_ConnectMode ConnectMode, IDispatch* AddInInst, SAFEARRAY** custom) {
        if (!Application) return E_POINTER;
        return S_OK;
    }
    
    STDMETHODIMP CThisAddIn::OnDisconnection(ext_DisconnectMode RemoveMode, SAFEARRAY** custom) {
        return S_OK;
    }
    
    STDMETHODIMP CThisAddIn::OnAddInsUpdate(SAFEARRAY** custom) {
        return S_OK;
    }
    
    STDMETHODIMP CThisAddIn::OnStartupComplete(SAFEARRAY** custom) {
        return S_OK;
    }
    
    STDMETHODIMP CThisAddIn::OnBeginShutdown(SAFEARRAY** custom) {
        return S_OK;
    }
    
    STDMETHODIMP CThisAddIn::GetCustomUI(BSTR RibbonID, BSTR* RibbonXml) {
        if (!RibbonXml) return E_POINTER;
        *RibbonXml = SysAllocString(LR"(<?xml version="1.0" encoding="UTF-8"?>
                                        <customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="OnRibbonLoaded">
                                          <ribbon>
                                            <tabs>
                                              <tab id="customTab" label="Excel AddIn">
                                                <group id="customGroup" label="Group">
                                                  <button id="customButton"
                                                          label="Click Me"
                                                          getImage="GetImage"
                                                          size="large"
                                                          onAction="ButtonClicked" />
                                                </group>
                                              </tab>
                                            </tabs>
                                          </ribbon>
                                        </customUI>)");
        return *RibbonXml ? S_OK : E_OUTOFMEMORY;
    }
    
    STDMETHODIMP CThisAddIn::CTPFactoryAvailable(ICTPFactory* CTPFactoryInst) {
        if (!CTPFactoryInst) return E_POINTER;
        m_CTPFactoryInst = CTPFactoryInst;
        HRESULT hr = m_CTPFactoryInst->CreateCTP(L"ExcelAddInDemo.MyCustomTaskPane", L"My Custom Task Pane", vtMissing, &m_CTP);
        if (SUCCEEDED(hr)) {
            hr = m_CTP->put_Visible(VARIANT_TRUE);
            if (FAILED(hr)) return hr;
            hr = m_CTP->put_Width(600);
            if (FAILED(hr)) return hr;
        }
        return hr;
    }

    Kết quả:
    1751905889664.png
     

    File đính kèm

    Lần chỉnh sửa cuối:
    Web KT

    Bài viết mới nhất

    Back
    Top Bottom