Search code examples
c++c++buildervcl

When does a VCL application get its Application->MainForm->Handle?


My application populates some panels by means of incoming messages, using the SendStructMessage() function in Message.hpp.

SendStructMessage() need a valid windows handle to send to.

I have encapsulated the SendStrucMessage() in a function, like this:

bool sendAppMessage(ApplicationMessageEnum msgID, void* s)
{
    if(!Application || !Application->MainForm || !Application->MainForm->Handle)
    {
        Log(lError) << "Failed to get a valid handle when trying to send application message";
        return false;
    }
    HWND h = Application->MainForm->Handle;

    AppMessageStruct data;
    data.mMessageEnum = msgID;
    data.mData = s;

    LRESULT res =  SendStructMessage(h, UWM_MESSAGE, 0, &data);
    if(res)
    {
        Log(lError) << "Sending message: "<<msgID<<" was unsuccesful";
        return false;
    }

    return true;
}

Trying to call this from either the MainForm's OnShow or OnCreate event doesn't work, as in either case the Application->MainForm->Handle is still NULL.

My question is, in a VCL application's startup phase, where can one be sure that the Application->MainForm->Handle is actually created?

Currently I kick off a Timer checking for a valid handle:

void __fastcall TMain::WaitForHandleTimerTimer(TObject *Sender)
{
    if(Application->MainForm->Handle)
    {
        WaitForHandleTimer->Enabled = false;

        //Send a message to main ui to update sequence shortcuts
        if(sendAppMessage(abSequencerUpdate) != true)
        {
            Log(lDebug)<<"Sending sequencer update to UI was unsuccesful";
        }
    }
}

Is there a better way?


Solution

  • The TWinControl::Handle property getter creates a new HWND at the time the property is read, if an HWND hasn't already been created yet. If an error occurs while creating the HWND, an exception will be thrown.

    So, your !Handle condition will always be false, because the Handle property can never return NULL (the WindowHandle property can, though).

    bool sendAppMessage(ApplicationMessageEnum msgID, void* s)
    {
        if (!((Application) && (Application->MainForm)))
        {
            Log(lError) << "Failed to get a valid handle when trying to send application message";
            return false;
        }
    
        AppMessageStruct data;
        data.mMessageEnum = msgID;
        data.mData = s;
    
        LRESULT res = SendStructMessage(Application->MainForm->Handle, UWM_MESSAGE, 0, &data);
        if (res)
        {
            Log(lError) << "Sending message: " << msgID << " was unsuccesful";
            return false;
        }
    
        return true;
    }
    

    If you want to check if the Handle has been created without actually creating it, use the Form's HandleAllocated() method:

    bool sendAppMessage(ApplicationMessageEnum msgID, void* s)
    {
        if (!((Application) && (Application->MainForm) && (Application->MainForm->HandleAllocated())))
        {
            Log(lError) << "Failed to get a valid handle when trying to send application message";
            return false;
        }
    
        AppMessageStruct data;
        data.mMessageEnum = msgID;
        data.mData = s;
    
        LRESULT res = SendStructMessage(Application->MainForm->Handle, UWM_MESSAGE, 0, &data);
        if (res)
        {
            Log(lError) << "Sending message: " << msgID << " was unsuccesful";
            return false;
        }
    
        return true;
    }
    

    Otherwise, don't use SendMessage()/SendStructMessage() at all. You can call the Form's Perform() method instead, which will deliver the message directly to the Form's assigned WindowProc without requiring any HWND at all:

    bool sendAppMessage(ApplicationMessageEnum msgID, void* s)
    {
        if (!((Application) && (Application->MainForm))
        {
            Log(lError) << "Failed to get a valid handle when trying to send application message";
            return false;
        }
    
        AppMessageStruct data;
        data.mMessageEnum = msgID;
        data.mData = s;
    
        LRESULT res = Application->MainForm->Perform(UWM_MESSAGE, 0, (LPARAM)&data);
        if (res)
        {
            Log(lError) << "Sending message: " << msgID << " was unsuccesful";
            return false;
        }
    
        return true;
    }
    

    Alternatively, consider removing the MainForm dependency from sendAppMessage(). You can send to Application->Handle instead, and then have the MainForm register a callback method using Application->HookMainWindow().

    bool sendAppMessage(ApplicationMessageEnum msgID, void* s)
    {
        if (!((Application) && (Application->Handle))
        {
            Log(lError) << "Failed to get a valid handle when trying to send application message";
            return false;
        }
    
        AppMessageStruct data;
        data.mMessageEnum = msgID;
        data.mData = s;
    
        LRESULT res = SendStructMessage(Application->Handle, UWM_MESSAGE, 0, &data);
        if (res)
        {
            Log(lError) << "Sending message: " << msgID << " was unsuccesful";
            return false;
        }
    
        return true;
    }
    
    __fastcall TMainForm::TMainForm(TComponent *Owner)
        : TForm(Owner)
    {
        Application->HookMainWindow(&AppMessage);
    }
    
    __fastcall TMainForm::~TMainForm()
    {
        Application->UnhookMainWindow(&AppMessage);
    }
    
    bool __fastcall TMainForm::AppMessage(TMessage &Message)
    {
        if (Message.Msg == UWM_MESSAGE)
        {
            WindowProc(Message);
            return true;
        }
        return false;
    }