How to manually call button.onClick function

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
ValeV
Experienced Solver
Experienced Solver
Posts: 68
Joined: Wed Jan 30, 2019 1:39 pm

How to manually call button.onClick function

Post by ValeV » Thu Jan 31, 2019 7:11 am

Heyall,
I am going through introduction tutorials (http://wiki.codeblocks.org/index.php/WxSmith_tutorials), so I am fairly new to this.

So as you know, my question is, how to call onButton1Click(wxCommandEvent& event) manually. I made new .cpp file which gets Frame object from [nameOfProject]app class. So I can access the function from this .cpp file, but I don't know how/what to pass as its parameter (event).

Hope you guys can help me out.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2007
Joined: Sun Jan 03, 2010 5:45 pm

Re: How to manually call button.onClick function

Post by PB » Thu Jan 31, 2019 7:27 am

Why would you want to do that? This would be very unusual.

Usually it is the other way around, the event handler calls the user defined function which executes the code triggered by user actions. The user defined function can be called from multiple places, e.g. Save and SaveAs handlers...

ValeV
Experienced Solver
Experienced Solver
Posts: 68
Joined: Wed Jan 30, 2019 1:39 pm

Re: How to manually call button.onClick function

Post by ValeV » Thu Jan 31, 2019 8:20 am

PB wrote:Why would you want to do that? This would be very unusual.

Usually it is the other way around, the event handler calls the user defined function which executes the code triggered by user actions. The user defined function can be called from multiple places, e.g. Save and SaveAs handlers...
I understand, makes more sense.

But I dont know how it solves my problem. Basically I am making a GUI application for using a dongle to communicate with microcontroller with messages. I have a handler function for receiving messages (the dongle driver API calls this function when it receives new message).

In my handler function I have 'printg("new message received\n")' (printg is from tutorial http://wiki.codeblocks.org/index.php/Wx ... ng_Results and i have it set globally). The problem is, this text is never displayed.

I know the function is working ok, because it works in my console application (where i have infinite while loop).

Can you maybe help me with this?

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2007
Joined: Sun Jan 03, 2010 5:45 pm

Re: How to manually call button.onClick function

Post by PB » Thu Jan 31, 2019 9:22 am

I do not know about printg, the function seems rather arcane and not very useful, considering there is wxLog* family of functions.


Sorry, I do not understand the handler issue. So this is not actually a button click handler?

I assume you want to call the microcontroller function with some parameters when a button is clicked? Or is the issue that you do not receive message from the microcontroller? If it is the latter I suppose it depends on how the communication is established - is it a callback function, a windows message....

ValeV
Experienced Solver
Experienced Solver
Posts: 68
Joined: Wed Jan 30, 2019 1:39 pm

Re: How to manually call button.onClick function

Post by ValeV » Thu Jan 31, 2019 11:46 am

PB wrote:I do not know about printg, the function seems rather arcane and not very useful, considering there is wxLog* family of functions.


Sorry, I do not understand the handler issue. So this is not actually a button click handler?

I assume you want to call the microcontroller function with some parameters when a button is clicked? Or is the issue that you do not receive message from the microcontroller? If it is the latter I suppose it depends on how the communication is established - is it a callback function, a windows message....
Sorry about not being understandable, I will try to clarify.

printg() function prints string into wxTextCtrl object. I understand wxLog is not the same. (?)


>Sorry I do not understand the handler issue. So this is not actually a button click handler?

No it's not. Dongle's driver API calls my handler function (basically a callback function) when new message is received by this dongle. My handler function then reads this new message and prints it. This handler exists so I don't have to read new messages in while loop to check if there are new messages. The dongle's driver does it for me and tells me, when new message arrives.
This program is all working in my console application, so not wxWidgets used here.

Now I want to add GUI to this program, so I draw GUI with wxWidgets. Now I want to connect functionality of my console application, and this GUI.

So what I did is make MainFrame from GUI global (so my console module can see it). Now, when message handler is called by dongle's driver API, I want to read new message and print it into wxTextCtrl object's field. In order to do that, I took printg() function from tutorial I mentioned in first post.

Problem is new messages arent being printed.

So...

>I assume you want to call the microcontroller function with some parameters when a button is clicked? Or is the issue that you do not receive message from the microcontroller? If it is the latter I suppose it depends on how the communication is established - is it a callback function, a windows message....

It's the latter. My callback function (handler) is not being called by microcontroller when new messages arrives.

I don't know how to solve this yet.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2007
Joined: Sun Jan 03, 2010 5:45 pm

Re: How to manually call button.onClick function

Post by PB » Thu Jan 31, 2019 1:38 pm

I am not sure if this will be any help, but here is an example of "translating" the callback function call into a custom wxEvent.
viewtopic.php?f=1&t=44428&start=15#p183078

However, this may be an overkill for your case and you could do it simpler.

I would also make sure the callback is called in the context of the GUI thread. Depending on the 3rd party library, this may not be always the case.

But it still does not explain why the callback function is not called. My guess would be that it is called but the printg call gets lost. Did you try placing a breakpoint there to see if this is the case? Or perhaps calling it using CallAfter()? Or maybe just for debugging, use wxYield() to give the UI chance to update...

It is difficult to say without saying any relevant code...

Manolo
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 689
Joined: Mon Apr 30, 2012 11:07 pm

Re: How to manually call button.onClick function

Post by Manolo » Thu Jan 31, 2019 4:46 pm

In pseudo code:

Code: Select all

void MyDongleReceiverCallback(SomeDongleMsgType doMsg) //you do have this, right?
{
    wxString msg = TransformTowxString(doMsg);
    globalTextCtrlPtr->SetValue(msg); //this is a global pointer
]
It can be improved if that callback above is a member of a class that has access to the wxTextCtrl to be updated, e.g. by storing a weak pointer to it, instead of using a global pointer. Global pointers can be dangling.

ValeV
Experienced Solver
Experienced Solver
Posts: 68
Joined: Wed Jan 30, 2019 1:39 pm

Re: How to manually call button.onClick function

Post by ValeV » Fri Feb 01, 2019 8:12 am

Thank you, @PB and @Manolo, for your suggestions and help.

I found out why my callback function didn't work. It's very simple and :oops: : microcontroller was OFF. So dongle couldn't communicate with it. Please dont crucify me, I am not a jedi yet.

Nevertheless, I would like to follow Manolo's suggestion and not use global functions (printg()), it's bad programming.


@PB
>I would also make sure the callback is called in the context of the GUI thread.

I don't understand this. How do you make sure it's called in context of GUI thread?


@Manolo
>callback function

Yes I do have exactly such callback function. MainFrame of GUI is global, but I can't seem to access wxTextCtrl component. I was thinking, in this callback function, to put a simple (Results is wxTextCtrl component's name):

Code: Select all

    MainFrame->Results->AppendText("lala");
But Results is private, so I can't access it. I can use:

Code: Select all

Results->appendText("lala");
somewhere in GUI main.cpp file though. Why can I access it here, but not from dongle callback function?

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2007
Joined: Sun Jan 03, 2010 5:45 pm

Re: How to manually call button.onClick function

Post by PB » Fri Feb 01, 2019 9:55 am

ValeV wrote:@PB
>I would also make sure the callback is called in the context of the GUI thread.

I don't understand this. How do you make sure it's called in context of GUI thread?
If the function is not called in the GUI thread, you should not call any GUI-related methods or bad things may happen. You can check whether it is the GUI thread (= thread in which wxWidgets were initialized) by #including <wx/thread> and in the callback function check the return value of wxThread::IsMain(). If the method returns false you should take this into account.

ValeV
Experienced Solver
Experienced Solver
Posts: 68
Joined: Wed Jan 30, 2019 1:39 pm

Re: How to manually call button.onClick function

Post by ValeV » Fri Feb 01, 2019 11:21 am

PB wrote:
ValeV wrote:@PB
>I would also make sure the callback is called in the context of the GUI thread.

I don't understand this. How do you make sure it's called in context of GUI thread?
If the function is not called in the GUI thread, you should not call any GUI-related methods or bad things may happen. You can check whether it is the GUI thread (= thread in which wxWidgets were initialized) by #including <wx/thread> and in the callback function check the return value of wxThread::IsMain(). If the method returns false you should take this into account.
It returns false, so I am not in GUI thread. Does this mean I shouldn't write some text in wxTextCtrl? What are my options?

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2007
Joined: Sun Jan 03, 2010 5:45 pm

Re: How to manually call button.onClick function

Post by PB » Fri Feb 01, 2019 1:00 pm

ValeV wrote:It returns false, so I am not in GUI thread. Does this mean I shouldn't write some text in wxTextCtrl? What are my options?
If it is just for debugging, you can redirect all wxLog* output e.g. with wxLogTextCtrl or use wxLogWindow.

If you need it for real, there are several options. You can use e.g. wxQueueEvent() or CallAfter() which will be executed in the main thread.

The code below demonstrates using both an event and CallAfter(), as well as redirecting a log. Please notice that in your application the callback function is probably not a member of a wxDialog/wxFrame/etc and therefore you must somehow pass it a pointer to the frame or dialog where you want the data. How you do this depends on callback function, some allow a custom data which makes this easy, some do not and you must use another way (usually a global/static variable, see e.g. wxMIDICallBackWrapper in the first and second code blocks here).

Code: Select all

#include <wx/wx.h>

class MyDialog : public wxDialog
{
public:
    MyDialog() : 
        wxDialog(NULL, wxID_ANY, "Test", wxDefaultPosition, wxSize(800, 500)), 
        m_dataPacketCount(0)
    {                                                      
        wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);

        // this button will communicate the data to m_dataDisplay using an event
        wxButton* buttonUseEvent = new wxButton(this, wxID_ANY, "This button uses wxQueueEvent()");
        mainSizer->Add(buttonUseEvent, wxSizerFlags().Expand().Border());
        buttonUseEvent->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyDialog::OnButtonSendDataEvent, this);

        // this handles the data event
        Bind(wxEVT_THREAD, &MyDialog::OnHandleDataEvent, this);
        
        // this button will use CallAfter() to communicate the data to m_dataDisplay
        wxButton* buttonCallAfter = new wxButton(this, wxID_ANY, "This button uses CallAfter()");
        mainSizer->Add(buttonCallAfter, wxSizerFlags().Expand().Border());
        buttonCallAfter->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyDialog::OnButtonUseCallAfter, this);

        // this is where the data will be displayed
        m_dataDisplay = new wxTextCtrl(this, wxID_ANY, "<No data packet arrived yet>", wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
        m_dataDisplay->SetBackgroundColour(*wxYELLOW);
        mainSizer->Add(m_dataDisplay, wxSizerFlags().Expand().Border());
        
        // this is where all wxLog*() calls will be redirected to
        wxTextCtrl* logCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 
            wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);     
        wxLog::SetActiveTarget(new wxLogTextCtrl(logCtrl)); 
        mainSizer->Add(logCtrl, wxSizerFlags().Expand().Border().Proportion(1));
        
        SetSizer(mainSizer);                 
    }	    
private:    
    size_t m_dataPacketCount;    
    wxTextCtrl* m_dataDisplay;
    
    void UpdateDataDisplay(const wxString& data)
    {
        wxLogMessage("Updating data display to '%s'", data);
        m_dataDisplay->SetValue(data);
    }

    // handler for buttonUseEvent
    void OnButtonSendDataEvent(wxCommandEvent&)
    {
        const wxString data = wxString::Format("Data packet no. %zu", ++m_dataPacketCount);        
        wxThreadEvent threadEvent;
        
        wxLogMessage("Sending custom event with data '%s'", data);

        threadEvent.SetString(data);            
        wxQueueEvent(this, threadEvent.Clone());
    }

    // handler for buttonCallAfter
    void OnButtonUseCallAfter(wxCommandEvent&)
    {
        const wxString data = wxString::Format("Data packet no. %zu", ++m_dataPacketCount);                
        
        wxLogMessage("Calling CallAfter() to set data to '%s'", data);
        CallAfter(&MyDialog::SetDataWithCallAfter, data);
    }

    // handles event sent from OnButtonSendDataEvent()
    void OnHandleDataEvent(wxThreadEvent& evt)
    {
        const wxString data = evt.GetString();                
        
        wxLogMessage("Setting data from OnHandleDataEvent(): '%s'", data);
        UpdateDataDisplay(data);
    }

    // called with CallAfter() from OnButtonUseCallAfter
    void SetDataWithCallAfter(const wxString& data)
    {
        wxLogMessage("Setting data from SetDataWithCallAfter(): '%s'", data);
        UpdateDataDisplay(data);        
    }
};

class MyApp : public wxApp
{
public:	
    bool OnInit()
    {
        MyDialog().ShowModal();
        return false;
    }
}; wxIMPLEMENT_APP(MyApp);
If the data members of wxThreadEvent (wxString, int, long) are not enough to store the data, you can use its Get/SetPayload() methods to pass any data.

Nunki
I live to help wx-kind
I live to help wx-kind
Posts: 197
Joined: Fri Sep 14, 2012 8:26 am
Location: Kontich, Belgium
Contact:

Re: How to manually call button.onClick function

Post by Nunki » Sun Feb 10, 2019 11:02 am

Hi ValeV,

Maybe this reply is a little late, maybe you already have an answer, anyway. The way I understand your question is that when binding your GUI button with a method like onButton1Click(wxCommandEvent& event) this is Ok when the user clicks the button. If, however, you want to call that function you don't have a wxCommandEvent, so how can you call that method. Well the way I solved this is :

Code: Select all

void MyClass::TestButton( wxCommandEvent& event )
{
 MyButtonMehod(....);
 event.Skip();
}
Where MyButtonMethod contains all code to handle the button. Doing it that way, you can now call MyButtonMethod from your code with a wxCommandEvent event.

Hope this sets you on your way, good luck.

Regards,
Nunki

Post Reply