GUI interface question: scroll bars

Started by
8 comments, last by Telastyn 17 years, 1 month ago
Greetings! I've been working on my own OpenGL GUI to help expand my knowledge of C++ and OpenGL. I've come across a design issue that I could use some help with. I've got a ListBox widget that has a ScrollBar widget. As elements get added and removed from the ListBox, or as the user scrolls up and down through the list, the ListBox updates the ScrollBar so that the ScrollBar knows how to draw itself (mainly, how large the slider should be and where it's located). The problem I'm having is I'm not sure how to get the ScrollBar to relay information back up to the ListBox. If the user clicks on the arrow buttons, or the track, or the slider, what interface should the ScrollBar use to tell the ListBox "Go up one element in the list" or "Go up a whole page", etc?
Advertisement
How does your UI handle normal buttons?

The arrow buttons are just normal buttons bound to up(1), down(1) for the ListBox's start element. Changing position is similarly just bound to a ListBox function which takes a index or percentage (since the scrollbar knows that).


Perhaps I'm misunderstanding your problem...
Normal buttons (such as 'OK' and 'Cancel' buttons) are handled special. They are the only widgets which can perform user-defined actions/functions.

Buttons within other widgets (like a button as part of a spinner text box) interact directly with the widget. If the Up button of a spinner is pressed, it increments the value in the text box. It's part of the same widget and has knowledge/access to everything it needs to perform the desired action.

The difference between the above example and my question is that in the above example the button is part of the over-all spinner widget and so has knowledge of what exactly should happen when it is pressed. In my question, the ScrollBar is a widget unto itself. When the Up button is pressed, it doesn't know exactly what that means to the ListBox, ie, it doesn't have access to the ListBox internals. Also, other widgets (like DropDowns or TextFields) can have ScrollBars, so the interface will have to be generic enough to work with any widget that may contain a Scrollbar. I'm looking for some sort of interface where the ScrollBar can tell the widget that contains it "Up" was pressed, or "Page Down", etc.

Right now, the ScrollBar doesn't have a pointer to the ListBox. I can change this, but since I need to keep it generic, I would have to give it a pointer to the widget base class. This way it will work with other widgets (like a DropDown, etc). However, it doesn't seem right to include in the widget base class methods like "Up" and "Page Up" since those methods won't make sense to all widgets.

Did I make my question more clear?
So why are those special?


Anyways what you're probably looking for is something like this:
struct Button{    boost::function   OnClick;};Button upArrow;upArrow.OnClick = // code to boost::bind a ListBox instance with ListBox::Up


Forgive me, it's been a little while since I've used much binding in C++ but you should get the idea. The only interface you need is a functor which takes the correct parameters. The functor then can store what needs done where.
Quote:Original post by Telastyn
So why are those special?


Normal buttons are special because they are the only widgets who's action is defined else where using function pointers. In other words, I have a file called GUI_Functions.cpp where all the button widget pressing functionality is coded. Each button widget can be linked to a function in GUI_Functions.cpp. All other widgets have their actions/functionality defined within the widget itself.

However, this isn't really my problem. My problem is how to interface a specific widget (ScrollBar) with other widgets (ListBox, DropDown, etc) in a "has-a" relationship. Let me try to break it down some more with some examples of how my widget heirarchy looks.

WidgetBase class -> ScrollBar class (abstract) -> VerticleScrollBar class
WidgetBase class -> ListBox class "has-a" VerticleScrollBar class
WidgetBase class -> DropDown class "has-a" VerticleScrollBar class
WidgetBase class -> Button class (uses function pointers)
WidgetBase class -> TextBox class -> Spinner class

The Spinner widget has buttons (up and down). However, they are not Buttons. They are an integrated part of the Spinner class and not related to the Button class.

The ListBox widget does not have buttons by itself, but it does through having a VerticleScrollBar member object. I'm looking for a design scheme where the VerticleScrollBar member object can give information to the class that contains it, whether it be a ListBox or a DropDown, etc.

One method would be to give the VerticleScrollBar a 'this' pointer to the ListBox casted as a WidgetBase. But this would require either: A) the VerticleScrollBar to figure out what derived class the WidgetBase pointer really is, cast it, and call ListBox specific functions, or: B) include into WidgetBase functions the VerticleScrollBar can call. I don't like 'A' because it requires the VerticleScrollBar to specifically know about ListBoxes and DropDowns and TextFields, etc. I don't like 'B' because it includes into WidgetBase functions that don't apply to all derived widgets. So... I'm looking for a different method.
You might want to look into implicit invocation. Each Widget would allow Listeners to subscribe to certain Events they announce. In your case a ListBox would subscribe to a ScrollBar's UpEvent or DownEvent and handle accordingly. Imp. Inv. is a good architecture for GUIs because Widgets do not have to explicitly know about other Widgets they affect.

Wiki (not much, but they link to papers): http://en.wikipedia.org/wiki/Implicit_invocation
Quote:Original post by Mantear
Quote:Original post by Telastyn
So why are those special?


Normal buttons are special because they are the only widgets who's action is defined else where using function pointers.


Right. Why is that? Why duplicate code when the only thing that differs is the action behavior (the function pointer [and please, for the love of god use boost::function])?

Quote:
However, this isn't really my problem.


Yes, yes it is.

Quote:

WidgetBase class -> ScrollBar class (abstract) -> VerticleScrollBar class
WidgetBase class -> ListBox class "has-a" VerticleScrollBar class
WidgetBase class -> DropDown class "has-a" VerticleScrollBar class
WidgetBase class -> Button class (uses function pointers)
WidgetBase class -> TextBox class -> Spinner class


The ListBox widget does not have buttons by itself, but it does through having a VerticleScrollBar member object. I'm looking for a design scheme where the VerticleScrollBar member object can give information to the class that contains it, whether it be a ListBox or a DropDown, etc.


struct VerticalScrollBar: ScrollBar{    boost::function<void()>         OnUp;    boost::function<void()>         OnDown;    boost::function<void(float)>    OnScrollbarMove;    // members which hold the total number of members, scrollbar position, location, color, texture, various rendering info....};struct ListBox: WidgetBase{    // Ignoring the fact that we might want non-scrollable ListBoxes...    // A bunch of rendering and container layout data/functions...    VerticalScrollBar      myVScrollBar;    void                   StartOneUp(){...}    void                   StartOneDown(){...}    void                   GotoPercent(float percent){...}    ListBox(...){        // once again, appologies if the syntax is off...              myVScrollBar.OnUp = boost::bind(ListBox::StartOneUp, this);        myVScrollBar.OnDown = boost::bind(...);    }};


Aggregation handles 'has-a'. The command/strategy pattern handles how to tie the "other" piece's behavior to whatever is using/reacting to it. This is pretty much exactly what WireAlbatross also describes.
Quote:Original post by Telastyn
Quote:Original post by Mantear
Quote:Original post by Telastyn
So why are those special?


Normal buttons are special because they are the only widgets who's action is defined else where using function pointers.


Right. Why is that? Why duplicate code when the only thing that differs is the action behavior (the function pointer [and please, for the love of god use boost::function])?


Because that's not the only thing that is different about them. The drawing code is completely different. An independent button reacts differently to mouse actions and other generic WidgetBase functions, such as tabbing, than a button that is a part of a larger widget. Yes, I could wrap things so that every widget that has button-action to it used a derived Button widget, but that would be over-kill.


Quote:
Quote:
However, this isn't really my problem.


Yes, yes it is.


No it's not. My problem is how to get a set of classes to interface properly with a member object of type ScrollBar. Which your suggestion of using boost::function is an option that I'm considering.


Another thought I have is this. Lets say I make any class that wants to have a ScrollBar inherit from an interface class ScrollBarInterface. It'll be an abstract class with virtual member functions OnDown, OnUp, OnPageDown, OnPageUp, etc. The ListBox class will have to implement them. Now, when the ListBox class initializes its ScrollBar member, it'll pass its 'this' pointer casted as a ScrollBarInterface. This will allow the ScrollBar to call OnUp, etc, without having knowledge of or caring whether its parent object is a ListBox or a DropDown or whatever. Are there any pitfalls to something like that?
I recently built a UI system for my game. I first built an Image class. From that I derived a Button class (which is like an image except the image changes depending on stuff like the mouse position).

I used 3 buttons and a background Image to built my scrollbar.
I reused the scrollbar to create my listbox.
I'll probably use a textbox and a listbox to built a combobox.

I used an event model, so the buttons trigger Clicked events when they've been clicked. So the code can be user-specified, or specified by the class which reuses them.
-----------------------------------------------“The best, most affordable way to save the most lives and improve overall health is to increase the number of trained local, primary healthcare workers.”Learn how you can help at www.ghets.org
The main pitfall off the top of my head is in maintenance. If you happen to add functionality, it'll require all implementations of the interface to add the new abstract function or break. This is especially problematic if your code is being used as a module and perhaps extended by someone else. They go to upgrade their UI package and suddenly things don't compile...

With the functor method, they just don't get the extra functionality upon an upgrade. In my experience, added interface classes also tend to be a bit less flexible and have a tendency to grow beyond their original scope which can lead to design tangles. (ymmv)

This topic is closed to new replies.

Advertisement