GTK Application Flow: How to properly design the flow

Hi all. I have some questions how to properly design the flow of an GTK application. To describe my question a little bit better i try to show off my current design:

flow01

The arrows are basically references. An Application knows about a Window and vice verse. The Window has a ref to all childs.

My question is how to properly design a click on a row of that listbox (which triggers the activate signal) which should magically trigger some machinery which does the following:

  • activate the stack and bring the ArticlePage in front
  • show some buttons in the headerbar and hide some buttons there
  • load the article with the data item as parameter

In an ideal world i would use the GAction machinery in GTK because this would allow me to decouple the logic of specific flows in my application. The activated row echoes into the application show_page and bubbles the widget hierarchy upwards till some widget has registered this action. The problem: i cannot have GObject parameters for that. My data item is therefore useless.

On the other hand it would be possible if i have backward references to the Window and have the functionality called directly. My problem with that approach is that i have cyclic references in my code which could be a problem during disposing these objects. Another idea would be a weak reference then but this feels wrong from an architecture point of view.

Third idea which came into my mind was to have some sort of controller object which has references to all involved GUI elements like that:

I often think about this specific problem in general. What i really want is some sort of loose coupling of all involved elements to be able to rearrange everything when i want to do that. The ListBox should emit a signal/action/whatever, the page has a function to load a page when i give him an data item and a mediator should connect these for now. If i want to rearrange this later i just have to change the mediator, not the involved GUI elements. Maybe some of you have more experience how to proper model a situation like that. I would be glad to hear about you.

Thanks in advance!

Hi

It’s quite difficult to help without knowing which language you are using. Also the design pattern is something which suits our logic. There could be a better way depending on your programming language and your application’s design; but this is what I commonly do in Python for your given points.

  1. When a row is activated, it will emit a signal. I will connect the signal to a function with stack’s page name as data.
  2. Now the function will change the stack’s visible page.
  3. After changing visible page, I will check a dictionary if that page name requires any header-bar widgets and pack them.

The above points are very trivial, but it should give you the idea.
Thanks

I think you’ll find plenty of inspiration if you look for “unidirectional data flow” GUIs. All the “functional reactive” stuff in vogue is related to that.

In a traditional GTK application, you may have a bunch of widgets that trigger actions, but those actions may need other widgets to be reconfigured, or your data model to be changed, and when your data model changes you need to configure the widgets again, and it becomes a big mess, as you have already found out. Your state is spread in ambiguous ways between your actual data model and whatever the widgets store on their own. Writing tests is hard because you need a GUI up and running, and asynchronicity anywhere makes it doubly hard. Your functions that change the GUI often have to g_signal_handler_block(), then change the GUI, then unblock() all the time.

With a unidirectional data flow thing, you have these:

  • A data model, the really canonical representation of the thing which your app operates on.
  • Code to translate your data model into the view, i.e. configure the widgets as needed.
  • Code to capture actions or signals from widgets, and translate them to changes in the data model.

In the example you mentioned, you wanted a click on a listbox row to reconfigure some widgets (bring the ArticlePage to the front, show some buttons and hide others) and change your data model (change the currently-loaded article).

With UDF, it would go something like this. The listbox emits an activate signal. Your handler makes note of the article that ought to be loaded and changes the data model to reflect that. The data model says, “this is a different article than the one I already had, so I’ll trigger a load operation”. That may change your state machine to a Loading state.

The part that synchronizes the views to the data model notices that the current article changed. So, it shows the buttons and hides others as appropriate. If the data model is in a Loading state, maybe it sets the cursor to a hourglass or whatever. Maybe it disables a bunch of widgets while loading is in progress.

One nice thing about this is that it becomes easier to think of the widgetry as just a quirky implementation detail. “Change the current document” is a concrete action that can be triggered by selecting something in your listbox, or maybe by pressing a hotkey, or maybe a DBus API callable from the outside, or maybe even a unit test!

I found https://sinusoid.es/lager/ to be a very readable description of a framework to implement this kind of thing. It’s specifically for a framework in C++, which I don’t use, but the concepts are very clean and completely applicable to other languages. The examples in there make heavy use of C+±isms for matching events to actions, I found it useful to mostly ignore the syntax and focus on the concepts.

There are ready-made frameworks to do UDF with Gtk; I know of two of them for Rust, vgtk and relm. Maybe there are others for other languages.

1 Like

For Haskell there’s gi-gtk-declarative, which also looks quite nice. Those are the three I’m aware of, but I’m surprised of not having come across anything like this in a dynamic language like Python yet.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.