Contents

Custom widgets

Have you ever looked at an application and wondered, how a particular gui item was created? Probably every wannabe programmer has. Then you were looking at a list of widgets provided by your favourite gui library. But you couldn't find it. Toolkits usually provide only the most common widgets like buttons, text widgets, sliders etc. No toolkit can provide all possible widgets.

There are actually two kinds of toolkits. Spartan toolkits and heavy weight toolkits. The FLTK toolkit is a kind of a spartan toolkit. It provides only the very basic widgets and assumes, that the programemer will create the more complicated ones himself. wxWidgets is a heavy weight one. It has lots of widgets. Yet it does not provide the more specialized widgets. For example a speed meter widget, a widget that measures the capacity of a CD to be burned (found e.g. in nero). Toolkits also don't usually have charts.

Programmers must create such widgets by themselves. They do it by using the drawing tools provided by the toolkit. There are two possibilities. A programmer can modify or enhance an existing widget. Or he can create a custom widget from scratch.

Here I assume, you have read the chapter on the Device Contexts.

The Burning Widget

This is an example of a widget, that we create from scratch. This widget can be found in various media burning applications, like Nero Burning ROM.

widget.bmx
SuperStrict Import wx.wxPanel Global num:Int[] = [75, 150, 225, 300, 375, 450, 525, 600, 675 ] Type Widget Extends wxPanel Field m_parent:wxPanel Field cur_width:Int Method Create:Widget(parent:wxWindow, id:Int = -1, x:Int = -1, y:Int = -1, .. w:Int = -1, h:Int = -1, style:Int = wxTAB_TRAVERSAL) Return Widget(Super.Create(parent, id, x, y, w, 30, wxSUNKEN_BORDER)) End Method Method OnInit() m_parent = wxPanel(GetParent()) ConnectAny(wxEVT_PAINT, OnPaint) ConnectAny(wxEVT_SIZE, OnSize) End Method Method SetCurWidth(width:Int) cur_width = width End Method Function OnSize(event:wxEvent) Local panel:Widget = Widget(event.parent) panel.Refresh() End Function Function OnPaint(event:wxEvent) Local panel:Widget = Widget(event.parent) Local font:wxFont = New wxFont.CreateWithAttribs(9, wxFONTFAMILY_DEFAULT, .. wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, False, .. "Courier 10 Pitch") Local dc:wxPaintDC = New wxPaintDC.Create(panel) dc.SetFont(font) Local width:Int, height:Int panel.GetSize(width, height) Local stepSize:Int = width / 10.0 Local till:Int = ((width / 750.0) * panel.cur_width) Local full:Int = ((width / 750.0) * 700) If panel.cur_width >= 700 Then dc.SetPen(New wxPen.CreateFromColour( .. New wxColour.Create(255, 255, 184))) dc.SetBrush(New wxBrush.CreateFromColour( .. New wxColour.Create(255, 255, 184))) dc.DrawRectangle(0, 0, full, 30) dc.SetPen(New wxPen.CreateFromColour( .. New wxColour.Create(255, 175, 175))) dc.SetBrush(New wxBrush.CreateFromColour( .. New wxColour.Create(255, 175, 175))) dc.DrawRectangle(full, 0, till - full, 30) Else dc.SetPen(New wxPen.CreateFromColour( .. New wxColour.Create(255, 255, 184))) dc.SetBrush(New wxBrush.CreateFromColour( .. New wxColour.Create(255, 255, 184))) dc.DrawRectangle(0, 0, till, 30) End If dc.SetPen(New wxPen.CreateFromColour(New wxColour.Create(90, 80, 60))) For Local i:Int = 1 To num.length dc.DrawLine(i * stepSize, 0, i * stepSize, 6) dc.GetTextExtent(num[i-1], width, height) dc.DrawText(num[i-1], i * stepsize - width/2, 8) Next dc.Free() End Function End Type
burning.bmx <- Open source
SuperStrict Framework wx.wxApp Import wx.wxFrame Import wx.wxSlider Import "widget.bmx" New MyApp.Run() Const ID_SLIDER:Int = 1 Type MyApp Extends wxApp Method OnInit:Int() Local open:Burning = Burning(New Burning.Create(Null, .. wxID_ANY, "The Burning Widget", -1, -1, 350, 200)) open.Show(True) Return True End Method End Type Type Burning Extends wxFrame Field m_slider:wxSlider Field m_wid:Widget Method OnInit() Local panel:wxPanel = New wxPanel.Create(Self, wxID_ANY) Local centerPanel:wxPanel = New wxPanel.Create(panel, wxID_ANY) m_slider = New wxSlider.Create(centerPanel, ID_SLIDER, 75, 0, 750, -1, -1, .. 150, -1, wxSL_LABELS) Local vbox:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL) Local hbox:wxBoxSizer = New wxBoxSizer.Create(wxHORIZONTAL) Local hbox2:wxBoxSizer = New wxBoxSizer.Create(wxHORIZONTAL) Local hbox3:wxBoxSizer = New wxBoxSizer.Create(wxHORIZONTAL) m_wid = New Widget.Create(panel, wxID_ANY) m_wid.SetCurWidth(75) hbox.Add(m_wid, 1, wxEXPAND) hbox2.Add(centerPanel, 1, wxEXPAND) hbox3.Add(m_slider, 0, wxTOP | wxLEFT, 35) centerPanel.SetSizer(hbox3) vbox.AddSizer(hbox2, 1, wxEXPAND) vbox.AddSizer(hbox, 0, wxEXPAND) panel.SetSizer(vbox) m_slider.SetFocus() Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED, OnScroll) Centre() End Method Function OnScroll(event:wxEvent) Local frame:Burning = Burning(event.parent) frame.m_wid.SetCurWidth(frame.m_slider.GetValue()) frame.m_wid.Refresh() End Function End Type

We put a wxPanel on the bottom of the window and draw the entire widget manually. All the important code resides in the OnPaint() method of the Widget class. This widget shows graphically the total capacity of a medium and the free space available to us. The widget is controlled by a slider widget. The minimum value of our custom widget is 0, the maximum is 750. If we reach value 700, we began drawing in red colour. This normally indicates overburning.

 Local width:Int, height:Int
 panel.GetSize(width, height)
 ... 
 Local till:Int = ((width / 750.0) * cur_width)
 Local full:Int = ((width / 750.0) * 700)

We draw the widget dynamically. The greater the window, the greater the burning widget. And vice versa. That is why we must calculate the size of the wxPanel onto which we draw the custom widget. The till parameter determines the total size to be drawn. This value comes from the slider widget. It is a proportion of the whole area. The full parameter determines the point, where we begin to draw in red color. Notice the use of floating point arithmetics. This is to achieve greater precision.

The actual drawing consists of three steps. We draw the yellow or red and yellow rectangle. Then we draw the vertical lines, which divide the widget into several parts. Finally, we draw the numbers, which indicate the capacity of the medium.

Function OnSize(event:wxEvent)
  Local panel:Widget = Widget(event.parent)
  panel.Refresh()
End Function

Every time the window is resized, we refresh the widget. This causes the widget to repaint itself.

dc.Free()

Don't forget to Free() the device context when you are finished with it.

Function OnScroll(event:wxEvent)
  Local frame:Burning = Burning(event.parent)

  frame.m_wid.SetCurWidth(frame.m_slider.GetValue())
  frame.m_wid.Refresh()
End Function

If we scroll the thumb of the slider, we get the actual value and save it into the cur_width variable. This value is used, when the burning widget is drawn. Then we cause the widget to be redrawn.


Burning Widget
Figure: Burning Widget