none
creating a dialog-window in a native outlook add-in RRS feed

  • Question

  • I'm creating a native C++ Outlook add-in (.dll) and started my Visual Studio 2013 project using MS guidance. The idea is to get user input from a dialog box when the user clicks a Ribbon button and then act on the input after the dialog closes.

    Where I need help is creating a Window in the ButtonClicked function. I've tried all the wrong ways which include MFC class wizard and ATL window (CAxDialogImpl). The common denominator is I don't have a valid parent window to create another window. Even if I get an active window handle from outlook.

    Ideally, I would not take a dependency on MFC but will use it if necessary. I'm looking for assistance creating a dialog/window in Win32 or ATL, any ideas on how to achieve this?

    I get C#/managed is the way to go, but the requirements dictate otherwise.

    Sunday, October 25, 2015 9:52 PM

Answers

  • Where I need help is creating a Window in the ButtonClicked function. I've tried all the wrong ways which include MFC class wizard and ATL window (CAxDialogImpl).

    I don't know what you mean when you say that you've tried all the wrong ways.  It would be helpful if you posted the code so that we can better understand the situation.

    The common denominator is I don't have a valid parent window to create another window. Even if I get an active window handle from outlook.

    This is also a bit confusing.  What do you mean by "valid parent window" and "active window handle from outlook"?

    When your button is pressed the handler function is passed an IRibbonControl interface pointer. Using this pointer you can call it's Context method to obtain an interface pointer to the object from which the button was pressed (Explorer or Inspector); you can call the Id method to retrieve the Ribbon ID of the button control that was pressed and you can call the Tag method to retrieve any tag associated with the button control.

    For example my ATL COM addins use IRibbonControl::Context to retrieve the Outlook:: _Explorer or Outlook::_Inspector interface pointer.  You can retrieve a window handle from an Outlook::_Explorer interface or Outlook::_Inspector interface by doing a QueryInterface for IOleWindow.  The returned IOleWindow interface pointer can be used to retrieve a valid HWND for an Explorer or Inspector windows.

    Finally, if you have created an ATL dialog all you have to do is to declare an instance of the dialog as a variable in the button's handler function.  Then, just call the variable's DoModal() function.

    An ATL dialog's DoModal function is often called without parameters.  However, the default parameters that the class definition supplies to the function are the HWND  returned by GetActiveWindow()  and an LPARAM value initialized to NULL.

    • Edited by RLWA32 Sunday, October 25, 2015 10:44 PM expanded DoModal()
    • Marked as answer by Joel_Z Monday, October 26, 2015 12:10 AM
    Sunday, October 25, 2015 10:30 PM

All replies

  • Where I need help is creating a Window in the ButtonClicked function. I've tried all the wrong ways which include MFC class wizard and ATL window (CAxDialogImpl).

    I don't know what you mean when you say that you've tried all the wrong ways.  It would be helpful if you posted the code so that we can better understand the situation.

    The common denominator is I don't have a valid parent window to create another window. Even if I get an active window handle from outlook.

    This is also a bit confusing.  What do you mean by "valid parent window" and "active window handle from outlook"?

    When your button is pressed the handler function is passed an IRibbonControl interface pointer. Using this pointer you can call it's Context method to obtain an interface pointer to the object from which the button was pressed (Explorer or Inspector); you can call the Id method to retrieve the Ribbon ID of the button control that was pressed and you can call the Tag method to retrieve any tag associated with the button control.

    For example my ATL COM addins use IRibbonControl::Context to retrieve the Outlook:: _Explorer or Outlook::_Inspector interface pointer.  You can retrieve a window handle from an Outlook::_Explorer interface or Outlook::_Inspector interface by doing a QueryInterface for IOleWindow.  The returned IOleWindow interface pointer can be used to retrieve a valid HWND for an Explorer or Inspector windows.

    Finally, if you have created an ATL dialog all you have to do is to declare an instance of the dialog as a variable in the button's handler function.  Then, just call the variable's DoModal() function.

    An ATL dialog's DoModal function is often called without parameters.  However, the default parameters that the class definition supplies to the function are the HWND  returned by GetActiveWindow()  and an LPARAM value initialized to NULL.

    • Edited by RLWA32 Sunday, October 25, 2015 10:44 PM expanded DoModal()
    • Marked as answer by Joel_Z Monday, October 26, 2015 12:10 AM
    Sunday, October 25, 2015 10:30 PM
  • Where I need help is creating a Window in the ButtonClicked function. I've tried all the wrong ways which include MFC class wizard and ATL window (CAxDialogImpl).

    I don't know what you mean when you say that you've tried all the wrong ways.  It would be helpful if you posted the code so that we can better understand the situation.

    The common denominator is I don't have a valid parent window to create another window. Even if I get an active window handle from outlook.

    This is also a bit confusing.  What do you mean by "valid parent window" and "active window handle from outlook"?

    When your button is pressed the handler function is passed an IRibbonControl interface pointer. Using this pointer you can call it's Context method to obtain an interface pointer to the object from which the button was pressed (Explorer or Inspector); you can call the Id method to retrieve the Ribbon ID of the button control that was pressed and you can call the Tag method to retrieve any tag associated with the button control.

    For example my ATL COM addins use IRibbonControl::Context to retrieve the Outlook:: _Explorer or Outlook::_Inspector interface pointer.  You can retrieve a window handle from an Outlook::_Explorer interface or Outlook::_Inspector interface by doing a QueryInterface for IOleWindow.  The returned IOleWindow interface pointer can be used to retrieve a valid HWND for an Explorer or Inspector windows.

    Finally, if you have created an ATL dialog all you have to do is to declare an instance of the dialog as a variable in the button's handler function.  Then, just call the variable's DoModal() function.

    An ATL dialog's DoModal function is often called without parameters.  However, the default parameters that the class definition supplies to the function are the HWND  returned by GetActiveWindow()  and an LPARAM value initialized to NULL.

    My post was a bit confusing, I see that now. I also made a mistake with it. To clarify and make a correction, I had tried using the Outlook Explorer Window handle obtained during the OnConnect callback. When calling ShowWindow() and then UpdateWindow of a MFC window class instance (using the Outlook window handle), it hit an access violation.

        HWND hWnd = NULL;
        CComQIPtr<Outlook::_Explorer> explorer;
        spApp->ActiveExplorer(&explorer);
        CComQIPtr<IOleWindow> window = explorer;
        if(NULL != window)
             window->GetWindow(&hWnd);

    After hearing your input, I'm going to try getting the window handle after Outlook has finished opening and then use DoModal. Thanks!

    Sunday, October 25, 2015 11:51 PM
  • I can't tell if you're using MFC or ATL for your dialog but you should consider one more thing.  Any modal dialog that you create in your button handler is going to run on Outlook's main thread.  While your modal dialog is running all of the Outlook UI will be blocked and so will all other Outlook processing that takes place on the main thread.  That's not a problem if the dialog is used quickly and then dismissed.



    • Edited by RLWA32 Monday, October 26, 2015 12:09 AM
    Monday, October 26, 2015 12:06 AM
  • I have specifics now about my problem opening a new Window in the ribbon button handler. In button click, executing dlg.Create(m_hWnd); causes an assert in atlwin.h:3191. The stack shows the window handle is good and the assert is when the window handle is good, so I guess it wants a NULL handle. I had thought I needed to pass a valid window handle because that is an input parameter. 

    STDMETHODIMP CConnect::ButtonClicked(IDispatch* ribbon) {
        CCloudDlg dlg;
        CComQIPtr<Office::IRibbonControl> pRibCtrl = ribbon;
        if(NULL != pRibCtrl) {
            CComQIPtr<Outlook::_Explorer> pExplorer;
            CComQIPtr<IDispatch> pRibCtx;
            pRibCtrl->get_Context(&pRibCtx);
            pExplorer = pRibCtx;
            CComQIPtr<IOleWindow> pWindow = pExplorer;
            if(NULL != pWindow)
                pWindow->GetWindow(&m_hWnd);
        }
        if(NULL != m_hWnd)
            dlg.Create(m_hWnd);
        
        return S_OK;
    }


    #ifdef _DEBUG
    		if(m_hWnd != NULL)	// should be cleared in WindowProc
    		{
    			ATLTRACE(atlTraceWindowing, 0, _T("ERROR - Object deleted before window was destroyed\n"));
    			ATLASSERT(FALSE);
    		}
    #endif //_DEBUG

    the window handle value from the debugger:

    m_hWnd 0x000000000013080e {unused=??? } HWND__ *

    Here is the whole class that contains the assert:

    template <class TBase /* = CWindow */>
    class ATL_NO_VTABLE CWindowImplRoot : 
    	public TBase, 
    	public CMessageMap
    {
    public:
    	CWndProcThunk m_thunk;
    	const _ATL_MSG* m_pCurrentMsg;
    	DWORD m_dwState;
    
    	enum { WINSTATE_DESTROYED = 0x00000001 };
    
    // Constructor/destructor
    	CWindowImplRoot() : m_pCurrentMsg(NULL), m_dwState(0)
    	{
    	}
    
    	virtual ~CWindowImplRoot()
    	{
    #ifdef _DEBUG
    		if(m_hWnd != NULL)	// should be cleared in WindowProc
    		{
    			ATLTRACE(atlTraceWindowing, 0, _T("ERROR - Object deleted before window was destroyed\n"));
    			ATLASSERT(FALSE);
    		}
    #endif //_DEBUG
    	}
    
    // Current message
    	const _ATL_MSG* GetCurrentMessage() const
    	{
    		return m_pCurrentMsg;
    	}
    
    	// "handled" management for cracked handlers
    	BOOL IsMsgHandled() const
    	{
    		const _ATL_MSG* pMsg = GetCurrentMessage();
    		ATLASSUME(pMsg != NULL);
    		ATLASSERT(pMsg->cbSize >= sizeof(_ATL_MSG));
    		return pMsg->bHandled;
    	}
    	void SetMsgHandled(_In_ BOOL bHandled)
    	{
    		_ATL_MSG* pMsg = (_ATL_MSG*)GetCurrentMessage();	// override const
    		ATLASSUME(pMsg != NULL);
    		ATLASSERT(pMsg->cbSize >= sizeof(_ATL_MSG));
    		pMsg->bHandled = bHandled;
    	}
    
    // Message forwarding and reflection support
    	LRESULT ForwardNotifications(
    		_In_ UINT uMsg,
    		_In_ WPARAM wParam,
    		_In_ LPARAM lParam,
    		_Out_ BOOL& bHandled);
    	LRESULT ReflectNotifications(
    		_In_ UINT uMsg,
    		_In_ WPARAM wParam,
    		_In_ LPARAM lParam,
    		_Out_ BOOL& bHandled);
    	static _Success_(return != FALSE) BOOL DefaultReflectionHandler(
    		_In_ HWND hWnd,
    		_In_ UINT uMsg,
    		_In_ WPARAM wParam,
    		_In_ LPARAM lParam,
    		_Out_ LRESULT& lResult);
    };


    I think the MFC/ATL forums were axed(archived). It still may be related to Outlook or how I'm using it, I don't know. Do you know what I'm doing wrong here?

    Monday, October 26, 2015 12:55 AM
  • it looks like the CCloudDlg class deconstructor is called before CCloudDlg.Create(&m_hWnd); returns. Probably not Outlook related, but if anyone knows what I'm doing wrong or where to learn how to do this I'd appreciate it.  
    Monday, October 26, 2015 1:07 AM
  • STDMETHODIMP CConnect::ButtonClicked(IDispatch* ribbon) {
        CCloudDlg dlg;
        CComQIPtr<Office::IRibbonControl> pRibCtrl = ribbon;
        if(NULL != pRibCtrl) {
            CComQIPtr<Outlook::_Explorer> pExplorer;
            CComQIPtr<IDispatch> pRibCtx;
            pRibCtrl->get_Context(&pRibCtx);
            pExplorer = pRibCtx;
            CComQIPtr<IOleWindow> pWindow = pExplorer;
            if(NULL != pWindow)
                pWindow->GetWindow(&m_hWnd);
        }
        if(NULL != m_hWnd)
            dlg.Create(m_hWnd);
    

    The problem here is that dlg.Create results in a modeless dialog. When the CCloudDlg object goes out of scope when the button handler function exits it will be destroyed while the related window still exists.  That is why the error message right before ASSERT says "Object destructed before window destroyed".

    if you intend to use  a modeless dialog you should create it on the heap, not the stack.

    CCloudDlg *pDlg = new CCloudDlg;

    pDlg->Create(m_hWnd);

    You must also include the code to close the window properly.  The default wizard generated dialogs include code to call EndDialog which is for modal dialogs only.  If you intend to use modeless dialogs then you must call DestroyWindow(). Finally, since the CCloudDlg object was created on the heap you will have a memory leak unless you arrange for the CCloudDlg object to delete itself when the modeless dialog window is closed.  You can accomplish this by overriding the virtual OnFinalMessage function as follows

    void CCloudDlg::OnFinalMessage(HWND h)
    {
      delete this;
    }




    • Edited by RLWA32 Monday, October 26, 2015 1:45 AM
    Monday, October 26, 2015 1:43 AM
  • STDMETHODIMP CConnect::ButtonClicked(IDispatch* ribbon) {
        CCloudDlg dlg;
        CComQIPtr<Office::IRibbonControl> pRibCtrl = ribbon;
        if(NULL != pRibCtrl) {
            CComQIPtr<Outlook::_Explorer> pExplorer;
            CComQIPtr<IDispatch> pRibCtx;
            pRibCtrl->get_Context(&pRibCtx);
            pExplorer = pRibCtx;
            CComQIPtr<IOleWindow> pWindow = pExplorer;
            if(NULL != pWindow)
                pWindow->GetWindow(&m_hWnd);
        }
        if(NULL != m_hWnd)
            dlg.Create(m_hWnd);

    The problem here is that dlg.Create results in a modeless dialog. When the CCloudDlg object goes out of scope when the button handler function exits it will be destroyed while the related window still exists.  That is why the error message right before ASSERT says "Object destructed before window destroyed".

    if you intend to use  a modeless dialog you should create it on the heap, not the stack.

    CCloudDlg *pDlg = new CCloudDlg;

    pDlg->Create(m_hWnd);

    You must also include the code to close the window properly.  The default wizard generated dialogs include code to call EndDialog which is for modal dialogs only.  If you intend to use modeless dialogs then you must call DestroyWindow(). Finally, since the CCloudDlg object was created on the heap you will have a memory leak unless you arrange for the CCloudDlg object to delete itself when the modeless dialog window is closed.  You can accomplish this by overriding the virtual OnFinalMessage function as follows

    void CCloudDlg::OnFinalMessage(HWND h)
    {
      delete this;
    }




    That was a beauty! I appreciate you, RLWA32!!
    Monday, October 26, 2015 2:01 AM