Move any widget

Description

SeS: In my quest to develop a gui generator, I have reached a stage in which I need to provide the user the ability to explore widgets placement and do a quick and easy form layout design. Same idea as some of us are already familiar with within Visual Basic and C++. The code presented here is a simplified version in order to be able to share it with a wider tcl/tk community, from newbie to guru.

The code as presented is developed in & for (yes, the tool is creating itself) tG2 v1.06.01

Some limitations which have already been solved in tG2:

  • the titlebar height and framewidth of a toplevel is assumed fixed
  • when widgets are placed in frames, it will create another offset which needs to be taken into account, recursively...

The presented code is tested with tcl/tk v8.4.19 & WinXP OS.

With Over-quoting Removed

Removed overquoting and unneeded [expr] commands. Used %W instead of $w. Braced [expr] expressions.Set cursor just once for each window, and removed the event that was doing the same thing. Used event generate instead of [eval]. Many of these changes were suggest by AMG

SeS 1st May 2013: whoever added this correction, thanks. It is a nice reference to how things should be within tcl with regard to quotes. I realize, I have some more of these examples inside tG² which needs my attention, luckily the source code that is being generated by tG² is less hellish with this respect. Having to look for solutions to solve a problem or create an idea in combination with the freedom that tcl provides pushed me off-track with regard to these fundamental tcl-rules during early stages of development of tG². Time is a healer some say...

#! bin/env tclsh

package require Tk
# ------------------------------------------------------------------------------
bind . <Configure> {set components(geo) [split [split [wm geometry .] x] +]}

# ------------------------------------------------------------------------------
bind . <Motion> {
    if {$components(moveObject) != ""} {
        place $components(moveObject) \
            -x [expr {[winfo pointerx .] - [lindex $components(geo) 1] - 3  - $objectXoff}] \
            -y [expr {[winfo pointery .] - [lindex $components(geo) 2] - 29 - $objectYoff}]
    }
}

# ------------------------------------------------------------------------------
proc makeMovableObject {w} {
    $w config -cursor crosshair
    bind $w <ButtonPress-1> {+
        set objectXoff %x
        set objectYoff %y
        set components(moveObject) %W 
        event generate %W <Motion>
    }
    bind $w <ButtonRelease-1> {+
        set components(moveObject) ""
    }
}

set components(moveObject) ""
event generate . <Configure>

# ---------------------------------- TEST CODE ---------------------------------
button .button1 -text "hello world"
place .button1 -x 50 -y 50
makeMovableObject .button1

label .label1 -text "hello again"
place .label1 -x 50 -y 80
makeMovableObject .label1

Overquoted Version

#! bin/env tclsh

package require Tk

# ------------------------------------------------------------------------------
bind . <Configure> {set components(geo) [split [split [wm geometry .] "x"] "+"]}

# ------------------------------------------------------------------------------
bind . <Motion> {
    if {$components(moveObject) != ""} {
        place $components(moveObject) \
            -x [expr [winfo pointerx .] - [lindex $components(geo) 1] - 3  - $objectXoff] \
            -y [expr [winfo pointery .] - [lindex $components(geo) 2] - 29 - $objectYoff]
    }
}

# ------------------------------------------------------------------------------
proc makeMovableObject {w} {
    set cmd "bind $w  <Motion>    \{$w config -cursor crosshair\}"; eval $cmd
    set cmd "bind $w  <Leave>     \{$w config -cursor \"\"\}"; eval $cmd
    set cmd "bind $w  <ButtonPress-1>   \{+; 
        set objectXoff \[expr %x]
        set objectYoff \[expr %y]
        set components(moveObject) $w
        eval \[bind . <Motion>\]
    \}"
    eval $cmd
    
    bind $w <ButtonRelease-1> {+; 
        set components(moveObject) ""
    }
}

set components(moveObject) ""
eval [bind . <Configure>]

# ---------------------------------- TEST CODE ---------------------------------
button .button1 -text "hello world"
place .button1 -x 50 -y 50
makeMovableObject .button1

label .label1 -text "hello again"
place .label1 -x 50 -y 80
makeMovableObject .label1

Discussion

  Comments

AMG: Is there any particular reason why the braces backslashed inside of double quotes? e.g. "foo \{ bar \} quux" As far as I know, this is only necessary when a brace-quoted string (here, the third argument to proc) contains mismatched braces, and this is necessary even outside double quotes, for instance in comments. Maybe you're just doing it out of stylistic preference, which is just fine.

Also I am curious about this use of eval. Why put the script inside a variable? Just pass it to eval directly. I can see why you're tempted to use eval, since you want substitutions to take place inside braces, but you can usually just run bind directly and avoid quoting hell by leveraging the list command:

bind $w <Motion> [list $w config -cursor crosshair]

Here, you also have the option of using the %W bind substitution, which is replaced with the window name. It works the same as %x and %y, which you already use.

You use "eval [bind . <Motion>\]" to execute the script associated with the <Motion> event. You can also use the [event generate command to do pretty much the same thing. Or you can use {*} instead of eval: "{*}[bind . <Motion>]". Also I suggest putting your bind scripts in procs to simplify the argument to bind, to make them easier to call, and to improve performance (this makes bytecode compilation possible). For example:

proc click {w x y} {
    global objectXoff objectYoff components
    set objectXoff $x
    set objectYoff $y
    set components(moveObject) $w
    event generate $w <Motion>
}
bind $w <ButtonPress-1> {click %w %x %y}

Try to avoid using <Motion> when you can just use <Enter>. But in this application, you shouldn't need bindings for setting the cursor anyway. Just set the cursor for the widget once, and the window system will automatically change the mouse pointer to match the cursor of the widget it's currently on.

Another question: what is expr doing? The only thing I can think of is that it forces the internal representation of %x and %y to be a number, but this is going to automatically happen anyway so there's no need to be explicit.

While I'm on the subject of expr, you really should brace your expr-essions in your <Motion> binding for the "." widget. This improves both performance and security.

SeS - 27 Mar 2010 Thanks for the feedback AMG, I think I can answer all your questions at once by simply saying that there is no particular reason for using any of the methods I decided to use, just stuff I learned from the past 4+ years and I am still learning... The thing is, there are so many ways in tcl/tk to solve a specific problem, I just have to make a choice, sometimes it is not the most logical nor up to usual (IT) standards. Don't expect that from an electronics expert anyway, I have to be pragmatic in most cases, if it works, I am usually happy. I see tG2 as a multi-stage development, the first stage is to create the program with a solid & sound architecture with the (limited) knowledge that I have at the moment, to prove the concept to myself and enjoy it's output both private and professional, later I hope to tune the script to a sharable version. So, your advises and that of other guru's are always appreciated and I will try to adapt my scripts to a way of programming the majority does, to make sure if I decide to share the code of tG2 one day (final stage), people will enjoy updating/improving it. Regards, SeS.


Fabricio Rocha - 14 Mar 2010 - Very interesting project, Lars.... Is there any way to contact you? I found no info in your page...

SeS - 2010-03-17 08:30:21

Fabriccio, I am sorry that I created a confusion here by omitting/forgetting my initials (SeS or SES_home). For some reason, Lars made an update or something and the page is written on his name, I think this is not on purpose, something he or myself did wrong during page creation? I do not want to reveal my real name yet to prevent any email communication at this moment. I am an electronics engineer working for a semicon company, so I have a daytime job, this project is a private project, so you do understand that I am kind of bizzy, that's the reason. Thank you though for your interest, please follow me on the progress by subscribing to the youtube account. As soon as I am confident I have a sharable version of tG2 I will let the world know.

Of course dont hesitate to ask me questions via this channel, as soon as I have time and check out these pages, I will try answer your questions. Regards, SeS.

Lars H: Indeed, I was just gnoming by (fixing spelling of category name). Content one wants to take credit for should carry some sort of signature.


SeS - 2010-03-17 09:09:58

Thanks Lars! Also, for the record:

This is the first page I created at wiki.tcl.tk, but I have more contributions by commenting to initiatives by other enthusiasts/developers:

Ctext

Drag and Drop

Simple Block Selection for Text Widget

And now I really have to go back to work...

Regards, SeS

SEH: tG2 looks very interesting. You should submit it as a Google Summer of Code project. You could mentor a student to make improvements to it all summer, while you get back to your day job!

SES: 28 Mar 2010 : Thanks SEH, I like your idea, but outsourcing the development of a program which is not documented and exists 60% in real code and 40% still in my mind as idea's, is a risky business. You end up supporting / directing the student, so maybe loose more time. Furthermore, I am also using the output of this private project for my job, it´s a perfect oppertunity to test it while using it actively. This kind of feedback is valuable if one wants to create a bugfree program to enhousiasts like you and I, and not risk a bad first impression by users due to undiscovered bugs. I am aware that time is ticking and I am risking release of tG2 in a time when the tcl/tk community is already advanced to tcl/tk v8.7 or maybe even higher...if it's worth it, I think the community will embrace, update and improve it anyway. Regards, SeS.


WJG (28/03/10) I think that your project is a good idea and I wish you every success. I started something similar a few years ago using the place widget command. I was thinking of making something similar to the old Hypercard that was shipped with Macs back in the 1990s! I followed the link to Youtube to see the demos, more good stuff.


SES_home - 2010-04-02 14:55:40

Thanks for the positive feedback WJG, my problem is that I have been working with VB since 1997, I am used to the comfort the IDE provided me for so long. After the switch to tcl/tk around 2005, I realised I have to help myself now, the past knowledge & experiences helps a lot in forming my idea's. I am aware of the existence of other tcl/tk IDE's, but it's time to define my own IDE with things that I value most. I have a lot to offer with tG2, but it would have taken much more time without the contributions of the tcl/tk community on wiki. Yourself and most people who responded in this page have some impressive record of contributions, keep up the good job!


RFox - 2013-05-01 13:55:05

I kind of like to build up these commands using list a lot of the quoting hell goes away if you do e.g.:

  set cmd [list bind $w  <Motion>    [list $w config -cursor crosshair]]  
  eval $cmd

For Tcl 8.5 and later the last line of course is replaced by:

  {*}$cmd

However for the first two commands I'm also confused about why you don't just:

 bind $w <Motion> [list $w config -cursor crosshair]

rather than this business of building a command and then executing it.

For

    set cmd "bind $w  <ButtonPress-1>   \{+; 
        set objectXoff \[expr %x]
        set objectYoff \[expr %y]
        set components(moveObject) $w
        eval \[bind . <Motion>\]
    \}"

I tend instead to:

  proc recordLocation {w x y} {
    set ::objectXoff $x
    set ::objectYoff $y
    set ::components(moveObject) $w
    {*}[bind . <Motion>]

  }
  bind $w <ButtonPress-1> [list recordLocation %w %x %y]

But probably I'm missing something about this?


SeS - 2013-05-01 The reasoning using variable cmd as an intermediate, especially at that time, was to see what the actual substitution would look like if I puts this into the console, to make sure it does contain what I expect (for a newbie this is a challenge, at least it was for me). It's a trick I learned from others on the wiki, but it is a usefull one as long one does this as a temporary solution. I got reluctant (due to time pressure) and did not change this in many cases. Once I got a working solution, I was happy and continued on. lately, I am reviewing my scripts, trying to fix these occurrences. I am not using tcl v8.5 or later due to heritage, I started with 8.4.19, I am sure migrating to the latest tcl/tk version yield a more simplified syntax as you suggest.