Page 1 of 1

[REPLIED] Event priorities with a ListIconGadget()

Posted: Fri Sep 20, 2024 9:03 am
by boddhi
This is not so much a question as a surprising observation, probably related to Windows.
OR certainly my lack of knowledge! :mrgreen:

Consider the following code:

Code: Select all

If OpenWindow(0, 0, 0, 322, 300, "Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  StringGadget(0, 8, 10, 306, 20, "StringGadget 1")
  StringGadget(1, 8, 40, 306, 20, "StringGadget 2")
  ListIconGadget(2, 8, 60, 306, 100, "ListIcon",200)
  AddGadgetItem(2,-1,"Item 1")
  AddGadgetItem(2,-1,"Item 2")
  AddGadgetItem(2,-1,"Item 3")
  EditorGadget(3, 8, 170, 306, 100, #PB_Editor_ReadOnly)
  SetActiveGadget(0)
  Repeat
    Evenmt=WaitWindowEvent()
    Select Evenmt  
      Case #PB_Event_Gadget
        Select EventType()
          Case #PB_EventType_Change
            Select EventGadget()
              Case 0,2:AddGadgetItem(3,-1,"Change "+Str(EventGadget()))
            EndSelect
          Case #PB_EventType_Focus
            Select EventGadget()
              Case 0,1:AddGadgetItem(3,-1,"Enter "+Str(EventGadget()))
            EndSelect
          Case #PB_EventType_LostFocus
            Select EventGadget()
              Case 0,1:AddGadgetItem(3,-1,"Exit "+Str(EventGadget()))
            EndSelect
        EndSelect
    EndSelect
  Until Evenmt = #PB_Event_CloseWindow
EndIf
 
I'm rather surprised to find that when a StringGadget that has focus loses it to a ListIconGadget, the event priority is:
#PB_EventType_Change (ListIcon)
#PB_EventType_LostFocus (StringGadget)
I would logically have expected it to be:
#PB_EventType_LostFocus (StringGadget)
#PB_EventType_Change (ListIcon)
as in the case of a StringGadget to another StringGadget where we have
#PB_EventType_LostFocus (StringGadget 1)
#PB_EventType_Focus (StringGadget 2)
Why do I have a problem with this?
Because my StringGadget displays the data of a ListIconGadget item, and I want to retrieve the contents of this field when the focus is lost, and use GetGadgetState(ListIconGadget) to find out which ListIcon item it corresponds to.
However, as #PB_EventType_Change occurs before #PB_EventType_LostFocus, I don't get the right item number...
 
My current solution is to use the same procedure for Focus and LostFocus events, storing the ListIcon item number in a Static variable.
(Note: I could also use a SetGadgetData to store the current item no.)

Re: Event priorities with a ListIconGadget()

Posted: Fri Sep 20, 2024 10:27 am
by spikey
Wouldn't it make more sense to use the #PB_EventType_Change on the ListIcon to update the string? #PB_EventType_Focus and #PB_EventType_LostFocus are gadget centric, not gadget item centric. You can potentially have multiple #PB_EventType_Change's sequentially on the gadget without a #PB_EventType_LostFocus occurring. Furthermore the #PB_EventType_LostFocus isn't guaranteed to occur if the user leaves the control focused and wanders off, which means the string will be out of date. This doesn't seem a desirable UI behaviour to me.

Re: Event priorities with a ListIconGadget()

Posted: Fri Sep 20, 2024 11:20 am
by boddhi
spikey wrote: Wouldn't it make more sense to use the #PB_EventType_Change on the ListIcon to update the string? [...]
Hi spikey,
Thanks for your reply.

Perhaps I misunderstood you,
I'll try again another way to explain my situation:
• Firstly, I have a ListIconGadget that displays some data from a Sqlite database and whose DB ID is stored with SetGadgetItemData()
• Secondly, when I click on the ListIcon, I retrieve the data from the DB with the ID of the selected row and feed various StringGadgets.
• Thirdly, I modify the contents of these StringGadgets. Here, I want the content to be saved in the DB when the fields lose focus.

Losing focus can be achieved in several ways: for example, by moving from a StringGadget to another StringGadget or by clicking on a new ListIcon element.

In the case of moving between StringGadget, no problem, the order of events is LostFocus(Current gadget) -> Focus(New gadget) and I recover the correct StringGadget contents, since they have not been altered by a change of ListIcon item.
On the other hand, in the case where the loss of focus occurs through a change in the ListIcon item, as #PB_EventType_Change of the ListIcon occurs BEFORE the #PB_EventType_LostFocus of the StringGadget, the StringGadget are updated before the #PB_EventType_LostFocus of the StringGadget, I don't retrieve the correct content of the StringGadget that had the focus before the change in the ListIcon item.
(Sorry for all the repetition of ListIcon and StringGadget words in the text, but I'm trying to be as precise as possible).

For better understanding, with the following code:
1 - First, click on a valid listicon item (not -1 :wink: )
2 - Modify StringGadget(0)
3 - Click on a new ListIcon item and observe the StringGadget(0) content retrieved.

Code: Select all

Procedure ListIconChange()
  Text.s=""
  Item=GetGadgetState(2)
  If item>=0
    Text=GetGadgetItemText(2,Item,1)
  EndIf
  SetGadgetText(0,Text)
EndProcedure

If OpenWindow(0, 0, 0, 622, 300, "Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  StringGadget(0, 8, 10, 606, 20, "StringGadget 1")
  StringGadget(1, 8, 40, 606, 20, "StringGadget 2")
  ListIconGadget(2, 8, 60, 606, 100, "ListIcon",200)
  AddGadgetColumn(2,1,"Content",100)
  AddGadgetItem(2,-1,"Item 1"+Chr(10)+"Content 1")
  AddGadgetItem(2,-1,"Item 2"+Chr(10)+"Content 2")
  AddGadgetItem(2,-1,"Item 3"+Chr(10)+"Content 3")
  EditorGadget(3, 8, 170, 606, 100, #PB_Editor_ReadOnly)
  SetActiveGadget(0)
  Repeat
    Evenmt=WaitWindowEvent()
    Select Evenmt  
      Case #PB_Event_Gadget
        Select EventType()
          Case #PB_EventType_Change
            Select EventGadget()
              Case 2:ListIconChange()
            EndSelect
          Case #PB_EventType_LostFocus
            Select EventGadget()
              Case 0,1:AddGadgetItem(3,-1,"Exit "+Str(EventGadget())+" - StrinGadget(0) content: "+GetGadgetText(0))
            EndSelect
        EndSelect
    EndSelect
  Until Evenmt = #PB_Event_CloseWindow
EndIf
This is not the modified content but the new content induced by #Pb_EventType_Change which occurred before #Pb_EventType_LostFocus.
This is not the case when switching from one StringGadget to another, where the content is correctly retrieved.

Hoping I've made more clearer explanations. :D

Re: Event priorities with a ListIconGadget()

Posted: Fri Sep 20, 2024 12:26 pm
by spikey
boddhi wrote: Fri Sep 20, 2024 11:20 am Perhaps I misunderstood you,
Hoping I've made more clearer explanations. :D
No, it was me who misunderstood you! You need to maintain current state data in the design, so you do not rely solely on the current index of the listicon to determine which record the string gadgets are editing, because, as you've observed this can change in an asynchronous way (for the reason BarryG gives below). Instead an independent variable should be set to the id of the record being edited in the string gadgets when you first load them.

I would then use a "record modified" flag variable which is set by a #PB_Event_Change on any editing control to indicate an alteration has been made to the data. (Bearing in mind that just typing and then removing a space from the end of a gadget constitutes (two) #PB_Event_Change, you might want to cache the original text and look for an actual text change before setting the flag. This would reduce redundant updates).

If I want more fine detail than this, each control can have its own bit in the variable, so bit 0 is the first gadget, bit 1 the second gadget...

When any relevant event occurs on the ListIcon gadget you can check for a non-zero value in this variable to see if something has been altered, and if you set at bit level you can determine which content has been modified too. You then know that there are pending writes to the database and can deal with these first. The independent variable should used to determine the row to update not the current state of the list icon gadget.

This way you stay in control and aren't dependent on a specific triggering sequence.

Re: Event priorities with a ListIconGadget()

Posted: Fri Sep 20, 2024 12:31 pm
by BarryG
PureBasic's gadgets are just OS controls behind the scenes. On Windows, a StringGadget is a TextBox, so it depends on the order that the OS sends messages on the TextBox. PureBasic just follows the message order that the OS sends.

Re: Event priorities with a ListIconGadget()

Posted: Fri Sep 20, 2024 4:38 pm
by boddhi
Thank you both for your answers.

The solution I adopted before my post is to store the selected item of the listicon in a static variable and also the contents of the StringGadgets at each #PB_EventType_Change event of the StringGadget (to avoid global variables) then process the #PB_EventType_Change of the ListIcon and finally the #PB_EventType_LostFocus of the StringGadget.

As I wrote (perhaps I should have posted in the General Discussion section after all), this isn't really a question or a request for a solution, but rather a question about the surprising (to me) order of events. I've always been convinced that #PB_EventType_LostFocus occurred before a change of focus to another gadget. Which is generally the case, but not with a ListIcon (and maybe other gadgets too?).

I suspected that Windows was probably responsible for this, but perhaps the same is true on other OS.

Thanks again.

Re: Event priorities with a ListIconGadget()

Posted: Fri Sep 20, 2024 6:56 pm
by boddhi
Here, grosso-modo (very grosso-modo) how I do... (I didn't include the test part if the text was changed between the take and the loss of focus.)

Code: Select all

Procedure ListIconChange()
  Protected.i Item
  Protected.s Text
  
  ; Here, a SetGadgetData could be used too to store current item
  Item=GetGadgetState(2)
  If Item>=0
    Text=GetGadgetItemText(2,Item,1)
    If Not IsWindowEnabled_(GadgetID(0))
      DisableGadget(0,#False)
    EndIf
  Else
    DisableGadget(0,#True)
  EndIf
  SetGadgetText(0,Text)
  SetGadgetText(1,Text)
EndProcedure
;
Procedure StringGadgetEvent()
  Protected.i ItemData
  Protected.u Gadget=EventGadget()
  Static.i ListIconItem
  Static.s TextStringGadget
  
  Select Gadget
    Case 0
      Select EventType()
        Case #PB_EventType_Focus
          ListIconItem=GetGadgetState(2)
          TextStringGadget=GetGadgetText(Gadget)
        Case #PB_EventType_Change
          TextStringGadget=GetGadgetText(Gadget)
        Case #PB_EventType_LostFocus
          AddGadgetItem(3,-1,"Exit "+Str(Gadget))
          AddGadgetItem(3,-1,"  Actual StrinGadget(0) content: "+GetGadgetText(0))
          AddGadgetItem(3,-1,"  StrinGadget(0) content before lose focus: "+TextStringGadget)
          SetGadgetItemText(2,ListIconItem,TextStringGadget,1)
  EndSelect
EndProcedure
;
If OpenWindow(0, 0, 0, 622, 300, "Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  StringGadget(0, 8, 10, 606, 20, "StringGadget 1")
  StringGadget(1, 8, 40, 606, 20, "StringGadget 2",#PB_String_ReadOnly) ; In my original code, only few gadgets are editable
  ListIconGadget(2, 8, 60, 606, 100, "ListIcon",200)
  AddGadgetColumn(2,1,"Content",100)
  AddGadgetItem(2,-1,"Item 1"+Chr(10)+"Content 1")
  AddGadgetItem(2,-1,"Item 2"+Chr(10)+"Content 2")
  AddGadgetItem(2,-1,"Item 3"+Chr(10)+"Content 3")
  SetGadgetItemData(2,0,1000)
  SetGadgetItemData(2,1,1001)
  SetGadgetItemData(2,2,1002)
  EditorGadget(3, 8, 170, 606, 100, #PB_Editor_ReadOnly)
  DisableGadget(0,#True)
  SetActiveGadget(2)
  ;
  Repeat
    Evenmt=WaitWindowEvent()
    Select Evenmt  
      Case #PB_Event_Gadget
        Gadget=EventGadget()
        Select EventType()
          Case #PB_EventType_Change
            Select Gadget
              Case 0:StringGadgetEvent()
              Case 2:ListIconChange()
            EndSelect
          Case #PB_EventType_Focus,#PB_EventType_LostFocus
            Select Gadget
              Case 0:StringGadgetEvent()
            EndSelect
        EndSelect
    EndSelect
  Until Evenmt = #PB_Event_CloseWindow
EndIf

Re: Event priorities with a ListIconGadget()

Posted: Fri Sep 20, 2024 8:16 pm
by boddhi
Code above modified, I forgot the line:

Code: Select all

SetGadgetItemText(2,ListIconItem,TextStringGadget,1)