0

I have code that executes in InitInstance that checks if my application is already running, and if so brings it to the foreground. Standard code:

if (m_hMutex != nullptr)
{   // indicates running instance
    if (::GetLastError() == ERROR_ALREADY_EXISTS)
    {
        EnumWindows(searcher, (LPARAM)&hOther);

        if (hOther != nullptr)
        {
            ::SetForegroundWindow(::GetLastActivePopup(hOther));

            if (IsIconic(hOther))
            {
                ::ShowWindow(hOther, SW_RESTORE);
            }
        }

        return FALSE; // terminates the creation
    }
}

The above I have no issues with. Recently I added support for double-clicking a file from File Explorer and it open in my software CDialog derived). So I do the following in InitInstance:

CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (PathFileExists(cmdInfo.m_strFileName))
{
    m_bOpenFileFromFileExplorer = true;
    m_strFileToOpenFromFileExplorerPath = cmdInfo.m_strFileName;
}

Then, in my main dialog OnInitDialog I do this:

if (theApp.OpenFileFromFileExplorer())
{
    CString strFileToOpen = theApp.GetFileToOpenFromFileExplorerPath();
    CString strFileExtension = PathFindExtension(strFileToOpen);

    strFileExtension.MakeLower();
    if (strFileExtension == _T(".srr"))
        PostMessage(WM_COMMAND, ID_FILE_OPENREPORT);
    else if (strFileExtension == _T(".mwb"))
        PostMessage(WM_COMMAND, ID_FILE_OPEN_CHRISTIAN_LIFE_AND_MINISTRY_REPORT);
}

Finally, each of my respective handlers for each editor does something like this):

void CMeetingScheduleAssistantDlg::OnFileOpenReport()
{
    CCreateReportDlg dlgReport(this);
    CString          strFilePath, strFileName;

    if (theApp.OpenFileFromFileExplorer())
    {
        strFilePath = theApp.GetFileToOpenFromFileExplorerPath();
        strFileName = PathFindFileName(strFilePath);
        if (strFilePath == _T("") || strFileName == _T(""))
        {
            // Error!
            return;
        }
    }
    else
    {
        CString strTitle, strFilter;

        strTitle.LoadString(IDS_STR_SELECT_SRR_FILE);
        strFilter.LoadString(IDS_STR_SRR_FILTER);

        CFileDialog dlgOpen(TRUE, _T(".SRR"),
            nullptr, OFN_PATHMUSTEXIST | OFN_HIDEREADONLY, strFilter, this);

        dlgOpen.m_ofn.lpstrTitle = strTitle;

        // get a file to open from user
        if (dlgOpen.DoModal() != IDOK)
            return;

        strFilePath = dlgOpen.GetPathName();
        strFileName = dlgOpen.GetFileName();
    }

    // AJT V9.1.0 - Most Recent File List support
    theApp.AddToRecentFileList(strFilePath);

    // tell report we want to open it
    dlgReport.SetFileToOpen(strFilePath, strFileName);

    // display it
    dlgReport.DoModal();

    // AJT V9.1.0 Bug Fix
    SetDayStates(m_calStart);
    SetDayStates(m_calEnd);
}

The other handler works in a similar way. There are no issues with the code implemented and users can double-click a file and it will open in the software with the right editor.

Ofcourse, if my software is already running (but only on the primary dialog) and the user double clicks a file, the duplicate instance will trigger and it will simply bring that window to the foreground.

What I would like to do is this:

Is the duplicate instance on the primary window?
    Bring it to the foreground.
    Trigger to open this file the user has double in this attempted instance.
    Shutdown this instance.
Else
    Bring it to the foreground.
    Not much else we can do since a modal window is open in the other instance.
    So just shut down.
End if

So how do I do that bit:

Is the duplicate instance on the primary window?
    Bring it to the foreground.
    Trigger to open this file the user has double in this attempted instance.
    Shutdown this instance.
Else

?

Update

The problem is that:

HWND hOther = nullptr;
if (DetectRunningInstance(hOther))
{
    DetectFileToOpenFromFileExplorer(); // AJT v20.1.6

    CString strFile = GetFileToOpenFromFileExplorerPath();

    LPCTSTR lpszString = strFile.GetBufferSetLength(_MAX_PATH);
    COPYDATASTRUCT cds;
    cds.dwData = 1;
    cds.cbData = _MAX_PATH;
    cds.lpData = (LPVOID)lpszString;

    DWORD dwResult;
    SendMessageTimeout(hOther, WM_COPYDATA,
            NULL, (LPARAM)(LPVOID)&cds, SMTO_BLOCK, 2000, &dwResult);
    strFile.ReleaseBuffer();


    return FALSE; // Terminates the creation
}

...
...

BOOL CMeetingScheduleAssistantDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
    if (pCopyDataStruct->dwData == nnn)
    {
        LPCTSTR lpszString = (LPCTSTR)(pCopyDataStruct->lpData);
        AfxMessageBox(lpszString);
    }

    return TRUE;
}

The above passes the string, even if the other instance of MSA has a modal window up. So SendMessageTimeout never actual times out.

Got!

if (GetLastActivePopup() != this)
Community
  • 1
  • 1
Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • 1
    You can send arbitrary data to a window using the [WM_COPYDATA](https://learn.microsoft.com/en-us/windows/win32/dataxchg/wm-copydata) message. – IInspectable May 10 '20 at 07:36
  • @IInspectable OK, so in theory that message can be used to pass the `CString` text to the other running instance. But how do we detect if the other running instance is on the primary window (does not have another modal dialog popup active etc.)? – Andrew Truckle May 10 '20 at 07:46
  • @IInspectable If I understand this right, I can use `SendMessageTimeout`. Since, if the duplicate instance does have a popup window activated, it won't be able to process the message and time out. So that would be the route to go. – Andrew Truckle May 10 '20 at 08:15
  • 1
    I'd not even try to determine the state internal to the running instance. Just send an *"open this file, if possible"* message, and have the sender observe the result (either the message handling result, or the timeout in case of `SendMessageTimeout`). – IInspectable May 10 '20 at 10:22
  • I will update with my complete answer once I have it working 100% as I would like. Watch this space. :) – Andrew Truckle May 10 '20 at 13:20

1 Answers1

0

This is what I have so far...

Once I have determined that another instance is already running, and that a file was provided on the command line, I then run this method before I cancel the duplicate instance:

void CMeetingScheduleAssistantApp::TryToOpenFileInOtherInstance(HWND hOtherInstance)
{
    CString strFile = GetFileToOpenFromFileExplorerPath();

    LPCTSTR lpszString = strFile.GetBufferSetLength(_MAX_PATH);
    COPYDATASTRUCT cds;
    cds.dwData = xxxxx;
    cds.cbData = _MAX_PATH;
    cds.lpData = (LPVOID)lpszString;

    DWORD_PTR dwResult;
    if (SendMessageTimeout(hOtherInstance, WM_COPYDATA,
                    NULL, (LPARAM)(LPVOID)&cds, SMTO_BLOCK, 2000, &dwResult) != 0)
    {
        // The message was sent and processed
        if (dwResult == FALSE)
        {
            // The other instance returned FALSE. This is probably because it
            // has a pop-up window open so can't open the file
            ::OutputDebugString(_T("InitInstance::SendMessageTimeout [dwResult was FALSE].\n"));
        }
    }
    else
    {
        DWORD dwError = ::GetLastError();
        if (dwError == ERROR_TIMEOUT)
        {
            // The message timed out for some reason
            ::OutputDebugString(_T("InitInstance::SendMessageTimeout [ERROR_TIMEOUT].\n"));
        }
        else
        {
            // Another unknown error
        }
        CString strError = _T("");
        strError.Format(_T("InitInstance::SendMessageTimeout [%d: %s]\n"), dwError, GetLastErrorAsStringEx(dwError));

        ::OutputDebugString(strError);
    }
    strFile.ReleaseBuffer();
}

In the WM_COPYDATA message handler:

BOOL CMeetingScheduleAssistantDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
    if (pCopyDataStruct->dwData == xxx)
    {
        LPCTSTR lpszString = (LPCTSTR)(pCopyDataStruct->lpData);
        {
            if (GetLastActivePopup() != this) // Popup windows!
            {
                // TODO: Tell user?
                return FALSE;
            }

            theApp.SetFileToOpenFromFileExplorer(lpszString);

            OpenFileFromFileExplorer();
        }
    }

    return TRUE;
}

I was trying to adapt the CopyData approach as described here but could not get it to work — Got it to work and code is shown here.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164