Как отключить кнопку Perl, пока не будет выполнена внешняя команда?

sub push_button2
{
    $but2->configure(-state => 'disabled');

    open(launch, "+>C:\\integration\\check_label\\launch_check_label.bat") or die "Couldn't open file launch_check_label.bat$!";
    print launch "$ARGV[1]:\n";
    print launch "perl C:\\integration\\check_label\\check_label.pl $ARGV[0] $ARGV[1] $text";

    system("start C:\\integration\\check_label\\external_command.bat");
    $but2->configure(-state => 'normal');
}

Вышеупомянутый фрагмент кода не сработал, он отключил кнопку только на несколько миллисекунд, и кнопка была активна мгновенно, даже до запуска командного файла.

2 ответа

В вашем подходе есть пара вопросов. Во-первых, последовательность "кнопка выключения", "делать что-то, не относящееся к TK", "кнопка включения" не работает, и кнопка всегда остается включенной. Причина в том, что изменения отображения выполняются только в случае возврата или обратного вызова update() или же idletasks() называется. Чтобы продемонстрировать это, в следующем примере кнопка никогда не отключается:

use Tk;
my $mw = tkinit;
my $b; $b = $mw->Button(-text => 'Click me!', -command => sub {
    $b->configure(-state => 'disabled');
    # $mw->idletasks; # or alternatively: $mw->update;
    sleep 10; # simulate some task
    $b->configure(-state => 'normal');
})->pack;
MainLoop;

Но если idletasks() или же update() вызов активирован, тогда все работает как положено.

В примере сценария есть недостаток: в то время как sleep() (или любая занятая задача) активна, обновление графического интерфейса невозможно. Приложение кажется замороженным для пользователя. Даже обновления окон больше не происходят. Если это проблема, тогда весь скрипт должен быть реструктурирован неблокирующим образом. В этом случае можно было бы начать новый процесс в фоновом режиме (как вы уже делаете это с помощью start команда), но затем вы должны знать, когда фоновый процесс завершен, и снова включить кнопку. Один простой подход состоит в том, чтобы использовать какой-то "файл сигналов" и периодически проверять, был ли этот файл сигналов создан из фонового процесса. Вот пример, который работает в системах Unix (но должен работать и в Windows, если в system() вызов):

use Tk;
my $mw = tkinit;
my $b; $b = $mw->Button(-text => 'Click me!', -command => sub {
    $b->configure(-state => 'disabled');
    # no idletasks/update necessary now
    system("(rm -f /tmp/signal_file; sleep 10; touch /tmp/signal_file) &");
    my $repeater; $repeater = $mw->repeat(1000, sub {
        if (-e "/tmp/signal_file") {
        unlink "/tmp/signal_file";
        $b->configure(-state => 'normal');
        $repeater->cancel;
    }
    });
})->pack;
MainLoop;

Этот подход не очень элегантен, потому что требует периодической проверки. Более элегантный способ может быть написан с Tk::IO модуль, но возможно, что это работает только на платформах Unix:

use Tk;
use Tk::IO;
my $exec_fh;
my $mw = tkinit;
my $b; $b = $mw->Button(-text => 'Click me!', -command => sub {
    $b->configure(-state => 'disabled');
    # no idletasks/update necessary now
    $exec_fh = Tk::IO->new(-linecommand => sub {
    # empty, we're not interested in the output
    }, -childcommand => sub {
        $b->configure(-state => 'normal');
    });
    $exec_fh->exec("sleep 10");
})->pack;
MainLoop;

Дальнейшие хорошие чтения Tk::fileevent Pod и, возможно, вы можете дать AnyEvent попытка

Вы не закрыли launch перед выполнением вашего system команда.

Если вывод на launch дескриптор файла буферизуется (обычно до 4 КБ), тогда ваш файл, вероятно, пуст во время system команда запускается. Если твой system В этом случае команда сразу же вернется, и ваша кнопка будет немедленно активирована.

Как раз перед вашим system команда, вы должны:

close(launch);

(Кроме того, традиционно используется верхний регистр для файловых дескрипторов.)

Другие вопросы по тегам