Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] wxNotificationMessage never triggers event callbacks (regarding #180) #181

Closed
baderouaich opened this issue Jun 4, 2022 · 1 comment
Assignees
Labels
bug Something isn't working Fixed
Milestone

Comments

@baderouaich
Copy link
Collaborator

baderouaich commented Jun 4, 2022

Describe the bug

Regarding #180
wxWidgets's wxNotificationMessage::Bind() never triggers the event callbacks.
i assume it's because wxNotificationMessage object goes out of scope then it unbinds all the event callbacks.

To Reproduce

Steps to reproduce the behavior:

void show_notifier(const xtd::ustring& title,
                 const xtd::ustring& message,
                 xtd::delegate<void()> on_notifier_closed)
{
   wxNotificationMessage notifmsg;
   notifmsg.SetTitle(convert_string::to_wstring(title));
   notifmsg.SetMessage(convert_string::to_wstring(message));
   notifmsg.Bind(wxEVT_NOTIFICATION_MESSAGE_DISMISSED, [&](wxCommandEvent& event) {
          /// ================> THIS WILL NEVER BE TRIGGERED  <<===================
          on_notifier_closed();
          xtd::diagnostics::debug::write_line("wxEVT_NOTIFICATION_MESSAGE_DISMISSED\n");
          event.Skip();
        });
  notifmsg.Bind(wxEVT_NOTIFICATION_MESSAGE_CLICK, [&](wxCommandEvent& event) {
          /// ================> THIS WILL NEVER BE TRIGGERED  <<===================
          xtd::diagnostics::debug::write_line("wxEVT_NOTIFICATION_MESSAGE_CLICK\n");
          event.Skip();
        });
   notifmsg.Show(); 
}

Expected behaviour

wxNotificationMessage::Bind to trigger the callback properly.

Workaround

when wxNotificationMessage is instantiated as a class member (has a long life), then it works fine. the problem is that wxNotificationMessage unbinds all callbacks at the destruction. how can we create a wxNotificationMessage and keep bounds alive ?

  • Problem 1: we can't assume when the events will be triggered or wait for them like xtd::forms::message_dialog, we have to register the event callbacks to be triggered later and wait for the callbacks (which also can be ignored, user may not interact with the notification), which leads to Problem 2

  • Problem 2: once the wxNotificationMessage object goes out of scope, it unbinds all the registered event callbacks. also using wxNotificationMessage as a control so we can keep it alive is not possible since it's not a wxWindow.

  • Possible Solutions:

    • store notifications in a static cache (e.g: std::map) with unique id for each notification, then use show(id) and close(id) to display and hide notifications. and for callbacks we use something like xtd::async_callback in xtd::net::sockets::socket.cpp
@baderouaich baderouaich self-assigned this Jun 4, 2022
baderouaich added a commit that referenced this issue Jun 4, 2022
	- started message_notifier system implementation
	- investigating a problem in wxNotificationMessage::Bind() issue #181
@gammasoft71
Copy link
Owner

The only message that doesn't seem to work properly is the wxEVT_NOTIFICATION_MESSAGE_DISMISSED message. The others work fine.

So here is the workaround to make sure you get a message when closing the message by timeout:

#include <wx/wx.h>
#include <wx/notifmsg.h>
#include <wx/generic/notifmsg.h>
#include <wx/msgout.h>

namespace Examples {
  #if defined(__APPLE__)
  using wxOwnNotificationMessageBase = wxGenericNotificationMessage;
  #else
  using wxOwnNotificationMessageBase = wxNotificationMessage;
  #endif

  class wxOwnNotificationMessage : public wxOwnNotificationMessageBase {
  public:
    wxOwnNotificationMessage() : wxOwnNotificationMessage(wxString())  {}
    wxOwnNotificationMessage(const wxString& title, const wxString& message = wxString(), wxWindow *parent = nullptr, int flags = wxICON_INFORMATION) : wxOwnNotificationMessageBase(title, message, parent, flags) {
      autoCloseTimer.Bind(wxEVT_TIMER, [&](wxTimerEvent& event) {
        autoCloseTimer.Stop();
        Close();
        wxPostEvent(this, wxCommandEvent(wxEVT_NOTIFICATION_MESSAGE_DISMISSED));
      });

      Bind(wxEVT_NOTIFICATION_MESSAGE_CLICK, [&](wxCommandEvent& e) {
        autoCloseTimer.Stop();
        e.Skip();
      });
      
      Bind(wxEVT_NOTIFICATION_MESSAGE_ACTION, [&](wxCommandEvent& e) {
        autoCloseTimer.Stop();
        e.Skip();
      });
    }
    
    int32_t TimeoutValue() const noexcept {return timeoutValue;}
    void TimeoutValue(int32_t value) noexcept {timeoutValue = value;}

    bool Show(int timeout = Timeout_Auto) {
      wxOwnNotificationMessageBase::Show(Timeout_Never);
      if (timeout == Timeout_Auto) {
        autoCloseTimer.StartOnce(timeoutValue);
      }
    }
    
  private:
    int32_t timeoutValue = 3000;
    wxTimer autoCloseTimer;
  };

  class MainFrame : public wxFrame {
  public:
    MainFrame() : wxFrame(nullptr, wxID_ANY, "Notification message example") {
      notificationMessage->AddAction(wxID_OK, "Ok");
      notificationMessage->AddAction(wxID_CANCEL, "Cancel");
      notificationMessage->Bind(wxEVT_NOTIFICATION_MESSAGE_ACTION, [&](wxCommandEvent& e) {
        wxMessageOutputDebug().Printf("Action [%d] clicked", e.GetId());
        notificationMessage->Close();
        e.Skip(true);
      });
      
      notificationMessage->Bind(wxEVT_NOTIFICATION_MESSAGE_CLICK, [&](wxCommandEvent& e) {
        wxMessageOutputDebug().Output("Message click");
        e.Skip(true);
      });
      
      notificationMessage->Bind(wxEVT_NOTIFICATION_MESSAGE_DISMISSED, [&](wxCommandEvent& e) {
        wxMessageOutputDebug().Output("Close on timeout");
        e.Skip(true);
      });
      
      button->SetLabel("Notification");
      button->Bind(wxEVT_BUTTON, [&](wxCommandEvent& event) {
        notificationMessage->Show(wxNotificationMessage::Timeout_Auto);
      });
      
      Bind(wxEVT_DESTROY, [&](wxCommandEvent& e) {
        notificationMessage->Close();
        delete notificationMessage;
      });
      
    }
    
    ~MainFrame() {
    }
    
  private:
    wxPanel* panel = new wxPanel(this);
    wxButton* button = new wxButton(panel, wxID_ANY, "Close", {10, 10}, {160, 50});
    wxOwnNotificationMessage* notificationMessage = new wxOwnNotificationMessage("Notification", "This is a notification message", this, wxICON_ERROR);
  };

  class Application : public wxApp {
    bool OnInit() override {
      (new MainFrame())->Show();
      return true;
    }
  };
}

wxIMPLEMENT_APP(Examples::Application);

For the rest, messages will always be received in xtd::message_notifier since it will always contain a handle on the native class. The native class will only be destroyed when xtd::message_notifier is deleted.

@gammasoft71 gammasoft71 added this to the 0.2.0 milestone Jun 23, 2022
Repository owner moved this from Todo to Done in xtd - kanban board Jun 23, 2022
@gammasoft71 gammasoft71 added the bug Something isn't working label Mar 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Fixed
Projects
Development

No branches or pull requests

2 participants