Skip to content

Control Sizes On Linux

With the myriad of different Window Managers and themes on Linux, and personal preferences, you can be assured that your UI will look different from one Linux user to the next. The main challenge of being a native app is trying to normalize the UI experience across different platforms (yes, even different Linux distros).

As each platform has their own native default control sizes and such, your app will most likely need to be adjusted accordingly. On Windows and OS X this almost never changes, so thankfully a 22 pixel high PushButton would look just fine on OS X, as it does on Windows. The problem on Linux is that you have different themes that dictate how much padding should go into a control, and the much larger default font size.

MyButtonScreenshot.png

Obtaining the default control size

Most people I’ve seen will hardcode a default control size (usually just the height), which probably works most of the time, and is good enough for many of the most popular Linux distros available. The problem is that it’s not 100% accurate, and doesn’t really account for the width (although you could estimate the width by checking the Graphics.StringWidth of the control’s caption and adding whatever extra padding you think is required). The better solution however, would be to obtain the default control size, which GTK+ (the toolkit that drives the UI in our Linux framework) will give us. GTK+ also relies on the Window Manager to report this information so it doesn’t know it until the control is “realized.” This is an important distinction for when we can actually request the suggested size of the control.

Show me the code!

The code to obtain the desired size is quite simple:

Declare Sub gtk_widget_get_requisition Lib "libgtk-x11-2.0" _
  (widget As Integer, ByRef requisition As GtkRequisition)
// GtkRequisition is a simple structure with just two members:
// Width As Integer, and Height As Integer
Dim req As GtkRequisition
gtk_widget_get_requisition(PushButton1.Handle, req)

The slightly harder part is figuring out when the widget is setup enough so that this returns something usable. If you had pasted that code into PushButton1’s Open event for example, you’d get back a Width/Height of 0. Thankfully it’s not terribly complex, we just need to hook into the widget’s “realize” event in the Open event:

Declare Sub g_signal_connect_data Lib "libgobject-2.0" _
  (instance As Integer, signal As CString, _
  callback As Ptr, data As Ptr, destroy_data As Ptr, _
  connectFlags As Integer)
g_signal_connect_data(PushButton1.Handle, "realize", _
  AddressOf RealizeCallback, Nil, Nil, 0)

Then add a shared method RealizeCallback(widget As Integer, data As Ptr) which will get triggered when the widget is setup. For sake of completeness you could add something really simple in the RealizeCallback to update the appropriate controls that have this event connected:

Declare Sub gtk_widget_get_requisition Lib "libgtk-x11-2.0" _
   (widget As Integer, ByRef requisition As GtkRequisition)
Dim req As GtkRequisition
gtk_widget_get_requisition(widget, req)
For iter As Integer = 0 To Window1.ControlCount - 1
  If Window1.Control(iter) IsA RectControl Then
    Dim rc As RectControl = RectControl(Window1.Control(iter))
    If rc.Handle = widget Then
      rc.Width = req.Width
      rc.Height = req.Height
    End If
  End If
Next

Conclusion

The main purpose of this blog post was to demonstrate how you can obtain the default control size on Linux, but of course it doesn’t mention anything about how your window size/layout may change because of it, that’s another tricky bit which can be even more annoying to solve. For example, now that your ComboBox is 15 pixels taller, how does that affect controls around it? While these problems are solvable, the future of Auto-Layout will greatly reduce this kind of headache and custom coding. I think we’re all looking forward to that day.