• Bind to buttonrelease but not to double click

    From Alexandru@21:1/5 to All on Mon Feb 28 09:12:35 2022
    Is there a simple way (idealy only using bind tricks) to implement the behavior:

    If user makes a double click, do nothing.
    If user releases the first mouse button, do something.

    I tried this:

    pack [label .l1 -text "Test"]
    bind .l1 <ButtonRelease-1> {puts works}
    bind .l1 <Double-1> {break}

    But the "works" message apears twice if a do a double click.
    I'm trying to find something that does not need "after cancel".

    Thanks
    Alexandru

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Meshparts@21:1/5 to All on Mon Feb 28 09:09:08 2022
    Is there a simple way (idealy only using bind tricks) to implement the behavior:

    If user makes a double click, do nothing.
    If user releases the first mouse button, do something.

    I tried this:

    pack [label .l1 -text "Test"]
    bind .l1 <ButtonRelease-1> {puts works}
    bind .l1 <Double-1> {break}

    But the "works" message apears twice if a do a double click.
    I'm trying to find something that does not need "after cancel".

    Thanks
    Alexandru

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andreas Leitgeb@21:1/5 to Alexandru on Mon Feb 28 17:54:16 2022
    I doubt that there is a solution without "after ..."

    Not sure why you want to avoid "after cancel"... you can
    pass a specific after-id to "after cancel", if you don't
    want to cancel by naming the full scriptlet.

    Another approach could also be to set&check a global (or namespaced)
    variable in the after-handler and in other handlers.

    e.g.:
    ButtonRelease: { set var armed; after 500 { if {$var eq "armed"} { do it } }
    DoubleClick: { set var unarmed; ... }

    Alexandru <alexandru.dadalau@meshparts.de> wrote:
    Is there a simple way (idealy only using bind tricks) to implement the behavior:

    If user makes a double click, do nothing.
    If user releases the first mouse button, do something.

    I tried this:

    pack [label .l1 -text "Test"]
    bind .l1 <ButtonRelease-1> {puts works}
    bind .l1 <Double-1> {break}

    But the "works" message apears twice if a do a double click.
    I'm trying to find something that does not need "after cancel".

    Thanks
    Alexandru

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Alexandru@21:1/5 to Andreas Leitgeb on Mon Feb 28 09:57:46 2022
    Andreas Leitgeb schrieb am Montag, 28. Februar 2022 um 18:54:22 UTC+1:
    I doubt that there is a solution without "after ..."

    Not sure why you want to avoid "after cancel"... you can
    pass a specific after-id to "after cancel", if you don't
    want to cancel by naming the full scriptlet.

    Because I like the pure "bind" solution more.
    For example bind .l1 <Button-1> {puts works} works! But then, I actually don't want to react to first button press, I want the release event. Not sure, why <Button-1> works as wanted, but not <ButtonDouble-1>

    Another approach could also be to set&check a global (or namespaced)
    variable in the after-handler and in other handlers.

    e.g.:
    ButtonRelease: { set var armed; after 500 { if {$var eq "armed"} { do it } } DoubleClick: { set var unarmed; ... }
    Alexandru <alexandr...@meshparts.de> wrote:
    Is there a simple way (idealy only using bind tricks) to implement the behavior:

    If user makes a double click, do nothing.
    If user releases the first mouse button, do something.

    I tried this:

    pack [label .l1 -text "Test"]
    bind .l1 <ButtonRelease-1> {puts works}
    bind .l1 <Double-1> {break}

    But the "works" message apears twice if a do a double click.
    I'm trying to find something that does not need "after cancel".

    Thanks
    Alexandru

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andreas Leitgeb@21:1/5 to Alexandru on Mon Feb 28 19:13:20 2022
    Alexandru <alexandru.dadalau@meshparts.de> wrote:
    Because I like the pure "bind" solution more.

    The "pure bind" approach kind of fails on the point, where it is
    supposed to peek into future, to determine whether another click
    will follow. -- That, or I completely misunderstood the original
    question.

    There are a few more or less dirty tricks to achieve all those
    behaviors that do not require peek-into-future directly without
    after handlers. If yours is of that kind, please try again,
    describing the situations you want to capture, and those you
    want to not react for.

    But if you need some event fired only if the user is done with
    his action, then you will need some kind of timer and see later
    if the user continued harrassing the mouse, or gave it a break. ;-)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Alexandru@21:1/5 to Andreas Leitgeb on Mon Feb 28 11:26:07 2022
    Andreas Leitgeb schrieb am Montag, 28. Februar 2022 um 20:13:25 UTC+1:
    Alexandru <alexandr...@meshparts.de> wrote:
    Because I like the pure "bind" solution more.
    The "pure bind" approach kind of fails on the point, where it is
    supposed to peek into future, to determine whether another click
    will follow. -- That, or I completely misunderstood the original
    question.

    There are a few more or less dirty tricks to achieve all those
    behaviors that do not require peek-into-future directly without
    after handlers. If yours is of that kind, please try again,
    describing the situations you want to capture, and those you
    want to not react for.

    But if you need some event fired only if the user is done with
    his action, then you will need some kind of timer and see later
    if the user continued harrassing the mouse, or gave it a break. ;-)

    Okay, I get it.
    Like I wrote I wanted this:

    If user makes a double click, do nothing.
    If user releases the first mouse button, do something.

    But I undertand the "looking into future" thing.
    The idea behind is that some users don't know if a single click is expected and thy start doing double clicks instead. So without anything else from the developer side, the user will trigger the action twice.

    This stands for normal bindings on buttons and lables. Because of this simple fact, the developer always needs to reinvent the wheel and make sure double clicks are ignored or at least make sure that just one of the two click is triggering a function.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andreas Leitgeb@21:1/5 to Andreas Leitgeb on Mon Feb 28 19:48:25 2022
    I saw your last followup only after posting this one ...

    change my suggestion slightly to:

    bind .l1 <Button-1> { set var "1st" }
    bind .l1 <Double-1> { set var "2nd" }
    bind .l1 <ButtonRelease-1> {
    if {$var eq "1st"} { do it ... }
    }

    That would fire on first ButtonRelease, but not on shortly following
    second, third, forth, ...


    Andreas Leitgeb <avl@logic.at> wrote:
    Wait, I've got a new idea of what you might have meant...
    (sorry for implying you wanted tcl to read future)

    My new interpretation is, that you want only the second
    ButtonRelease to fire...

    That would be:
    bind .l1 <Button-1> { set var "1st" }
    bind .l1 <Double-1> { set var "2nd" }
    bind .l1 <ButtonRelease-1> {
    if {$var eq "2nd"} { do it ... }
    }

    Note, that if the user just keeps hammering the Button, then you'll
    still get multiple Doubles and thus multiple "do it ..."-calls.

    To prevent that, you can either add a binding for <Triple-1> and
    set the var to something else, e.g. "3rd".

    I once had a case, where I wanted to handle Double Clicks, and then
    reset the engine, so next click would become a plain click again.
    For this I created a proc "reset_click":

    proc reset_click {} {
    event generate . <5>; event generate . <ButtonRelease-5>
    }

    and called it from the Double-handler.
    That way, user pressing 4 times in a row would get through as
    two separate doubleclicks.


    Andreas Leitgeb <avl@logic.at> wrote:
    Alexandru <alexandru.dadalau@meshparts.de> wrote:
    Because I like the pure "bind" solution more.

    The "pure bind" approach kind of fails on the point, where it is
    supposed to peek into future, to determine whether another click
    will follow. -- That, or I completely misunderstood the original
    question.

    There are a few more or less dirty tricks to achieve all those
    behaviors that do not require peek-into-future directly without
    after handlers. If yours is of that kind, please try again,
    describing the situations you want to capture, and those you
    want to not react for.

    But if you need some event fired only if the user is done with
    his action, then you will need some kind of timer and see later
    if the user continued harrassing the mouse, or gave it a break. ;-)


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ralf Fassel@21:1/5 to All on Mon Feb 28 20:41:50 2022
    * Alexandru <alexandru.dadalau@meshparts.de>
    | The idea behind is that some users don't know if a single click is
    | expected and thy start doing double clicks instead. So without
    | anything else from the developer side, the user will trigger the
    | action twice.

    When executing the function you could record a timestamp. Then when the function is invoked again, check the timestamp and immediately return if
    it is "too young".

    Pseudo:
    proc func {} {
    set now [clock milliseconds]
    if {[info exists ::last_invocation]
    && $now - $::last_invocation < 500} {
    # second click of double click
    return
    }
    set ::last_invocation $now
    # do whatever needs to be done
    }



    R'

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andreas Leitgeb@21:1/5 to Andreas Leitgeb on Mon Feb 28 19:34:56 2022
    Wait, I've got a new idea of what you might have meant...
    (sorry for implying you wanted tcl to read future)

    My new interpretation is, that you want only the second
    ButtonRelease to fire...

    That would be:
    bind .l1 <Button-1> { set var "1st" }
    bind .l1 <Double-1> { set var "2nd" }
    bind .l1 <ButtonRelease-1> {
    if {$var eq "2nd"} { do it ... }
    }

    Note, that if the user just keeps hammering the Button, then you'll
    still get multiple Doubles and thus multiple "do it ..."-calls.

    To prevent that, you can either add a binding for <Triple-1> and
    set the var to something else, e.g. "3rd".

    I once had a case, where I wanted to handle Double Clicks, and then
    reset the engine, so next click would become a plain click again.
    For this I created a proc "reset_click":

    proc reset_click {} {
    event generate . <5>; event generate . <ButtonRelease-5>
    }

    and called it from the Double-handler.
    That way, user pressing 4 times in a row would get through as
    two separate doubleclicks.


    Andreas Leitgeb <avl@logic.at> wrote:
    Alexandru <alexandru.dadalau@meshparts.de> wrote:
    Because I like the pure "bind" solution more.

    The "pure bind" approach kind of fails on the point, where it is
    supposed to peek into future, to determine whether another click
    will follow. -- That, or I completely misunderstood the original
    question.

    There are a few more or less dirty tricks to achieve all those
    behaviors that do not require peek-into-future directly without
    after handlers. If yours is of that kind, please try again,
    describing the situations you want to capture, and those you
    want to not react for.

    But if you need some event fired only if the user is done with
    his action, then you will need some kind of timer and see later
    if the user continued harrassing the mouse, or gave it a break. ;-)


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Alexandru on Mon Feb 28 19:49:18 2022
    Alexandru <alexandru.dadalau@meshparts.de> wrote:
    Andreas Leitgeb schrieb am Montag, 28. Februar 2022 um 20:13:25 UTC+1:
    Alexandru <alexandr...@meshparts.de> wrote:
    Because I like the pure "bind" solution more.
    The "pure bind" approach kind of fails on the point, where it is
    supposed to peek into future, to determine whether another click
    will follow. -- That, or I completely misunderstood the original
    question.

    There are a few more or less dirty tricks to achieve all those
    behaviors that do not require peek-into-future directly without
    after handlers. If yours is of that kind, please try again,
    describing the situations you want to capture, and those you
    want to not react for.

    But if you need some event fired only if the user is done with
    his action, then you will need some kind of timer and see later
    if the user continued harrassing the mouse, or gave it a break. ;-)

    Okay, I get it.
    Like I wrote I wanted this:

    If user makes a double click, do nothing.
    If user releases the first mouse button, do something.

    Except your "requirements" are ambigious. A "double click" is also two
    "mouse button release" events as well. So it is impossible, using pure bindings to events without timers, to ignore a double click while doing something on a button release.

    At the mouse hardware level there are only two button events: "button
    down" and "button up" (release). "Double Clicks" (or any other plural
    click types) are synthesized from these basic events by measuring
    timing between them.

    But I undertand the "looking into future" thing.
    The idea behind is that some users don't know if a single click is
    expected and thy start doing double clicks instead. So without
    anything else from the developer side, the user will trigger the
    action twice.

    1) Retrain users not to do that.

    or

    2) Inside the binding/command triggered from the widget, set the
    respective widget to disabled state, and simultaneously attach a lambda
    to an after event to fire in dt time (where dt is longer than the time
    between double clicks) to reenable the UI element. During the disabled
    time dt, the widget will ignore the second click.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Alexandru@21:1/5 to Rich on Mon Feb 28 11:56:15 2022
    Rich schrieb am Montag, 28. Februar 2022 um 20:49:23 UTC+1:
    Alexandru <alexandr...@meshparts.de> wrote:
    Andreas Leitgeb schrieb am Montag, 28. Februar 2022 um 20:13:25 UTC+1:
    Alexandru <alexandr...@meshparts.de> wrote:
    Because I like the pure "bind" solution more.
    The "pure bind" approach kind of fails on the point, where it is
    supposed to peek into future, to determine whether another click
    will follow. -- That, or I completely misunderstood the original
    question.

    There are a few more or less dirty tricks to achieve all those
    behaviors that do not require peek-into-future directly without
    after handlers. If yours is of that kind, please try again,
    describing the situations you want to capture, and those you
    want to not react for.

    But if you need some event fired only if the user is done with
    his action, then you will need some kind of timer and see later
    if the user continued harrassing the mouse, or gave it a break. ;-)

    Okay, I get it.
    Like I wrote I wanted this:

    If user makes a double click, do nothing.
    If user releases the first mouse button, do something.
    Except your "requirements" are ambigious. A "double click" is also two
    "mouse button release" events as well. So it is impossible, using pure bindings to events without timers, to ignore a double click while doing something on a button release.

    At the mouse hardware level there are only two button events: "button
    down" and "button up" (release). "Double Clicks" (or any other plural
    click types) are synthesized from these basic events by measuring
    timing between them.
    But I undertand the "looking into future" thing.
    The idea behind is that some users don't know if a single click is
    expected and thy start doing double clicks instead. So without
    anything else from the developer side, the user will trigger the
    action twice.
    1) Retrain users not to do that.

    or

    2) Inside the binding/command triggered from the widget, set the
    respective widget to disabled state, and simultaneously attach a lambda
    to an after event to fire in dt time (where dt is longer than the time between double clicks) to reenable the UI element. During the disabled
    time dt, the widget will ignore the second click.
    That's also an idea, except: it has the drawback in case an unexpected / uncatched error happens, then the button remains disabled. This also complicates things more.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Alexandru on Mon Feb 28 20:28:45 2022
    Alexandru <alexandru.dadalau@meshparts.de> wrote:
    Rich schrieb am Montag, 28. Februar 2022 um 20:49:23 UTC+1:
    2) Inside the binding/command triggered from the widget, set the
    respective widget to disabled state, and simultaneously attach a
    lambda to an after event to fire in dt time (where dt is longer than
    the time between double clicks) to reenable the UI element. During
    the disabled time dt, the widget will ignore the second click.

    That's also an idea, except: it has the drawback in case an
    unexpected / uncatched error happens, then the button remains
    disabled. This also complicates things more.

    The lambda you attach to the after event should be so simple that it
    will not error (unless the widget has been deleted). It would be as
    simple as:

    .widget_name configure -state normal

    And, if you wanted to take care of the "widget deleted" possibility:

    catch [list .widget_name configure -state normal]

    And, you can toggle the state, and launch the after, as the very last
    thing you do in the binding, then any other uncaught errors in the rest
    of the binding would not leave the button in disabled state. Due to
    Tcl's event loop, no events will be processed while your
    binding/command runs (unless your binding/command itself triggers an
    [update] somehow).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andreas Leitgeb@21:1/5 to Rich on Mon Feb 28 23:00:20 2022
    Rich <rich@example.invalid> wrote:
    Except your "requirements" are ambigious. A "double click" is also two "mouse button release" events as well. So it is impossible, using pure bindings to events without timers, to ignore a double click while doing something on a button release.

    He had a special kind of "ignore the double click". he meant: ignore the
    second one of a double-click, after having already dealt with the first
    click. That is perfectly possible without timers.

    What is similar, but not possible without timers would be: "if it is a doubleclick, do nothing at all, even suppress any action on first click".

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Andreas Leitgeb on Tue Mar 1 02:29:50 2022
    Andreas Leitgeb <avl@logic.at> wrote:
    Rich <rich@example.invalid> wrote:
    Except your "requirements" are ambigious. A "double click" is also two
    "mouse button release" events as well. So it is impossible, using pure
    bindings to events without timers, to ignore a double click while doing
    something on a button release.

    He had a special kind of "ignore the double click". he meant: ignore the second one of a double-click, after having already dealt with the first click. That is perfectly possible without timers.

    Yes, the "real meaning" finally did eventually get posted, although I
    believe it arrived after I posted the above.

    What is similar, but not possible without timers would be: "if it is
    a doubleclick, do nothing at all, even suppress any action on first
    click".

    Yes, that, indeed, would need timers.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From jtyler@21:1/5 to Alexandru on Mon Feb 28 20:18:28 2022
    On 2/28/2022 9:12 AM, Alexandru wrote:
    Is there a simple way (idealy only using bind tricks) to implement the behavior:

    If user makes a double click, do nothing.
    If user releases the first mouse button, do something.


    In the OP's case, I would suggest a somewhat different approach to
    eliminate the need for an event timer and be more user responsive.

    Treat the double-click the same as a single click. This way the first
    button up could "do something" while the second one - if it occurs too
    quickly - would just be ignored.

    Otherwise, the button release to "do something" would need to wait to be certain it wasn't part of a double click - less responsive.



    Here's an approach I use in a remote control program using the right
    mouse button that I think could work here to do the above:

    Use a global array to keep track of the current right mouse button state
    plus recent timestamps. Bind only to button up and down individually.

    ::rmb(down) - 0 = up, 1 down
    ::rmb(0..3) - [clock miliseconds] of last 4 up/down events

    For the OP, the button release event would look at the timestamps to
    decide if enough time had gone by that this was indeed another separate
    button click. The timings for double and triple click would be used to
    just ignore 2nd and 3rd clicks.

    I use this approach is to implement a mouse-only "shift" capability for
    an external device controller. An rmb down/up quickly sends one command,
    but if the rmb is held down (1/2 second or more), then other mouse ops
    (e.g. wheel and buttons 4/5) do a secondary operation. Then when the rmb
    is eventually released, it is ignored, mimicking a shift key

    Lots of uses and pretty easy to implement.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From clt.to.davebr@dfgh.net@21:1/5 to All on Tue Mar 1 16:15:16 2022
    Try binding the double click to a procedure that does nothing

    package require Tk

    destroy .bb

    proc log1c {w} {puts "1c on $w"}

    proc nop {args} {return}

    pack .bb [label .bb -text "Label Button!"] -side top

    bind .bb <Button-1> "log1c %W"
    bind .bb <Double-Button-1> "nop"

    This appears to ignore the single click event once a double click is recognized. I'm using on 8.6.8 and 8.7a3 on an old Slackware 14.2ish Linux using fvwm2 window manager. I can start mindlessly clicking and only get one message from log1c (until I pause
    clicking).

    If you remove the double click binding

    bind .bb <Double-Button-1> ""

    every click triggers the message.

    Dave B

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From jtyler@21:1/5 to jtyler on Tue Mar 1 13:37:12 2022
    On 2/28/2022 8:18 PM, jtyler wrote:

    In the OP's case, I would suggest a somewhat different approach to
    eliminate the need for an event timer and be more user responsive.


    Here's an implementation of what I suggested, with button 1. It doesn't
    look into the future, only the past. It will trigger a do something on
    the first left click, but not on ones that occur quickly after that (for
    any number of extra fast clicks). No timers. No delays to wait for
    possible double clicking. But a bit more code than I would have hoped.

    It also doesn't do the something if the left button was long pressed and
    a right click callback can test if the left is still down.

    array set ::lmb [list down 0 0 [clock milliseconds] 1 [clock
    milliseconds] 2 [clock milliseconds] 3 [clock milliseconds] ]
    set ::double_time 300
    proc shift {status} {
    global lmb
    set lmb(3) $lmb(2)
    set lmb(2) $lmb(1)
    set lmb(1) $lmb(0)
    set lmb(0) [clock milliseconds]
    set lmb(down) $status
    }
    proc btime {} { ;# time for button 1 down and up again
    expr { $::lmb(0) - $::lmb(2) }
    }
    proc htime {} { ;# time button 1 was held down
    expr { $::lmb(0) - $::lmb(1) }
    }

    proc press {} {
    shift 1
    puts "[format %10s [btime] ] press down = $::lmb(down) "
    }

    proc release {} {
    shift 0
    # puts "hold time [htime]"
    if { [htime] < 500 } {
    if { [btime] > $::double_time } {
    puts "\ndo it YES ********"
    }
    } else {
    puts "\ndo it NO hold time too long [htime]"
    }
    puts "[format %10s [btime] ] release down = $::lmb(down) "
    }

    proc rpress {} {
    if { $::lmb(down) } {
    puts "right click while left is down"
    } else {
    puts "right click while left is up"
    }
    }

    package require Tk
    catch {console show}

    button .path -text "label"
    pack .path -fill both -expand true

    bind .path <ButtonPress-3> rpress
    bind .path <ButtonPress-1> press
    bind .path <ButtonRelease-1> release

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Julian H J Loaring@21:1/5 to All on Fri Mar 25 06:46:15 2022
    Here is my attempt at a "debounce" class. I have stripped it from a package - the comments are for Natural Docs...
    -----
    #
    # Namespace: ::Debounce
    #
    # Author: J H J Loaring
    #
    # Version: 1.0
    #
    # Last modified: 25th March 2022
    #
    # Creates a debounced function that delays invoking a target script
    # until after _wait_ milliseconds have elapsed since the last time the
    # debounced function was invoked.
    #
    # Usage:
    # ==== Tcl ====
    # set debouncer [Debounce new]
    # ...
    # somecmd [$debounce 800 {script}]
    # =============
    #
    # Example:
    # ==== Tcl ====
    # set debouncer [Debounce new]
    # pack [button .b -text "Double Click Me" -font {TkDefaultFont 30}] -padx 10 -pady 10
    # bind .b <1> [$debounce 800 {puts "%W clicked"}]
    # =============
    #
    # Note:
    # The *unknown* method is used to allow a simplified call
    # without having to use the bind method
    #
    # $debounce 1000 {...script...}
    # vs
    # $debounce bind {...script...}
    #
    package require TclOO
    package require oo::util

    oo::class create Debounce {
    classmethod now {} {
    clock milliseconds
    }

    classmethod newref {} {
    classvariable counter
    return "[self]::__debounce_cmd_[incr counter]__"
    }

    constructor {} {
    variable interval 0
    variable starttime -1
    variable cmdref ""
    }

    destructor {
    variable cmdref
    rename $cmdref ""
    }
    method Starttimer {} {
    variable starttime
    set starttime [Debounce now]
    }

    method Wait {ms} {
    variable interval
    set interval $ms
    }

    method Delta {} {
    variable starttime
    expr {[Debounce now] - $starttime}
    }

    method IntervalElapsed {} {
    variable interval
    expr { [my Started] ? $_wait < [my Delta] : 0}
    }

    method Started {} {
    variable starttime
    expr {$starttime != -1}
    }

    method execscript {body} {
    if {![my Started]} {
    error "do not call method execscript directly"
    }
    if {![my Started] || [my IntervalElapsed]} {
    my Starttimer
    uplevel #0 $body
    }
    }

    method unknown {name args} {
    if {[llength $args] < 1} {
    return -code rerror "[self object] unknown method $args"
    }

    if {[string is integer $name]} {
    return [my bind $name {*}$args]
    }
    }
    #
    # $debounce bind 100 {puts "%W text"}
    #
    method bind {ms body} {
    variable cmdref
    my Wait $ms
    set cmdref [Debounce newref]
    interp alias {} $cmdref {} [self object] execscript
    return [list $cmdref $body]
    }
    }
    ----
    A little test
    ---
    pack [button .b1 -text "Click Me" -font {TkDefaultFont 30}] -padx 10 -pady 10
    pack [button .b2 -text "Double Click Me" -font {TkDefaultFont 30}] -padx 10 -pady 10
    bind .b1 <1> [list puts "%W clicked"]
    bind .b2 <1> [$debounce 800 {puts "%W clicked"}]
    ---
    The 800 ms value in the example was from trial an error...
    Hope this helps

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From clt.to.davebr@dfgh.net@21:1/5 to All on Fri Mar 25 15:57:22 2022
    From: Julian H J Loaring <jhjloaring@gmail.com>
    Date: Fri Mar 25 13:46:15 GMT 2022
    Subject: Bind to buttonrelease but not to double click


    Here is my attempt at a "debounce" class. I have stripped it from a package - the comments are for
    .....
    The 800 ms value in the example was from trial an error...
    Hope this helps


    I think you are dupicating logic in the Tk event system. The 800 ms delay will have to be tuned for each system and user.

    For a wiget (for example a label) that only respnds to double clicks
    (ignores single clicks or more than two clicks) try:


    label .odc -text "only double clik"

    pack .odc

    bind .odc <Button-1> return
    bind .odc <Double-Button-1> "puts ok"
    bind .odc <Triple-Button-1> return


    For a wiget that only responds to a single click when shift is not pressed try:

    label .osc -text "Only Single Click"

    pack .osc

    bind .osc <Button-1> "puts osc"
    bind .osc <Shift-Button-1> return
    bind .osc <Double-Button-1> return


    It just works, no variables, objects or timers are necessary.

    Dave B

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ralf Fassel@21:1/5 to All on Fri Mar 25 16:40:19 2022
    * Julian H J Loaring <jhjloaring@gmail.com>
    | Here is my attempt at a "debounce" class. I have stripped it from a package - the comments are for Natural Docs...
    --<snip-snip>--
    | #
    | package require TclOO
    | package require oo::util
    --<snip-snip>--

    $ wish8.6
    % info patchlevel
    8.6.12
    % package require TclOO
    1.1.0
    % package require oo::util
    can't find package oo::util

    ?

    | #
    | # $debounce bind 100 {puts "%W text"}
    | #
    | method bind {ms body} {
    | variable cmdref
    | my Wait $ms
    | set cmdref [Debounce newref]
    | interp alias {} $cmdref {} [self object] execscript

    - Who is responsible for deleting the 'used' aliases?

    R'

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Julian H J Loaring@21:1/5 to Ralf Fassel on Sat Mar 26 02:12:52 2022
    On Friday, 25 March 2022 at 15:40:25 UTC, Ralf Fassel wrote:
    * Julian H J Loaring
    | Here is my attempt at a "debounce" class. I have stripped it from a package - the comments are for Natural Docs...
    --<snip-snip>--
    | #
    | package require TclOO
    | package require oo::util
    --<snip-snip>--

    $ wish8.6
    % info patchlevel
    8.6.12
    % package require TclOO
    1.1.0
    % package require oo::util
    can't find package oo::util

    oo::util is in Tcllib. It is used for classvariable and classmethod in lieu of Tcl8.7
    ?
    | #
    | # $debounce bind 100 {puts "%W text"}
    | #
    | method bind {ms body} {
    | variable cmdref
    | my Wait $ms
    | set cmdref [Debounce newref]
    | interp alias {} $cmdref {} [self object] execscript
    - Who is responsible for deleting the 'used' aliases?

    This is done in the object destructor
    R'

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Julian H J Loaring@21:1/5 to clt.to...@dfgh.net on Sat Mar 26 02:55:58 2022
    On Friday, 25 March 2022 at 15:57:26 UTC, clt.to...@dfgh.net wrote:
    From: Julian H J Loaring
    Date: Fri Mar 25 13:46:15 GMT 2022
    Subject: Bind to buttonrelease but not to double click
    Here is my attempt at a "debounce" class. I have stripped it from a package - the comments are for
    .....
    The 800 ms value in the example was from trial an error...
    Hope this helps
    I think you are dupicating logic in the Tk event system. The 800 ms delay will have to be tuned for each system and user.

    For a wiget (for example a label) that only respnds to double clicks
    (ignores single clicks or more than two clicks) try:


    label .odc -text "only double clik"

    pack .odc

    bind .odc <Button-1> return
    bind .odc <Double-Button-1> "puts ok"
    bind .odc <Triple-Button-1> return


    For a wiget that only responds to a single click when shift is not pressed try:

    label .osc -text "Only Single Click"

    pack .osc

    bind .osc <Button-1> "puts osc"
    bind .osc <Shift-Button-1> return
    bind .osc <Double-Button-1> return


    It just works, no variables, objects or timers are necessary.

    Dave B
    I think I have spend too much time looking at JavaScript :) But who knows, a debounce may come in handy elsewhere...

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Julian H J Loaring@21:1/5 to Julian H J Loaring on Sat Mar 26 02:44:05 2022
    On Friday, 25 March 2022 at 13:46:18 UTC, Julian H J Loaring wrote:
    Here is my attempt at a "debounce" class. I have stripped it from a package - the comments are for Natural Docs...
    Apologies - I had tidied up the code just before posting and in introduced a bug :(

    Note: uses oo::util form Tcllib (https://core.tcl-lang.org/tcllib/technote/cd3a11c3065120d491009e64a19f7676176045cd)

    package require TclOO
    package require oo::util

    oo::class create Debounce {
    classmethod now {} {
    clock milliseconds
    }

    classmethod newref {} {
    classvariable counter
    # Use the Debounce class namespace
    return "[self]::__debounce_cmd_[incr counter]__"
    }

    constructor {} {
    variable interval 0
    variable starttime -1
    variable cmdref ""
    }

    destructor {
    variable cmdref
    # Delete the alias when the object dies
    rename $cmdref ""
    }
    method Starttimer {} {
    variable starttime
    set starttime [Debounce now]
    }

    method Wait {ms} {
    variable interval
    set interval $ms
    }

    method Delta {} {
    variable starttime
    expr {[Debounce now] - $starttime}
    }

    method IntervalElapsed {} {
    variable interval

    expr { [my Started] ? $interval < [my Delta] : 0}
    }

    method Started {} {
    variable starttime
    expr {$starttime != -1}
    }

    method execscript {body} {
    variable starttime

    if {![my Started] || [my IntervalElapsed]} {
    my Starttimer
    uplevel #0 $body
    }
    }

    method unknown {name args} {
    if {[llength $args] < 1} {
    return -code rerror "[self object] unknown method $args"
    }

    if {[string is integer $name]} {
    return [my bind $name {*}$args]
    }
    }

    method bind {ms body} {
    variable cmdref
    my Wait $ms
    set cmdref [Debounce newref]
    interp alias {} $cmdref {} [self object] execscript
    return [list $cmdref $body]
    }
    }

    # Test
    package require Tk
    set debounce [Debounce new]

    pack [button .b1 -text "Click Me" -font {TkDefaultFont 30}] -padx 10 -pady 10
    pack [button .b2 -text "Double Click Me" -font {TkDefaultFont 30}] -padx 10 -pady 10
    bind .b1 <1> [list puts "%W clicked"]
    bind .b2 <1> [$debounce 800 {puts "%W clicked"}]

    # Change 800 to taste and click away
    # ...
    # eventually delete the debounce object and the alias
    $debounce destroy

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ralf Fassel@21:1/5 to All on Mon Mar 28 11:15:59 2022
    * Julian H J Loaring <jhjloaring@gmail.com>
    | > % package require oo::util
    | > can't find package oo::util
    | >
    | oo::util is in Tcllib. It is used for classvariable and classmethod in lieu of Tcl8.7

    Ah, seems my tcllib is too old.

    | > | # $debounce bind 100 {puts "%W text"}
    | > | #
    | > | method bind {ms body} {
    | > | variable cmdref
    | > | my Wait $ms
    | > | set cmdref [Debounce newref]
    | > | interp alias {} $cmdref {} [self object] execscript
    | > - Who is responsible for deleting the 'used' aliases?
    | >
    | This is done in the object destructor

    What about overwriting an already set cmdref in the bind method?

    R'

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Julian H J Loaring@21:1/5 to Ralf Fassel on Tue Mar 29 05:26:36 2022
    On Monday, 28 March 2022 at 10:16:03 UTC+1, Ralf Fassel wrote:
    * Julian H J Loaring
    | > % package require oo::util
    | > can't find package oo::util
    | >
    | oo::util is in Tcllib. It is used for classvariable and classmethod in lieu of Tcl8.7
    Ah, seems my tcllib is too old.
    | > | # $debounce bind 100 {puts "%W text"}
    | > | #
    | > | method bind {ms body} {
    | > | variable cmdref
    | > | my Wait $ms
    | > | set cmdref [Debounce newref]
    | > | interp alias {} $cmdref {} [self object] execscript
    | > - Who is responsible for deleting the 'used' aliases?
    | >
    | This is done in the object destructor
    What about overwriting an already set cmdref in the bind method?

    R'
    I will have to think about that!

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)