Changing wxToggleButton behaviour Topic is solved

Do you have a typical platform dependent issue you're battling with ? Ask it here. Make sure you mention your platform, compiler, and wxWidgets version.
User avatar
schorsch_76
Earned a small fee
Earned a small fee
Posts: 19
Joined: Wed Mar 03, 2021 5:59 pm

Changing wxToggleButton behaviour

Post by schorsch_76 »

Dear wx developers,

some time ago i wrote an open source application AsciiFlowQt and now i ported it to wxWidgets. You can find the code on github
https://github.com/schorsch1976/AsciiFlowWx
https://github.com/schorsch1976/AsciiFlowQt

The wx version works perfectly fine except on one bug i encountered an i cant find whats wrong: In my preferences i can change the look of the buttons.
https://github.com/schorsch1976/AsciiFl ... e.cpp#L178

I create them all like

Code: Select all

m_tool2btn[Tool::Arrow] =
		new wxToggleButton(this, ID_ButtonToolArrow, "Arrow");
and i change them like:

Code: Select all

void MainFrame::ApplyPrefs()
{
	PreferencesDialog prefs;
	int icon_size = prefs.GetIconSize();
	wxLogDebug("IconSize: %d", icon_size);
	ButtonStyle style = prefs.GetButtonStyle();

	// TODO: This must come from prefs
	wxSize target_size(icon_size, icon_size);

	if (m_labels.empty())
	{
		for (auto &c : m_btns)
		{
			m_labels[c.first] = c.second->GetLabel();
		}
	}

	for (auto &c : m_btns)
	{
		// shorter
		Btn btn = c.first;
		wxAnyButton *p_button = c.second;

		// calculate the new one
		wxBitmap &original_bmp = m_bitmap[btn];
		wxImage original_image = original_bmp.ConvertToImage();

		wxImage scaled_image =
			original_image.Scale(target_size.GetWidth(),
								 target_size.GetHeight(), wxIMAGE_QUALITY_HIGH);
		wxBitmap scaled_bmp(scaled_image);

		p_button->SetBitmap(wxNullBitmap);
		switch (style)
		{
			case ButtonStyle::IconOnly:
				p_button->SetBitmap(scaled_bmp);
				p_button->SetBitmapPosition(wxDirection::wxUP);
				p_button->SetLabel("");
				break;

			case ButtonStyle::TextOnly:
				p_button->SetBitmap(wxNullBitmap);
				break;

			case ButtonStyle::TextBesidesIcon:
				p_button->SetBitmap(scaled_bmp);
				p_button->SetBitmapPosition(wxDirection::wxLEFT);
				p_button->SetLabel(m_labels[btn]);
				break;

			default:
			case ButtonStyle::TextUnderIcon:
				p_button->SetBitmap(scaled_bmp);
				p_button->SetBitmapPosition(wxDirection::wxUP);
				p_button->SetLabel(m_labels[btn]);
				break;
		}
		p_button->Update();
	}

	// force a new Layout
	Layout();
}
If i start with the new look (stored in the config), the buttons look like expected. If i change them repeatedly on the Prefs Dialog the application crashes sometimes and the bitmaps dont disappear.

I use Debian Bullseye/amd64 and wxWidgets 3.0.5.1.

Any ideas or hints?

Thank you!
User avatar
doublemax
Moderator
Moderator
Posts: 19103
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Changing wxToggleButton behaviour

Post by doublemax »

If it crashes, examine the call stack. This should give a hint.

Code: Select all

p_button->Update();
This alone does nothing, you need to call p_button->Refresh() first.
Use the source, Luke!
User avatar
schorsch_76
Earned a small fee
Earned a small fee
Posts: 19
Joined: Wed Mar 03, 2021 5:59 pm

Re: Changing wxToggleButton behaviour

Post by schorsch_76 »

I added the Refresh() call. The dump looks like:

Code: Select all

ASSERT INFO:
../src/gtk/anybutton.cpp(183): assert "image && (((__extension__ ({ GTypeInstance *__inst = (GTypeInstance*) ((image)); GType __t = ((gtk_image_get_type ())); gboolean __r; if (!__inst) __r = 0; else if (__inst->g_class && __inst->g_class->g_type == __t) __r = 1; else __r = g_type_check_instance_is_a (__inst, __t); __r; }))))" failed in GTKDoShowBitmap(): must have image widget

BACKTRACE:
[1] wxAnyButton::GTKDoShowBitmap(wxBitmap const&)
[2] wxAnyButton::DoSetBitmap(wxBitmap const&, wxAnyButtonBase::State)
[3] wxEvtHandler::ProcessEventIfMatchesId(wxEventTableEntryBase const&, wxEvtHandler*, wxEvent&)
[4] wxEventHashTable::HandleEvent(wxEvent&, wxEvtHandler*)
[5] wxEvtHandler::TryHereOnly(wxEvent&)
[6] wxEvtHandler::ProcessEventLocally(wxEvent&)
[7] wxEvtHandler::ProcessEvent(wxEvent&)
[8] wxWindowBase::TryAfter(wxEvent&)
[9] wxEvtHandler::SafelyProcessEvent(wxEvent&)
[10] g_signal_emit_valist
[11] g_signal_emit
[12] g_signal_emit_valist
[13] g_signal_emit
[14] g_signal_emit_valist
[15] g_signal_emit
[16] g_cclosure_marshal_VOID__BOXEDv
[17] g_signal_emit_valist
[18] g_signal_emit
[19] gtk_event_controller_handle_event
[20] g_closure_invoke
[21] g_signal_emit_valist
[22] g_signal_emit
[23] gtk_main_do_event
[24] g_main_context_dispatch
[25] g_main_loop_run
[26] gtk_main
[27] wxGUIEventLoop::DoRun()
[28] wxEventLoopBase::Run()
[29] wxAppConsoleBase::MainLoop()
[30] wxEntry(int&, wchar_t**)
[31] __libc_start_main



ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 7449
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Changing wxToggleButton behaviour

Post by ONEEYEMAN »

Hi,
This backtrace is not useful as it doesn't show where in your code the crash occur.

Also, are you sure the bitmap is successfully read and you have a good object prior to using it?

Thank you.
User avatar
doublemax
Moderator
Moderator
Posts: 19103
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Changing wxToggleButton behaviour

Post by doublemax »

I'm wondering if p_button->SetBitmap(wxNullBitmap); works as expected. Try commenting it out or replacing it with a dummy bitmap and check if it still crashes.
Use the source, Luke!
User avatar
schorsch_76
Earned a small fee
Earned a small fee
Posts: 19
Joined: Wed Mar 03, 2021 5:59 pm

Re: Changing wxToggleButton behaviour

Post by schorsch_76 »

I will try that today evening. I also think, as i compiled it with wxMSW and the appearance was somehow "not native looking" if i should replace these sizers and buttons with wxToolbars as they offer this functionality. (wxTB_NOICONS + wxTB_TEXT).
User avatar
schorsch_76
Earned a small fee
Earned a small fee
Posts: 19
Joined: Wed Mar 03, 2021 5:59 pm

Re: Changing wxToggleButton behaviour

Post by schorsch_76 »

It seems it crashes also. I used a wxBitmap from the wxArtProvider.

Code: Select all

MainFrame::MainFrame(const wxString &title, const wxPoint &pos,
					 const wxSize &size)
	: wxFrame(NULL, wxID_ANY, title, pos, size)
{
	wxBoxSizer *mainsizer = new wxBoxSizer(wxVERTICAL);

	m_empty = wxArtProvider::GetBitmap( wxART_QUESTION);

Code: Select all

void MainFrame::ApplyPrefs()
{
	PreferencesDialog prefs;
	int icon_size = prefs.GetIconSize();
	wxLogDebug("IconSize: %d", icon_size);
	ButtonStyle style = prefs.GetButtonStyle();

	wxSize target_size(icon_size, icon_size);

	if (m_labels.empty())
	{
		for (auto &c : m_btns)
		{
			m_labels[c.first] = c.second->GetLabel();
		}
	}

	for (auto &c : m_btns)
	{
		// shorter
		Btn btn = c.first;
		wxAnyButton *p_button = c.second;

		// calculate the new one
		const wxBitmap &original_bmp = m_bitmap[btn];
		wxImage original_image = original_bmp.ConvertToImage();

		wxImage scaled_image =
			original_image.Scale(target_size.GetWidth(),
								 target_size.GetHeight(), wxIMAGE_QUALITY_HIGH);
		wxBitmap scaled_bmp(scaled_image);

		p_button->SetBitmap(m_empty);
		switch (style)
		{
			case ButtonStyle::IconOnly:
				p_button->SetBitmap(scaled_bmp);
				p_button->SetBitmapPosition(wxDirection::wxUP);
				p_button->SetLabel("");
				break;

			case ButtonStyle::TextOnly:
				p_button->SetBitmap(m_empty);
				break;

			case ButtonStyle::TextBesidesIcon:
				p_button->SetBitmap(scaled_bmp);
				p_button->SetBitmapPosition(wxDirection::wxLEFT);
				p_button->SetLabel(m_labels[btn]);
				break;

			default:
			case ButtonStyle::TextUnderIcon:
				p_button->SetBitmap(scaled_bmp);
				p_button->SetBitmapPosition(wxDirection::wxUP);
				p_button->SetLabel(m_labels[btn]);
				break;
		}
		p_button->Refresh();
		p_button->Update();
	}

	// force a new Layout
	Layout();
}
I also started to use multiple toolbars (but its not yet finished). They seem to be able to handle that, but i always need to remove the buttons and readd them again. Then i can set the new scaled bitmap.

Code: Select all

void MainFrame::ApplyPrefs()
{
	PreferencesDialog prefs;
	int icon_size = prefs.GetIconSize();
	wxLogDebug("IconSize: %d", icon_size);
	ButtonStyle style = prefs.GetButtonStyle();

	wxASSERT(mp_top_toolbar && mp_left_toolbar);
	mp_top_toolbar->ClearTools();
	mp_left_toolbar->ClearTools();

	ResizeBitmaps(icon_size);
	wxSize target_size(icon_size, icon_size);
	mp_top_toolbar->SetToolBitmapSize(target_size);
	mp_left_toolbar->SetToolBitmapSize(target_size);

	AddTopTools();
	AddLeftTools();

	long top_style = mp_top_toolbar->GetWindowStyle();
	long left_style = mp_left_toolbar->GetWindowStyle();

	switch (style)
	{
		case ButtonStyle::IconOnly:
			top_style &= ~wxTB_TEXT;
			top_style &= ~wxTB_NOICONS;
			top_style &= ~wxTB_HORZ_LAYOUT;

			left_style &= ~wxTB_TEXT;
			left_style &= ~wxTB_NOICONS;
			left_style &= ~wxTB_HORZ_LAYOUT;
			break;

		case ButtonStyle::TextOnly:
			top_style |= wxTB_TEXT;
			top_style |= wxTB_NOICONS;
			top_style &= ~wxTB_HORZ_LAYOUT;

			left_style |= wxTB_TEXT;
			left_style |= wxTB_NOICONS;
			left_style &= ~wxTB_HORZ_LAYOUT;
			break;

		case ButtonStyle::TextBesidesIcon:
			top_style |= wxTB_TEXT;
			top_style &= ~wxTB_NOICONS;
			top_style |= wxTB_HORZ_LAYOUT;

			left_style |= wxTB_TEXT;
			left_style &= ~wxTB_NOICONS;
			left_style |= wxTB_HORZ_LAYOUT;
			break;

		default:
		case ButtonStyle::TextUnderIcon:
			top_style |= wxTB_TEXT;
			top_style &= ~wxTB_NOICONS;
			top_style &= ~wxTB_HORZ_LAYOUT;

			left_style |= wxTB_TEXT;
			left_style &= ~wxTB_NOICONS;
			left_style &= ~wxTB_HORZ_LAYOUT;
			break;
	}
	mp_top_toolbar->SetWindowStyle(top_style);
	mp_left_toolbar->SetWindowStyle(left_style);

	mp_top_toolbar->Realize();
	mp_left_toolbar->Realize();
User avatar
doublemax
Moderator
Moderator
Posts: 19103
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Changing wxToggleButton behaviour

Post by doublemax »

Going back to the initial assert:

Code: Select all

must have image widget
Maybe there is a bug that a button that's initially is created without a bitmap, can't be a assigned a bitmap later.

Try to create a minimal sample to narrow down the exact circumstances under which the assert is triggered.
Use the source, Luke!
User avatar
schorsch_76
Earned a small fee
Earned a small fee
Posts: 19
Joined: Wed Mar 03, 2021 5:59 pm

Re: Changing wxToggleButton behaviour

Post by schorsch_76 »

This is a minimal sample that produces this assertion.

Code: Select all

#include <wx/wx.h>

#include <wx/timer.h>
#include <wx/artprov.h>
#include <wx/tglbtn.h>

class MainFrame : public wxFrame
{
public:
	MainFrame(const wxString &title, const wxPoint &pos, const wxSize &size)
		: wxFrame(NULL, wxID_ANY, title, pos, size), m_timer(this), m_size(32)
	{
		wxBoxSizer *mainsizer = new wxBoxSizer(wxVERTICAL);
		
		mp_button = new wxButton(this, wxID_ANY, "New");
		mainsizer->Add(mp_button);
		SetSizerAndFit(mainsizer);
		
		m_raw_bmp = wxArtProvider::GetBitmap(wxART_CDROM);
		m_timer.Start(500);
	}

	void OnTimer(wxTimerEvent &event)
	{	
		mp_button->SetBitmap(wxNullBitmap);
		m_size+=1;

		if (m_size>64)
		{
			m_size=16;
		}
		wxSize target_size(m_size, m_size);
		
		// calculate the new one
		wxImage original_image = m_raw_bmp.ConvertToImage();

		m_used_bmp =
			original_image.Scale(target_size.GetWidth(),
									target_size.GetHeight(), wxIMAGE_QUALITY_HIGH);
	
		switch (m_size%4)
		{
			case 0:
				mp_button->SetBitmap(m_used_bmp);
				mp_button->SetBitmapPosition(wxDirection::wxUP);
				mp_button->SetLabel("");
				break;

			case 2:
				mp_button->SetBitmap(wxNullBitmap);
				break;

			case 3:
				mp_button->SetBitmap(m_used_bmp);
				mp_button->SetBitmapPosition(wxDirection::wxLEFT);
				mp_button->SetLabel("New");
				break;

			default:
				mp_button->SetBitmap(m_used_bmp);
				mp_button->SetBitmapPosition(wxDirection::wxUP);
				mp_button->SetLabel("New");
				break;
		}
		
		mp_button->Refresh();
		mp_button->Update();
	}
	
	wxTimer m_timer;
	wxButton* mp_button;
	int m_size;
	
	wxBitmap m_raw_bmp;
	wxBitmap m_used_bmp;
	
	wxDECLARE_EVENT_TABLE();
};


class MyApp : public wxApp
{
public:
	virtual bool OnInit()
	{
		wxImage::AddHandler(new wxPNGHandler);
		wxImage::AddHandler(new wxXPMHandler);

		MainFrame *frame =
			new MainFrame("wxBitmapBug", wxPoint(50, 50), wxSize(800, 600));
		frame->Show(true);
		return true;
	}
};

wxBEGIN_EVENT_TABLE(MainFrame, wxFrame)
	EVT_TIMER(wxID_ANY, MainFrame::OnTimer)

wxEND_EVENT_TABLE()


wxIMPLEMENT_APP(MyApp);

User avatar
doublemax
Moderator
Moderator
Posts: 19103
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Changing wxToggleButton behaviour

Post by doublemax »

Your definition of "minimal" is slightly different than mine ;)

"minimal" means it should not contain anything that's not needed to reproduce the issue, e.g. the whole wxTimer stuff.

And we also don't know yet if setting any (good) bitmap can trigger the assert, or if it only happens when setting a wxNullBitmap.
Use the source, Luke!
User avatar
schorsch_76
Earned a small fee
Earned a small fee
Posts: 19
Joined: Wed Mar 03, 2021 5:59 pm

Re: Changing wxToggleButton behaviour

Post by schorsch_76 »

I try to condense this a little more ;)
User avatar
schorsch_76
Earned a small fee
Earned a small fee
Posts: 19
Joined: Wed Mar 03, 2021 5:59 pm

Re: Changing wxToggleButton behaviour

Post by schorsch_76 »

I tried to make it smaller. Is this small enough?

It always triggers at the press of the button.

EDIT: This seems to me the minimal reproduction case ... if i remove any of the button->Set...() Functions the crash disappears.

Code: Select all

#include <wx/wx.h>
#include <wx/artprov.h>
#include <wx/tglbtn.h>

class MainFrame : public wxFrame
{
public:
	MainFrame(const wxString &title, const wxPoint &pos, const wxSize &size)
		: wxFrame(NULL, wxID_ANY, title, pos, size)
	{
		mp_button = new wxButton(this, wxID_ANY, "New");
		
		m_raw_bmp = wxArtProvider::GetBitmap(wxART_CDROM);
		mp_button->SetBitmap(m_raw_bmp);
		
		mp_button->Bind(wxEVT_BUTTON, [this](wxEvent&)
		{
			mp_button->SetLabel("");
			mp_button->SetBitmap(m_raw_bmp);

			mp_button->Refresh();
			mp_button->Update();
		});
	}
	wxButton* mp_button;
	wxBitmap m_raw_bmp;
};

class MyApp : public wxApp
{
public:
	virtual bool OnInit()
	{
		MainFrame *frame =
			new MainFrame("wxBitmapBug", wxPoint(50, 50), wxSize(800, 600));
		frame->Show(true);
		return true;
	}
};
wxIMPLEMENT_APP(MyApp);


User avatar
schorsch_76
Earned a small fee
Earned a small fee
Posts: 19
Joined: Wed Mar 03, 2021 5:59 pm

Re: Changing wxToggleButton behaviour

Post by schorsch_76 »

Even if i change the order of the SetLabel()/SetBitmap() functions the assert goes away.... :|
User avatar
doublemax
Moderator
Moderator
Posts: 19103
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Changing wxToggleButton behaviour

Post by doublemax »

The sample is fine, thx. But unfortunately it doesn't crash for me, neither under Windows, nor under Ubuntu. But i tested with the latest wxWidgets from Github.

Do you still get the same assert with the same call stack?
What happens if you just remove the mp_button->Update(); call?
Use the source, Luke!
User avatar
schorsch_76
Earned a small fee
Earned a small fee
Posts: 19
Joined: Wed Mar 03, 2021 5:59 pm

Re: Changing wxToggleButton behaviour

Post by schorsch_76 »

The assertion line is the same:
../src/gtk/anybutton.cpp(183)

EDIT: It also crashes without the update call...
I compile wx from master and rebuild the test with that....

I think it is the same callstack:

Code: Select all

ASSERT INFO:
../src/gtk/anybutton.cpp(183): assert "image && (((__extension__ ({ GTypeInstance *__inst = (GTypeInstance*) ((image)); GType __t = ((gtk_image_get_type ())); gboolean __r; if (!__inst) __r = 0; else if (__inst->g_class && __inst->g_class->g_type == __t) __r = 1; else __r = g_type_check_instance_is_a (__inst, __t); __r; }))))" failed in GTKDoShowBitmap(): must have image widget

BACKTRACE:
[1] wxAnyButton::GTKDoShowBitmap(wxBitmap const&)
[2] wxAnyButton::DoSetBitmap(wxBitmap const&, wxAnyButtonBase::State)
[3] wxEvtHandler::ProcessEventIfMatchesId(wxEventTableEntryBase const&, wxEvtHandler*, wxEvent&)
[4] wxEvtHandler::SearchDynamicEventTable(wxEvent&)
[5] wxEvtHandler::TryHereOnly(wxEvent&)
[6] wxEvtHandler::ProcessEventLocally(wxEvent&)
[7] wxEvtHandler::ProcessEvent(wxEvent&)
[8] wxEvtHandler::SafelyProcessEvent(wxEvent&)
[9] g_signal_emit_valist
[10] g_signal_emit
[11] g_signal_emit_valist
[12] g_signal_emit
[13] g_signal_emit_valist
[14] g_signal_emit
[15] g_cclosure_marshal_VOID__BOXEDv
[16] g_signal_emit_valist
[17] g_signal_emit
[18] gtk_event_controller_handle_event
[19] g_closure_invoke
[20] g_signal_emit_valist
[21] g_signal_emit
[22] gtk_main_do_event
[23] g_main_context_dispatch
[24] g_main_loop_run
[25] gtk_main
[26] wxGUIEventLoop::DoRun()
[27] wxEventLoopBase::Run()
[28] wxAppConsoleBase::MainLoop()
[29] wxEntry(int&, wchar_t**)
[30] __libc_start_main


Post Reply