wxWidgets: wxFrame.Destroy() causes exception

BlitzMax Forums/Brucey's Modules/wxWidgets: wxFrame.Destroy() causes exception

Jim Teeuwen(Posted 2009) [#1]
Hey ppl.

first off, I'm a wxWidgets noob, so forgive me if the followig sounds stupid :)

I made myself a custom Dialog.. it implements a notebook with some tabs and a close button. Nothing special rly.

The dialog is instantiated like this:

Local dlg:TAboutDialog = TAboutDialog(New TAboutDialog.Create_(owner, wxID_ANY, Null));
dlg.ShowModal();
dlg.Destroy();


All's well and good. The dialog displays and works. But as soon as I hit the Close button, Destroy() is called as you can see. The issue here is that at this point, the exception occurs.

I tried removing the call to Destroy(), but this only shifts the problem to when the main application is closed altogether.

There is no exception information, just the fact that something went wrong.

Now I figured it may have something to do with my own code in the dialog, because it only started happening after I added some particular tabs.

I commented everything out and started re-enabling it 1 by 1 to see when it went wrong. It turns out that it all goes to hell as soon as I added a 3rd tab. Regardless of the contents of the tab. or in which order they are added.. having the third tab will cause the error when the dialog is closed.

I went through the code that was actually occupying each tab to see what could possibly be causing it, without success. Everything works fine in itself.

It seems though that I am missing some fundamental principal regarding the disposing/destruction of controls in wxWidgets. Is there some special kind of rule regarding the disposing of dialogs and it's contents, Specifically involving the notebook widget?

Also possibly important, I am compiling the program with Threading support. This means i cannot use the standard IDE for compiling, but need to do it manually which means there is no debugger.


For reference sake, here is the code of the entire dialog.
Apart from the calls to App.Locale.Get(....), everything should be self contained. The App.Locale bit retrieves a language-dependant string for display on the UI elements. I am 100% certain this is not causing the issues as it has been working perfectly for a very long time.

To Get an understanding of the problem, you can easily see it happen (I hope) by commenting out the If..Then blocks in TAboutDialog.OnInit which add new pages to the notebook.

Just adding the 'General' page and any other page will work, but as soon as a third arbitrary page is added, things go wrong.

Oh and the 'TMapEx' type is simply a regular TMap, but with a Count() method added because TMap doesn't actually have one for some reason.

Type TAboutInfo
	Field Title:String;
	Field Description:String;
	Field Version:String;
	Field Copyright:String[];
	Field Developers:TMapEx;
	Field Designers:TMapEx;
	Field Translators:TMapEx;
	Field DocWriters:TMapEx;
	Field LicenseFile:String;
	Field IsDebug:Int;
	Field WebSites:String[][];
	Field Credits:String[];
	
	Method New()
		Self.Title = "";
		Self.Description = "";
		Self.Version = "";
		Self.Copyright = New String[0];
		Self.Developers = New TMapEx;
		Self.Designers = New TMapEx;
		Self.Translators = New TMapEx;
		Self.DocWriters = New TMapEx;
		Self.LicenseFile = "";
		Self.IsDebug = False;
		Self.Websites = New String[][0];
		Self.Credits = New String[0];
	End Method
End Type

Type TAboutDialog Extends wxDialog

	Global Info:TAboutInfo;

	Function Create:TAboutDialog(owner:wxWindow, info:TAboutInfo)
		TAboutDialog.Info = Info;
		Return TAboutDialog(New TAboutDialog.Create_(owner, wxID_ANY, Null));
	End Function

	Method OnInit:Int()
		Self.SetTitle(App.Locale.Get("TAboutDialog", "Title"));
		Self.SetSize(450, 340);
		Self.Center();

		Local notebook:wxNotebook = New wxNotebook.Create(Self, wxID_ANY);
		Local msz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local bsz:wxBoxSizer = New wxBoxSizer.Create(wxHORIZONTAL);
		
		Local panel:wxPanel = New TAboutGeneral.Create(notebook);
		notebook.AddPage(panel, panel.GetLabel());
		
		panel = New TAboutLicense.Create(notebook);
		notebook.AddPage(panel, panel.GetLabel());
		
		If(TAboutDialog.Info.Developers.Count() > 0) Then
			panel = New TAboutDevelopers.Create(notebook);
			notebook.AddPage(panel, panel.GetLabel());
		End If
		
		If(TAboutDialog.Info.Designers.Count() > 0) Then
			panel = New TAboutDesigners.Create(notebook);
			notebook.AddPage(panel, panel.GetLabel());
		End If
		
		If(TAboutDialog.Info.Translators.Count() > 0) Then
			panel = New TAboutTranslators.Create(notebook);
			notebook.AddPage(panel, panel.GetLabel());
		End If
		
		If(TAboutDialog.Info.DocWriters.Count() > 0) Then
			panel = New TAboutDocWriters.Create(notebook);
			notebook.AddPage(panel, panel.GetLabel());
		End If
		
		msz.Add(notebook, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5);
		bsz.Add(New wxButton.Create(Self, wxID_OK, App.Locale.Get("Buttons", "Close")), 0, wxALL, 5);
		msz.AddSizer(bsz, 0, wxALIGN_RIGHT);
		
		Self.SetSizer(msz);
	End Method
	
End Type

Type TAboutGeneral Extends wxPanel
	Method OnInit:Int()
		Self.SetLabel(App.Locale.Get("TAboutGeneral", "Title"));
		
		Local build:String = "";
		If(TAboutDialog.Info.IsDebug) Then
			build = " (Debug)";
		End If
		
		Local sz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local ltitle:wxStaticText = New wxStaticText.Create(Self, wxID_ANY, ..
				TString.Format("{0} v{1}{2}", [TAboutDialog.Info.Title, TAboutDialog.Info.Version, build]), ..
					-1, -1, -1, -1, wxST_NO_AUTORESIZE | wxALIGN_CENTRE);

		Local tf:wxFont = Self.GetFont().Copy();
		tf.SetWeight(wxFONTWEIGHT_BOLD);
		tf.SetPointSize(12);
		ltitle.SetFont(tf);
		
		sz.AddStretchSpacer(10);
		sz.Add(ltitle, 0, wxEXPAND | wxALL, 3);
		sz.AddStretchSpacer(3);
		
		For Local copy:String = EachIn TAboutDialog.Info.Copyright
			Local lcopy:wxStaticText = New wxStaticText.Create(Self, wxID_ANY, copy, -1, -1, -1, -1, wxST_NO_AUTORESIZE | wxALIGN_CENTRE);
			sz.Add(lcopy, 0, wxEXPAND | wxALL, 3);
		Next

		sz.AddStretchSpacer(3);
		
		For Local url:String[] = EachIn TAboutDialog.Info.Websites
			Local lwww:wxHyperlinkCtrl = New wxHyperlinkCtrl.Create(Self, wxID_ANY, url[1], url[0], -1, -1, -1, -1, wxHL_ALIGN_CENTRE | wxBORDER_NONE);
			sz.Add(lwww, 0, wxEXPAND | wxALL, 3);
		Next

		sz.AddStretchSpacer(10);
		
		Self.SetSizer(sz);
		tf.Free();
	End Method
	
End Type

Type TAboutLicense Extends wxPanel

	Method OnInit:Int()
		Self.SetLabel(App.Locale.Get("TAboutLicense", "Title"));
		
		Local text:String = "";
		Local fs:TStream = Null;
		Local file:String = App.Config.GetString("paths", "data") + TAboutDialog.Info.LicenseFile;
		
		Try
			fs = ReadFile(file);
			text = fs.ReadString(FileSize(file));
		Catch ex:Object
			text = file;
		End Try
		
		If(fs <> Null) Then
			fs.Close();
		End If
		
		Local sz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local flags:Int = wxTE_MULTILINE | wxTE_READONLY | wxTE_BESTWRAP | wxTE_LEFT;
		Local txt:wxTextCtrl = New wxTextCtrl.Create(Self, -1, text, -1, -1, -1, -1, flags);
		sz.Add(txt, 1, wxEXPAND | wxALL, 3);
		Self.SetSizer(sz);
	End Method

End Type


Type TAboutDevelopers Extends wxPanel

	Method OnInit:Int()
		Self.SetLabel(App.Locale.Get("TAboutDevelopers", "Title"));

		Local sw:wxScrolledWindow = New wxScrolledWindow.Create(Self, wxID_ANY, -1, -1, -1, -1, wxVSCROLL);
		Local sz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local tz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local tf:wxFont = Self.GetFont().Copy();
		tf.SetWeight(wxFONTWEIGHT_BOLD);
		tf.SetPointSize(10);

		tz.AddSpacer(10);
		For Local section:String = EachIn TAboutDialog.Info.Developers.Keys()
			Local ls:wxStaticText = New wxStaticText.Create(sw, wxID_ANY, section + ":", 10, -1, -1, -1, wxST_NO_AUTORESIZE | wxALIGN_LEFT);
			ls.SetFont(tf);
			tz.Add(ls, 0, wxEXPAND | wxLEFT, 10);
			
			Local names:String[] = String[](TAboutDialog.Info.Developers.ValueForKey(section));
			For Local name:String = EachIn names
				Local ls:wxStaticText = New wxStaticText.Create(sw, wxID_ANY, "- " + name, 10, -1, -1, -1, wxST_NO_AUTORESIZE | wxALIGN_LEFT);
				tz.Add(ls, 0, wxEXPAND | wxLEFT, 10);
			Next
			tz.AddSpacer(5);
		Next

		sw.SetScrollbars(10, 10, 100, 300);
		sw.Scroll(0, 0);
		sw.SetSizer(tz);
		
		sz.Add(sw, 1, wxEXPAND | wxALL, 3);
		Self.SetSizer(sz);
		tf.Free();
	End Method

End Type


Type TAboutDesigners Extends wxPanel

	Method OnInit:Int()
		Self.SetLabel(App.Locale.Get("TAboutDesigners", "Title"));

		Local sw:wxScrolledWindow = New wxScrolledWindow.Create(Self, wxID_ANY, -1, -1, -1, -1, wxVSCROLL);
		Local sz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local tz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local tf:wxFont = Self.GetFont().Copy();
		tf.SetWeight(wxFONTWEIGHT_BOLD);
		tf.SetPointSize(10);

		tz.AddSpacer(10);
		For Local section:String = EachIn TAboutDialog.Info.Designers.Keys()
			Local ls:wxStaticText = New wxStaticText.Create(sw, wxID_ANY, section + ":", 10, -1, -1, -1, wxST_NO_AUTORESIZE | wxALIGN_LEFT);
			ls.SetFont(tf);
			tz.Add(ls, 0, wxEXPAND | wxLEFT, 10);
			
			Local names:String[] = String[](TAboutDialog.Info.Designers.ValueForKey(section));
			For Local name:String = EachIn names
				Local ls:wxStaticText = New wxStaticText.Create(sw, wxID_ANY, "- " + name, 10, -1, -1, -1, wxST_NO_AUTORESIZE | wxALIGN_LEFT);
				tz.Add(ls, 0, wxEXPAND | wxLEFT, 10);
			Next
			tz.AddSpacer(5);
		Next

		sw.SetScrollbars(10, 10, 100, 300);
		sw.Scroll(0, 0);
		sw.SetSizer(tz);
		
		sz.Add(sw, 1, wxEXPAND | wxALL, 3);
		Self.SetSizer(sz);
		tf.Free();
	End Method

End Type


Type TAboutTranslators Extends wxPanel

	Method OnInit:Int()
		Self.SetLabel(App.Locale.Get("TAboutTranslators", "Title"));

		Local sw:wxScrolledWindow = New wxScrolledWindow.Create(Self, wxID_ANY, -1, -1, -1, -1, wxVSCROLL);
		Local sz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local tz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local tf:wxFont = Self.GetFont().Copy();
		tf.SetWeight(wxFONTWEIGHT_BOLD);
		tf.SetPointSize(10);

		tz.AddSpacer(10);
		For Local section:String = EachIn TAboutDialog.Info.Translators.Keys()
			Local ls:wxStaticText = New wxStaticText.Create(sw, wxID_ANY, section + ":", 10, -1, -1, -1, wxST_NO_AUTORESIZE | wxALIGN_LEFT);
			ls.SetFont(tf);
			tz.Add(ls, 0, wxEXPAND | wxLEFT, 10);
			
			Local names:String[] = String[](TAboutDialog.Info.Translators.ValueForKey(section));
			For Local name:String = EachIn names
				Local ls:wxStaticText = New wxStaticText.Create(sw, wxID_ANY, "- " + name, 10, -1, -1, -1, wxST_NO_AUTORESIZE | wxALIGN_LEFT);
				tz.Add(ls, 0, wxEXPAND | wxLEFT, 10);
			Next
			tz.AddSpacer(5);
		Next

		sw.SetScrollbars(10, 10, 100, 300);
		sw.Scroll(0, 0);
		sw.SetSizer(tz);
		
		sz.Add(sw, 1, wxEXPAND | wxALL, 3);
		Self.SetSizer(sz);
		tf.Free();
	End Method

End Type

Type TAboutDocWriters Extends wxPanel

	Method OnInit:Int()
		Self.SetLabel(App.Locale.Get("TAboutDocWriters", "Title"));

		Local sw:wxScrolledWindow = New wxScrolledWindow.Create(Self, wxID_ANY, -1, -1, -1, -1, wxVSCROLL);
		Local sz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local tz:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		Local tf:wxFont = Self.GetFont().Copy();
		tf.SetWeight(wxFONTWEIGHT_BOLD);
		tf.SetPointSize(10);

		tz.AddSpacer(10);
		For Local section:String = EachIn TAboutDialog.Info.DocWriters.Keys()
			Local ls:wxStaticText = New wxStaticText.Create(sw, wxID_ANY, section + ":", 10, -1, -1, -1, wxST_NO_AUTORESIZE | wxALIGN_LEFT);
			ls.SetFont(tf);
			tz.Add(ls, 0, wxEXPAND | wxLEFT, 10);
			
			Local names:String[] = String[](TAboutDialog.Info.DocWriters.ValueForKey(section));
			For Local name:String = EachIn names
				Local ls:wxStaticText = New wxStaticText.Create(sw, wxID_ANY, "- " + name, 10, -1, -1, -1, wxST_NO_AUTORESIZE | wxALIGN_LEFT);
				tz.Add(ls, 0, wxEXPAND | wxLEFT, 10);
			Next
			tz.AddSpacer(5);
		Next

		sw.SetScrollbars(10, 10, 100, 300);
		sw.Scroll(0, 0);
		sw.SetSizer(tz);
		
		sz.Add(sw, 1, wxEXPAND | wxALL, 3);
		Self.SetSizer(sz);
		tf.Free();
	End Method

End Type





Jim Teeuwen(Posted 2009) [#2]
Ok. Update. I compiled all this without Threading support and it worked flawlessly.

It appears that there is a problem with the new GC that I can't seem to pinpoint.


DavidDC(Posted 2009) [#3]
I don't think wxMax has threading support (yet?). Still, I wonder what would have happened if you tried Free() instead of Destroy()?

I've tested the two in XP and found Destroy leaked, whereas Free does not (for wxDialogs only - pretty much everything else uses Destroy).


Brucey(Posted 2009) [#4]
I'm wondering if one would need to build the static libraries with the new GC.

It's possible that because of the way that I'm interacting with wxWidgets at the low-level, that issues may be arising with regards the way the new GC is ref counting things.
I've searched for information concerning the use of wxWidgets and the GC, but there is not much to go on.


Jim Teeuwen(Posted 2009) [#5]
For now I'm just running the app without threading. I havn't reached a point where I actually need it anyways.

I'm not sure it's worth it to waste to much time on trying to fix this. At least until the threading makes it out of testing phase anyways. Too much can change to warrant any major fiddling around with wxWidgets, which otherwise works like a charm.