2

I regularly find myself writing Tk dialog boxes using ttk::entry widgets to prompt for a filename. I save the user's last input to such a dialog and display it as the default when next displaying it.

After I've populated the widget, if the full filename is longer than the entry box, then it will display the leftmost few characters which are generally the less interesting part of the filename and I'd rather it displayed the rightmost characters.

I found that attempting to use $entryWidget xview immediately didn't work very well - it did nothing, which I assume was because of some race condition - so I have taken to writing

after $N $entryWidget xview moveto 1.0

Is there a better way, and if not, what is a good choice for N? I dislike having magic numbers and as far as I recall, after 0 didn't work properly and neither did after idle.

Here's an example demonstrating the problem

package require Tk

set ent [ttk::entry .ent]
pack $ent -fill both -expand yes

$ent insert end "The quick brown fox jumps over the lazy dog"
after 1000 $ent xview moveto 1.0

set btn [ttk::button .btn -text Dismiss -command exit]
pack $btn -fill both -expand yes

Without the after 1000 at line 5(?) there is no error, and no effect. If I try after 10 there is no effect. If I leave out the after n and do update idletasks; $ent xview moveto 1.0 there is no effect.

"No effect" means that the dialog box displays "The quick brown fox jumps", the remainder of the string is hidden. With the code as above, it displays that initially but after a second (as expected, indeed, as coded) it switches to display "jumps over the lazy dog" with the rest hidden. It's undesirable for the user to be able to see the unscrolled text, but I can't work out how to avoid it other than by choosing a magic number of milliseconds to wait.

nurdglaw
  • 2,107
  • 19
  • 37

2 Answers2

1

end is a valid index, so you could say

$entryWidget xview end

Depending if your entry widget is not in readonly or disabled state, you could:

bind $entryWidget <FocusOut> {%W xview end}

I'm surprised .ent xview end returns an error. This works for me:

$ tclsh
% package req Tk
8.5.10
% entry .e
.e
% pack .e
% .e conf -textvar foo
% set foo {qpowieurpoqwyerpiqyweritqywpeityqwpeitrqiweyrioqwter1234}
qpowieurpoqwyerpiqyweritqywpeityqwpeitrqiweyrioqwter1234
% .e xview end

The entry widget displays 1234 at the end.

glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • Thanks for the `moveto end` tip. I'm concerned about how to sensibly display the user's last choice and how to time the `$entryWidget xview` command after I've populated the widget. I'll edit my question to clarify. – nurdglaw Jun 21 '13 at 09:39
  • I've just tried `xview end` but it fails with the error `expected integer but got "end"`. `xview mmoveto end` fails with `expected floating-point number but got "end"`. I'm using ActiveTcl 8.6.0 on WIndows XP – nurdglaw Jun 21 '13 at 09:45
  • Why do you need to delay? Why not set the xview as soon as you populate it? That's a strange error, please show the complete command you used. – glenn jackman Jun 21 '13 at 09:50
  • maybe you need to call `update idletasks` between setting the widget contents and moving the xview. – glenn jackman Jun 21 '13 at 09:53
  • Which is the "strange error"? After a little testing, I found that `$ent xview ` scrolls to show the end of the string, but `$ent xview end` fails with an error message. Of course, this doesn't help with the timing problem. – nurdglaw Jun 21 '13 at 10:24
  • I added a comment in my answer. You could always say `$ent xview [string length [$ent get]]`. The [man page](http://tcl.tk/man/tcl8.6/TkCmd/entry.htm#M35) hints about this describing the "end" index: "This is equivalent to specifying a numerical index equal to the length of the entry's string." – glenn jackman Jun 21 '13 at 10:37
  • It's a tile thing. I'm using `ttk::entry`. Yor example works fine on my system, but fails with the themed widget. Btw I'm getting Tk version 8.5.11. – nurdglaw Jun 21 '13 at 10:43
  • Huh, I get the same error for a ttk::entry widget. I'll file a bug report. Use the `string length` method. – glenn jackman Jun 21 '13 at 10:49
  • Thanks for raaising the bug report. Is there a good reason to switch to `$env xview [string length $str]` rather than stick with `$ent xview moveto 1.0`? – nurdglaw Jun 21 '13 at 10:51
  • A bug exists for your original question: http://core.tcl.tk/tk/tktview?name=2513186fff. Re `xview` vs `xview moveto` -- matter of choice – glenn jackman Jun 21 '13 at 10:54
1

This is a vastly tricky problem, much more than it appears to be at first. The issue is that it takes the processing of rather a lot of idle events (which take an uncertain but non-zero amount of time) for the content to be comprehended enough for the showing of the end of the data, and this processing happens after the usual events that you bind to for this sort of thing (<Map> and <Configure>).

[EDIT]: It turns out that what you need to do is to postpone the adjustment to the viewing location really late in the drawing process, to the <Expose> event which is where the windowing system asks for things to actually be displayed on the screen. (There's a complex series of events that are choreographed to actually deliver a window to the screen, with <Map> being a notify that the window is to appear, <Configure> being a notify of a change to the size of the window, and <Expose> a request to actually draw something.)

set ent [ttk::entry .ent]
pack $ent -fill both -expand yes
$ent insert end "The quick brown fox jumps over the lazy dog"

bind $ent <Expose> {
    # IMPORTANT! Unregister this event handler!
    bind %W <Expose> {}
    # Reposition the view on the content
    %W xview [%W index end]
}

set btn [ttk::button .btn -text "Dismiss" -command exit]
pack $btn -fill both -expand yes

The tricky bit is that we want to act in response to just the first <Expose> event, rather than every one (as rather a lot are delivered over the life of an application; there's also a built-in handler for this event at the low level of the implementation of the application that actually does the double-buffered drawing). This means that we need to include a de-registration (otherwise the window will be “nailed to the end”).

This code only works for content placed in before the first time a window is shown. To move it after that, call ttk::entry::See $ent end (which is what the ttk::entry binding implementation scripts use for that purpose).

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
  • Thanks Donal. @Glenn Jackman has dug out an existing bug report for this. – nurdglaw Jun 21 '13 at 11:08
  • Strange. This works fine if I copy and paste it into a wish shell, but if I save to file and source it from a wish shell, it doesn't. I guess this is a consequence of the "rather a lot of" idle events differing in the two cases. It seems I need to go back to my magic numbers. – nurdglaw Jun 21 '13 at 11:58
  • @nurdglaw Ugh. Let me experiment a bit more… – Donal Fellows Jun 21 '13 at 12:12
  • @nurdglaw Found a way to do it. (Works for me on OSX using the code as posted.) – Donal Fellows Jun 21 '13 at 12:29
  • Thanks Donal, that works a treat. Thanks also for the `ttk::entry::See` tip - I usually have a Browse button next to the entry which needs to populate it based on the result of `tk_getOpenFile` or the like. – nurdglaw Jun 21 '13 at 13:04