Recent

Author Topic: TCheckListBox with OnClick & OnClickCheck  (Read 10829 times)

andyH

  • Jr. Member
  • **
  • Posts: 82
TCheckListBox with OnClick & OnClickCheck
« on: May 15, 2021, 01:49:00 pm »
I suspect this is expected behaviour, but it is unwanted...

Have a TCheckListBox, and in the constructor:
Code: Pascal  [Select][+][-]
  1.   Box3.OnClick:= @Box3CK;

And in Box3CK:
Code: Pascal  [Select][+][-]
  1. Source:= Box3.ItemIndex;
  2. CheckStatus:= Checked[Source];
  3. writeln('box3 clicked on item ',Source);
  4. //do some checks on the item, if it passes
  5. Checked[Source]:= not CheckStatus;

The problem is that:
  • if I click on the item text in the checklistbox the event handler is run once = desired behaviour
  • if I click on the item checkbox the event handler is run twice = undesired

Terminal output:
Code: Pascal  [Select][+][-]
  1. box3 clicked on item 4  <<<< clicking once on item text = run once
  2. box3 clicked on item 3  <<<< clicking once on item checkbox = run twice
  3. box3 clicked on item 3

I suspect this is because I'm changing the state of the checkbox in the event handler?

I get the same behaviour if I also set Box3.OnClickCheck:= @Box3CK as well, no surprise, but I thought I'd check.  It did crash cinnamon (windows users = my linux desktop environment), but it wasn't repeatable.

Most of the time this is not a problem, but if the item in the checklistbox fails a test an error message dialog is displayed. If the user has clicked on the checkbox, this message appears twice = undesired.

I could:
  • set an event handler for OnClickCheck that does nothing, but this would mean the user has to click on the item text for anything to happen.
  • the OnClick handler recording the item index clicked in a global var - OnClickCheck reads this and if no change does nothing. Downside - the user has to then click on another checkbox before they can change the state of the original checkbox.
  • as above, but include time clicked, if OnClickCheck event handler called within say, 100ms, do nothing. But this is starting to get complicated...
Is there a better way, all I want is my event handler to run once whether the user clicks on the item text or checkbox?





winni

  • Hero Member
  • *****
  • Posts: 2419
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #1 on: May 15, 2021, 02:27:12 pm »
Hi!

That's bad design because you created an endless loop by default.

But without changing the design you can get around the problem:

   
Code: Pascal  [Select][+][-]
  1.  Source:= Box3.ItemIndex;
  2.     CheckStatus:= Checked[Source];
  3.     writeln('box3 clicked on item ',Source);
  4.     //do some checks on the item, if it passes
  5.     Box3.OnClick := Nil;
  6.     Checked[Source]:= not CheckStatus;
  7.     Box3.OnClick :=  @Box3CK;
  8.  

Winni

jamie

  • Hero Member
  • *****
  • Posts: 4587
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #2 on: May 15, 2021, 02:48:13 pm »
The problem stems from the fact that somewhere in history here they forget to separate User clicks and Software changes..

Changing a state of a component via code in most cases should not fire an event that was intended for user response.

This is why there is a MODIFIED property in a few controls now due to coders using these events via code when they should of been used only via User.


 Its obvious that if you change a setting via code and you would like the event that should only be triggered via user to fire then you simple directly call the event  handler after you made the changes. So what is so hard about that ? It would solve a lot of hacked code out there and endless loops until you run out of stack


The only true wisdom is knowing you know nothing

VTwin

  • Hero Member
  • *****
  • Posts: 1035
  • Former Turbo Pascal 3 user
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #3 on: May 15, 2021, 03:31:44 pm »
What widget set? I ran into a similar problem with TComboBox on Carbon, setting it in code was triggering the OnChange event.

It did not happen in Windows or Linux, so I assumed it was a Carbon bug. However, I was told it was expected behavior. My work around (an alternative to winni's, but not necessarily better) was to have a "fIsSetting" flag. The TComboBox checked this and exited if true.

Now Cocoa does not do this, so I assume it was a Carbon bug. Now Mac, Linux, and Windows all behave the same.

You might try this on several platforms, it sounds like a bug to me. I agree with jamie that this is at least undesirable behavior.
« Last Edit: May 15, 2021, 03:42:38 pm by VTwin »
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.0
macOS 10.13.6: Lazarus 2.0.12 (64 bit Cocoa)
Ubuntu 18.04.3: Lazarus 2.0.12 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.0.12 (64 bit on VBox)

VTwin

  • Hero Member
  • *****
  • Posts: 1035
  • Former Turbo Pascal 3 user
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #4 on: May 15, 2021, 04:59:11 pm »
Perhaps I misunderstood. I tried to reproduce the bug with a simple example. This project works as expected on Windows, Linux, and Mac.

Can you post an example to illustrate the bug?
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.0
macOS 10.13.6: Lazarus 2.0.12 (64 bit Cocoa)
Ubuntu 18.04.3: Lazarus 2.0.12 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.0.12 (64 bit on VBox)

andyH

  • Jr. Member
  • **
  • Posts: 82
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #5 on: May 15, 2021, 05:33:23 pm »
GTK2 and the end application is linux (ubuntu) only.

Based on the comments I looked and realised I was also setting selection = false, which I think was the primary cause of the event handler firing twice. Most of my problems have gone away.

I tried winnie's suggestion:
Code: Pascal  [Select][+][-]
  1. Box3.OnClick:= Nil;
  2. Checked[Source]:= not CheckStatus;
  3. Box3.OnClick:= @Box3CK;
Problem here is that first click on item text does nothing, subsequent clicks toggle state. Clicking on the checkbox does not toggle state (no event handler defined for OnClickCheck).

In this checklistbox, the first item is the partition table in a backup:
Code: Pascal  [Select][+][-]
  1.        if Source = 0 then //it's the partition table - if changed it must be restored
  2.           begin
  3.             Checked[Source]:= true; //default assuming part table has changed
  4.             if CheckPartTable then Checked[Source]:= not CheckStatus;
  5.             //check to see if there is an extended partition, its state = state of part table
  6.             //for i:= 1 to Count - 1 do if pos(ExtendStr,Items[i]) > 0 then Checked[i]:= Checked[Source];
  7.             exit;
  8.           end;
  9.  
This is now behaving as it should - if the partition table has changed, the checkbox MUST be enabled. Although I have managed click on the item text occasionally and the handler not fire (but not in a repeatable fashion). Also managed to get to a point where I could toggle the checkbox with the Enter key - something I need to track down, doing a restore and not restoring a partition table that has changed would be a disaster.

Making progress, thanks to the comments received, but not quite there yet.
thanks  :)

andyH

  • Jr. Member
  • **
  • Posts: 82
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #6 on: May 15, 2021, 06:33:33 pm »
Two remaining issues.

This is a skeleton of the event handler for OnClick (there is no event handler for OnClickCheck at the moment):
Code: Pascal  [Select][+][-]
  1. procedure TRestore.Box3CK(Sender: TObject);
  2. const
  3.   ExtendStr = 'extended';
  4.   PartStr = '[partition]';
  5. var
  6.   CheckStatus : boolean;
  7.   Source, i : byte;
  8.   Msg1 : string;
  9. begin
  10.   if Box3.ItemIndex < 0 then exit;
  11.   Source:= Box3.ItemIndex;
  12.   with Box3 do
  13.      begin
  14.        CheckStatus:= Checked[Source];
  15.        //do tests for specific items, set checkbox true or false as appropriate and exit
  16.        
  17.        //got here - then a default item
  18.        Checked[Source]:= not CheckStatus;
  19.      end
  20. end;
Click on the item text for a 'default item', it works and the checkbox is toggled, but click on the checkbox and no change, so change to Checked[Source]:= CheckStatus and this now works for the checkbox - clicking it toggles state, but now clicking on item text does nothing.

So I need to define a handler for OnClickCheck, that could set a flag and then call Box3CK which then checks the state of the flag and toggles the Checked[Source]:= statement, or is there a better way - can Box3CK determine whether it was called by OnClick or OnClickCheck?

Final issue - being able to toggle state with the Enter key. I assume I can fix this by using one of the OnKeyxxx hooks pointing at a procedure that does nothing?

VTwin

  • Hero Member
  • *****
  • Posts: 1035
  • Former Turbo Pascal 3 user
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #7 on: May 15, 2021, 08:24:13 pm »
It is good to post a full project rather than code snippets, as folks are more likely to test it.

In addition to checking for ItemIndex < 0, you need to check for ItemIndex >= Items.Count, otherwise an exception occurs if clicking below the last item (see my example).

Glad you are making progress.


“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.0
macOS 10.13.6: Lazarus 2.0.12 (64 bit Cocoa)
Ubuntu 18.04.3: Lazarus 2.0.12 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.0.12 (64 bit on VBox)

andyH

  • Jr. Member
  • **
  • Posts: 82
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #8 on: May 15, 2021, 08:31:29 pm »
Quote
need to check for ItemIndex >= Items.Count
Thanks for that.

As to posting a full project - at the moment it is 3,500 lines and growing  :)

andyH

  • Jr. Member
  • **
  • Posts: 82
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #9 on: May 16, 2021, 04:03:29 pm »
My final problem, which is now becoming slightly frustrating:
  • Clicking on the item text does not change the state of of the checkbox, so in the event handler a statement of the form Checked[Source]:= not Checked[Source] works, it toggles the checkbox, but
     
  • Clicking on the checkbox itself does change the state of the checkbox so Checked[Source]:= not Checked[Source] simply resets the checkbox to the state it was before the user clicked it.
This means that a single event handler, OnClick, cannot handle the user clicking on either the item or the checkbox. I thought OnItemClick would be the answer to a maiden's prayer - have separate event handlers for OnClickCheck and OnItemClick. Then I found this:

https://forum.lazarus.freepascal.org/index.php/topic,42425.msg296186.html#msg296186
Quote
OnItemClick is also fired when the check box is clicked - duplicate of OnClickCheck.
This was 2018, suspect nothing has changed and I'm GTK.

At the moment my options appear to be
  • Do nothing - accept that the user has to click on the item to change state.
     
  • Find some way of saving the state of the TCheckListBox before the user clicks on any item and then maintaining this shadow copy = sounds like a lot of work, getting in the way of the rest of the application.
     
Grateful for any opinion on this.

BTW - is there a way of inserting in-line code as opposed to code blocks, I use it on other forums but haven't found it here, hence the use of blue above.

lucamar

  • Hero Member
  • *****
  • Posts: 4024
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #10 on: May 16, 2021, 04:11:50 pm »
BTW - is there a way of inserting in-line code as opposed to code blocks, I use it on other forums but haven't found it here, hence the use of blue above.

Yep, using:
    [tt]This is code[/tt]
gives:
    This is code.

Use the "Tt" button in the reply editor.
« Last Edit: May 16, 2021, 04:13:32 pm by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

andyH

  • Jr. Member
  • **
  • Posts: 82
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #11 on: May 16, 2021, 04:24:17 pm »
Yep, using:
    [tt]This is code[/tt]
Use the "Tt" button in the reply editor.
Thanks!

andyH

  • Jr. Member
  • **
  • Posts: 82
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #12 on: May 17, 2021, 05:17:08 pm »
Now I am frustrated!

Have a single OnClick handler as this is supposed to trigger by clicking on either the checkbox or the item, Box3.OnClick:= @Box3CK;

To deal with the issues of clicking on the checkbox changing state, while clicking on the item does not visually change the checkbox state, I now have a custom TCheckListBox containing BoxCheck : array of boolean that mirrors the state of the checkboxes. Solves that problem.

The start of the event handler, very first line, has writeln('box3 clicked on item ',Box3.ItemIndex); So I can see when Box3CK gets fired.

In the event handler, Box3CK, the test on the first item in the TCheckListBox is 'has the partition table changed since the backup'. The code:
Code: Pascal  [Select][+][-]
  1.   with Box3 do //a set of if statements each exiting the procedure
  2.      begin
  3.        if Source = 0 then //it's the partition table - if changed it must be restored
  4.           begin
  5.             Checked[Source]:= true; //default assuming part table has changed
  6.             if CheckPartTable then //then okay to toggle
  7.                begin
  8.                  Checked[Source]:= not BoxCheck[Source];
  9.                  BoxCheck[Source]:= Checked[Source]; //save the new state
  10.                end
  11.             else BoxCheck[Source]:= true; //must be restored
  12.             //check to see if there is an extended partition, its state = state of part table
  13.             //for i:= 1 to Count - 1 do if pos(ExtendStr,Items[i]) > 0 then Checked[i]:= Checked[Source];
  14.             exit;
  15.           end;
Tested in a scenario where the partition table has changed since the backup so it MUST be restored = checkbox should always be true. CheckPartTable generates a message dialog if the part table has changed warning the user.

Testing - compile, run, select backup, box3 get populated (default all checkboxes = true):
  • Click on checkbox for item 0, event handler fires, message displayed, checkbox stays true = correct behaviour.
     
  • Click on checkbox a second time - event handler not fired, but checkbox showing false - see screenshot. This should not happen - it should fire the event handler.
     
  • Subsequent clicks on the checkbox - it behaves as it should, Box3CK fired, checkbox set true.
     
  • Clicking on the item, not the checkbox = behaves as it should.

To fix the problem, tried setting Box3.OnClickCheck:= @Box3CK as well as using OnClick. Seems to solve the problem, but now, of course, the handler is fired twice when you click on the checkbox = warning message displayed twice to user  :(

Yes it is a bit of a rant, but not sure how to proceed.

dseligo

  • Sr. Member
  • ****
  • Posts: 277
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #13 on: May 17, 2021, 07:37:23 pm »
As to posting a full project - at the moment it is 3,500 lines and growing  :)

He didn't mean the whole project you are working on. He means that you create small project which shows problem, and that we can compile and test.
I think I might have solution for you using OnClick and OnItemClick events.

jamie

  • Hero Member
  • *****
  • Posts: 4587
Re: TCheckListBox with OnClick & OnClickCheck
« Reply #14 on: May 18, 2021, 12:46:16 am »
I am not sure If I am following the issue here but I am going to give it my best..

That ListCheckBox is a branch from a normal TlistBox..

If you employ the OnClick event only, during that event, you use the ItemIndex that points to the item that is selected.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.CheckListBox1Click(Sender: TObject);
  2. begin
  3.   Caption := CheckListBox1.ItemIndex.Tostring;
  4. end;      
  5.  

 Viewing this, you will see that it will report the item from the list you clicked on, you do not need to click on the Check box although this event will also still get called if you do..

 While in the event you can use this
Code: Pascal  [Select][+][-]
  1.  TCheckListBox(Sender).Checked[TCheckListBox(Sender).ItemIndex] = ?
  2.  

There is no need to use the OnItemClick and OnClickCheck because they both do the same, only gets called when you click on the check marks with the exception that the OnItemClick carries the index. But we already showed you how to get that.. so these extra do dads are just wasted bloat as usual...

The only true wisdom is knowing you know nothing

 

TinyPortal © 2005-2018