Custom Controls in wxHaskell (part 2)

It may save you some typing to know that once this series is complete, I shall be publishing a Cabalized version of the Diff control on Hackage.

Subclassing the control

Witness types are used to represent the class hierarchy of the underlying wxWidgets library. The idea is that, for example, a Panel, which is a descendent of Window, can use all functions which accept a Window type.

Creating the child windows

Create an instance of a diffViewer control.

In this case we create a panel as a child of the provided parent window, and set its style flags to indicate that the control will expand (horizontally and vertically) into the space allocated to it. We use the cast operator to convert the created panel and its properties into a DiffViewer related types. The cast operator is very dangerous, and should be used with great care (this is pretty much the only time you should need it in your wxHaskell life) – it operates essentially like the C cast operator!

The diffViewer’ function does most of the work of creating and configuring the child windows in the control. These are all children of the panel, which is treated as the ‘owner’ of the control, and is the only window whose identity need be made visible outside of the control implementation.

A few things to note:

the diff output will be displayed on windows f1 and f2 – these use the Window type as we are going to take responsibility for painting this part of the control;

we create and manage scroll bars manually – this is because we wish to use the same scroll bars to scroll both of the windows containing diff information;

we are constructing a layout manually from sizers (call to buildLayout).

Laying out the child windows

The wxHaskell layout implementation is buggy in some circumstances (it doesn’t seem to handle resizes as I would expect when window size exceeds minsize). Since we want the control to follow the size hints given by the owning application, we will use sizers to create a manual layout. In theory, wxHaskell layout should behave identically, but it doesn’t – that’s a bug to go and look for another day…

Since wxHaskell was originally designed to abstract the creation of sizers using layout, this code is rather low level, using functions from WXCore – you would be forgiven for thinking that it is just C++ implemented in Haskell, and that is essentially exactly what it is – most of the functions in WXCore are Haskell wrappers around the wxWidgets C++ API, which has the benefit that you can use the wxWidgets C++ API documentation to help to understand what most WXCore functions do.

The last two lines: the calls to windowLayout and windowFit are critical, and should be called before the application which uses the Diff control performs its own layout (they set the constraints for size which the application should respect when setting the size of its own windows).

Scroll bars

In many cases, very little handling is required for scroll bars in wxHaskell since many of the common controls contain all the handling required for most purposes. However, as mentioned earlier, we are going to use a single set of control bars to control two client windows (one will contain the ‘original’ text and the other will contain the ‘updated’ text).

This code should serve as a simple example of custom scroll bar handling in wxHaskell.

Here we configure a custom event handler which provides the same handling for all scroll bar events. In more demanding applications (e.g. where the wxEVT_SCROLL_THUMBTRACK would cause too much processing to give smooth operation), you may want to do something a little different, perhaps by defining separate event handlers for different scroll bar events.

Define the ‘on scroll’ event handler for the scroll bars. In this case we can live with using the same event handler for both vertical and horizontal scroll bars as we will be updating the entire client area of the controlled windows on each scroll event (this is not too onerous, at least on my machine). This means that we just need to inform the parent window to refresh (i.e. repaint) the entire window next time the UI gets a chance to do so.

Like this:

Related

First, let me congratulate you on taking this initiative – it should be well worthwile. I have had some thoughts about what support wxHaskell newbies need to
get going.

First a suggestion – how about a screenshot of the finished control ? It would help
visualize what is being put together.

Now, I’d like to pick up on some general aspects of what you wrote.

In Part 1 you said “The control I am building is subclassed from Panel. This is probably a good choice for most controls as it can contain a top level sizer and many children.”
In Part 2 (Creating the child windows) you say “these use the Window type as we are going to take responsibility for painting this part of the control;”

The issue I have with wxHaskell is not having clear documentation about what
widgets do what and how they should be put together. For example I had a scroll-bar
bug that I posted on sourceforge – ultimately I discovered that setting a “layout”
attribute for the scrolled window caused the strange behaviour, which disappeared if I used a “size” attribute. So the big question is, where is this documented?

The wxWidgets website doesn’t seem to help and neither did the wxWidget C++ book.

To be honest, the wider API documentation isn’t all that it could be. In particular, I have never managed to really get my head around the Layout type (wxHaskell was originally written by Daan Leijen, who is a far more proficient Haskell designer than I am).

I believe there are a few subtle bugs in Layout, and I’m starting to work my way through this part of the code in far more detail in the hope of understanding and documenting it more thoroughly.

I’ll try to do a posting about the basic anatomy of an application, however. This is a good idea.

Seems like it may be useful to also present the high-level layout-based version of buildLayout even if wxHaskell does the wrong thing with it in practice. The idea would be “this is the code we /would/ write…”

It’d be a shame for people to glance at this and walk away with the impression that you have to write that sort of low-level code in wxHaskell.