Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Re: How to display Tk window without waiting for user input

by kcott (Archbishop)
on Jan 16, 2021 at 08:21 UTC ( [id://11126989]=note: print w/replies, xml ) Need Help??


in reply to How to display Tk window without waiting for user input

G'day Special_K,

Unfortunately, you're completely on the wrong track with an infinite loop constantly checking for a status change. A change of status, however that is effected, is an event: you should allow Tk to respond to such an event.

I don't think popping up a window for the notification is the right way to go. You could perhaps use a Tk::Toplevel that holds the notification. That's going to be messy. If it obscures part of the main GUI, user intervention will be needed to move it or bring the main GUI back to the foreground. Then there's the question of how long the popup persists. You could use Tk::after to remove it automatically after a certain period of time: that needs to be long enough for the user to read it; but not so long that you that get a number of popups all piled on top of each other.

I would implement what you're trying to do with a status bar that notifies individual changes, some sort of window with a log of status changes as they occur, or perhaps even both. I've put together a short script to demonstrate both methods; changing the selected radiobutton simulates a status change.

#!/usr/bin/env perl use strict; use warnings; use Tk; my $mw = MainWindow::->new(-title => 'Status Notifications'); $mw->geometry('512x288+50+100'); my $status = 'A'; my $last_status = $status; my $status_msg; _set_status_msg($status, \$status_msg); my $status_bar_F = $mw->Frame( )->pack(-side => 'bottom', -fill => 'x'); $status_bar_F->Label( -textvariable => \$status_msg, -anchor => 'w', -relief => 'sunken', )->pack(-fill => 'x'); my $main_F = $mw->Frame( )->pack(-side => 'top', -fill => 'both', -expand => 1); my $text_F = $main_F->Frame( )->pack(-side => 'right', -fill => 'y'); my $status_text = $text_F->Text( -width => 50, )->pack(); $status_text->insert(end => "$status_msg\n"); my $radio_F = $main_F->Frame( )->pack(-side => 'left', -fill => 'both', -expand => 1); for my $status_letter ('A' .. 'F') { $radio_F->Radiobutton( -text => $status_letter, -variable => \$status, -value => $status_letter, -command => sub { if ($status ne $last_status) { $last_status = $status; _set_status_msg($status_letter, \$status_msg); $status_text->insert(end => "$status_msg\n"); } }, )->pack(-side => 'top', -anchor => 'w', -pady => 2); } MainLoop; sub _set_status_msg { my ($status, $msg_ref) = @_; $$msg_ref = 'Status: ' . $status . '; Changed at: ' . localtime(); return; }

That code is fully functional and should work as is. Pay particular attention to references; for example, \$status_msg, \$status, and $$msg_ref in various places.

— Ken

Replies are listed 'Best First'.
Re^2: How to display Tk window without waiting for user input
by Special_K (Monk) on Jan 16, 2021 at 17:30 UTC

    > Unfortunately, you're completely on the wrong track with an infinite loop constantly checking for a status change. A change of status, however that is effected, is an event: you should allow Tk to respond to such an event.

    The issue is that within the call to check_for_status(); is a webpage scrape; there is no way to have the webpage "push" the status changes to me; I have to determine that myself. For that reason a while (1) loop seems necessary.

    The use case is that this script will just run silently in the background and alert me with a popup when/if a status change occurs.

      The issue is that within the call to check_for_status(); is a webpage scrape; there is no way to have the webpage "push" the status changes to me; I have to determine that myself. For that reason a while (1) loop seems necessary.

      Well I certainly hope that whoever is operating the webpage doesn't mind you polling the page that often, and that you're not voilating any TOS. You should definitely add a delay in there so that you're not hitting the webserver constantly.

      If this is a Tk GUI then you will need the MainLoop;, and everything needs to happen in there, and kcott's comment that you're on the wrong track by writing your own loop is correct. This means you need to use Tk's functionalities to achieve your "loop", which is usually done by setting a timer to go off in the future, which will then run a callback inside of the main loop, and the callback then starts a new timer. Though I am far from a Tk expert, Tk::after as suggested by kcott seems to be a way to achieve this (Update: tybalt89 and kcott have since posted examples). Either that, or spawn a separate thread/process, though then you've got the complication of two processes needing to be managed and needing to communicate.

      Of course, it's a different story if the only thing your code is doing is sending notifications, then most modern window managers have built-in notifications that you can use. For example, on Ubuntu, there's notify-send, which exits immediately and therefore wouldn't block your script (see Calling External Commands More Safely).

      "The issue is that within the call to check_for_status(); is a webpage scrape; ..."

      That's new information; however, it's fairly immaterial with respect to the Tk code.

      "... there is no way to have the webpage "push" the status changes to me; I have to determine that myself."

      Both points are obvious and accepted.

      "For that reason a while (1) loop seems necessary."

      It may seem necessary to you but, not only is it not necessary, it's still the wrong solution.

      Consider the following (fully functional) script which produces a notification similar in appearance to what I get from my email client when a new email arrives.

      #!/usr/bin/env perl use strict; use warnings; use constant { TIMER_INTERVAL => 1_000, NOTIFY_PERIOD => 10_000, }; use Tk; my $mw = MainWindow::->new(); $mw->configure(-title => 'Status Notification Manager'); $mw->geometry('320x60+50+100'); #$mw->withdraw(); my $status = _get_status(); my $last_status = $status; my $status_msg; _set_status_msg($status, \$status_msg); my $timer_id = $mw->repeat(TIMER_INTERVAL, sub { $status = _get_status(); if ($status ne $last_status) { $last_status = $status; _set_status_msg($status, \$status_msg); my $notify_panel = $mw->Toplevel(); $notify_panel->geometry('200x50-0-0'); $notify_panel->overrideredirect(1); my $msg_F = $notify_panel->Frame( )->pack( -side => 'left', -fill => 'both', -expand => 1, ); $msg_F->Label( -text => $status_msg, -anchor => 'w', )->pack( -side => 'left', -padx => 5, ); my $x_F = $notify_panel->Frame( )->pack( -side => 'left', -fill => 'y', ); my $x_B = $x_F->Button( -text => 'X', -bg => '#ff0000', -bd => 1, -relief => 'flat', -anchor => 'ne', -command => sub { if (Exists($notify_panel)) { $notify_panel->destroy(); } }, )->pack(); $notify_panel->raise(); $notify_panel->bell(); $mw->after(NOTIFY_PERIOD, sub { if (Exists($notify_panel)) { $notify_panel->destroy(); } }); } return; }); $mw->Button( -text => 'Exit', -command => sub { $timer_id->cancel(); exit; }, )->pack(-pady => 5); my $status_bar_F = $mw->Frame( )->pack(-side => 'bottom', -fill => 'x'); $status_bar_F->Label( -textvariable => \$status_msg, -anchor => 'w', -relief => 'sunken', )->pack(-fill => 'x'); MainLoop; sub _get_status { join ':', map sprintf('%02d', $_), (localtime())[2,1]; } sub _set_status_msg { my ($status, $msg_ref) = @_; $$msg_ref = 'Status: ' . $status; return; }

      I've commented out the $mw->withdraw();. You had it in your original (OP) code. It does work with my code; however, it makes shutting down the application problematic. Here's an example of running my code with it left in, and then shutting it down:

      ken@titan ~/tmp $ ./pm_11126974_tk_status_change_2.pl & [1] 618 ken@titan ~/tmp $ kill -HUP 618 ken@titan ~/tmp $ [1]+ Hangup ./pm_11126974_tk_status_change_2.pl ken@titan ~/tmp $

      If you didn't leave the PID in plain sight, as I did for this example, you have additional work determining what the PID is. Alternatively, you can run it without the '&' at the end; minimise the console you used — effectively tying it up for whatever amount of time you want to monitor status changes (hours?) — and then end the application as needed.

      I suggest leaving withdraw() out and just minimise the GUI, which is clearly labelled "Status Notification Manager". You can use this to query the last status between notifications; and just click on the "Exit" button when you're done.

      Notice that I've used three methods related to Tk::after: repeat() handles checking for status changes; the ID from repeat() is used to cancel() that checking when the "Exit" button is used; and, after() is used (within the repeat() callback) to get rid if the notification panel after a period of time. See that documentation for more on those methods and additional information.

      You may want to change the NOTIFY_PERIOD constant. You should almost definitely change the TIMER_INTERVAL constant; every five or ten minutes (300_000 or 600_000) would probably be better for accessing a webpage. Note that the values are in milliseconds.

      My &_get_status just returns a time (hh:mm) for demonstration purposes. This is where your webpage status retrieval code should go.

      I find all of the system sounds (bells, beeps, and so on) really annoying, so I've turned all of them off on my computer. The bell() method is untested by me, beyond the fact that its presence causes no problems: it's non-essential; you choose whether to leave or remove it.

      See also: Tk; Tk::Widget; Tk::Wm.

      Tests were run using: Win10; Cygwin; Cygwin XWin Server; Perl 5.32.0.

      — Ken

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://11126989]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (8)
As of 2024-04-24 11:36 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found