How to disable 'modification messages'?

Hi everyone!

While working on ffmpegGUI, I encountered a little challenge.
Picture this: I got a whole lot of BSpinners, BPopUpMenus and BCheckBoxes, each sending a BMessage to the window when their value changes to update the text of a BStringView.

Now, when receiving a certain message in the window, I want to set the values of all those widgets, but don’t want to trigger that update. But of course, all those BMessages pile up at MessageReceived() and get processed once my function to set widget values returns…

Is there another way than to temporarily NULL each widget’s ‘modification message’, and re-set the original message once the widget value is set?

Not sure if I understood correctly, but can´t you hook the MessageReceived on the topmost window and filter / not relay the message forward when you receive that “magic” message ?

BControl::SetValue does not send any message. BCheckBox::SetValue also doesn’t. For menus, I guess you are using BMenuItem::SetMarked which also does not send any message.

However, BSpinner::SetValue does call Invoke. This looks like an error in the API to me, that Invoke call should probably be removed to behave similarly to the other controls?

5 Likes

Thanks for the investigation! BSpinners and BDecimalSpinners are in use. Not doing an Invoke() for those seems to be a good change, though I know nothing, of course. :slight_smile:

Also, BTextControls react to a SetText(), but that may be because their SetModificationMessage() was set, besides their ‘regular’ BMessage, to get a message on every keystroke and not just when hitting ENTER etc.
Something that would also be useful for BSpinners.
Maybe those ‘modification messages’ also shouldn’t fire when their TextView was changes via SetText()?

It’s all happening in the main window’s MessageReceived(). All messages when widgets get changed by user input and the message that triggers a set-every-widget are processed there.

The following two ways for that, of which the first is the simplest.

  1. Set the private timestamp after the processing completed, then check the timestamp expressed by “when” field in the modification message inside the subsequent processing, and ignore all the modification messages generated at the time earlier than the private timestamp. However, cross-over issues arising from message queue need to be dealt with accordingly. (BControl::Invoke() should automatically adds a timestamp to the modification mesage).

  2. Use same modification message for all widgets, and check whether all changes handled or not, ignore it if handled.

For classes derive from BControl, I assume that any change in content, whether from UI or program, should be notified to the watcher. In this light, it is not a error for BControl::SetValue() to call Invoke().

from BeBook:

Typically, when a BControl object needs to take action (in response to a click, for example), it calls the Invoke() function, which copies the model message and delivers the copy to the designated target.

Thanks, that does indeed work. Good to know as work-around until the BSpinner API is fixed to behave as the other BControls.

It leaves some room for interpretation. :slight_smile:
The example “in response to a click”, might suggest that the message is only to be sent if there was user action. If the code itself does a SetValue(), one might interpret it to not send the message. After all, the programmer can decide to follow the user-click-codepath by explicitely sending the message themselves. Or not do it.
That’d save that awkward time-stamping business I’m facing… (It’s not 100% save, as there could conceivably be a delay that make my timestamping function return earlier than the widget’s message reaches the window’s MessageReceived()).

Making the fix should be simple enough, perhaps you can look into it too? :slight_smile: (Or create a ticket at least so we don’t forget about this.)

User interface processing and MessageReceived()'s processing are in the same thread, so you only need to process the last specified message in the current message queue to avoid the problem and save 100%. That’s the reason that I said it’s the simplest way.

I’ll try to decipher the “process the last specified message in the current message queue” some time. :slight_smile:
For my concrete issue for ffmpegGUI, there may be a more fundamental issue that could make all this moot. We’ll see.

For now, I filed #18305 (BSpinner: don't send modification message on SetValue()) – Haiku.

MessageQueue()->MessageAt() and Window()->DispatchMessage() are only the two functions for you to decipher it as you might find the same behaviour in other UI toolkit like Sync() or something else, nothing more.

Since we already use our own spinner classes in ffmpegGUI that derive from BSpinner (or BDecimalSpinner respectively) we could just override the SetValue() function and basically do the same things as the base class but without calling Invoke(). Imho that would solve our problem for now without too much effort. The override can be removed once BSpinner and BDecimalSpinner are fixed.