Мастера DELPHI, Delphi programming community Рейтинг@Mail.ru Титульная страница Поиск, карта сайта Написать письмо 
| Новости |
Новости сайта
Поиск |
Поиск по лучшим сайтам о Delphi
FAQ |
Огромная база часто задаваемых вопросов и, конечно же, ответы к ним ;)
Статьи |
Подборка статей на самые разные темы. Все о DELPHI
Книги |
Новинки книжного рынка
Новости VCL
Обзор свежих компонент со всего мира, по-русски!
|
| Форумы
Здесь вы можете задать свой вопрос и наверняка получите ответ
| ЧАТ |
Место для общения :)
Орешник
Коллекция курьезных вопросов из форумов
Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / rsdn.org

 
Чтобы не потерять эту дискуссию, сделайте закладку « предыдущая ветвь | форум | следующая ветвь »

Блокировка контролов на время длительного процесса


starche   (03.01.22 09:04

Здравствуйте!
Среда XE8, Win7, приложение Win32. Одна форма, на ней Panel1, на ней ToolBar1 (с 5 кнопками) и PageControl1 (с 4 вкладами). Вне Panel1 кнопка Button1, запускающая длительный процесс. Код:
procedure TForm1.Process;
var
 i: Integer;
begin
 d:= 0;
 for i:= 0 to MaxInt div 20 do
   d:= d + cos(i/MaxInt);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
 Caption:= '0';
 PageControl1.Enabled:= False;
 Screen.Cursor:= crHourGlass;
 try
   Process;
 finally
   PageControl1.Enabled:= True;
   Caption:= FloatToStr(d);
   Screen.Cursor:= crDefault;
 end;
end;

Код кнопок ToolBar1:
procedure TForm1.ToolButtonClick(Sender: TObject);
begin
 ShowMessage((Sender as TToolButton).Name);
end;

При нажатии кнопки Button1 запускается длительный процесс. Если в это время нажать кнопку на ToolBar1 или заголовок PageControl1, то, после окончания процесса сработает обработчик нажатия кнопки и переключится вкладка PageControl1. Как сделать так, чтобы контролы не реагировали на активность пользователя? Вместо PageControl1.Enabled:= False/True подставлял: Form1.Enabled:= False/True, BlockInput(True/False), Panel1:= False/True, EnableWindow(Form1.Handle, False/True). Безрезультатно. Работает как надо только, если в секции finally удалить включение контролов. Такое ощущение, что finally выполняется раньше Process. Как это исправить?


Игорян   (05.01.22 13:56[1]

Здравствуйте!


> Такое ощущение, что finally выполняется раньше Process.

Это ложное ощущение. Команды выполняются последовательно: Process-> секция "finally".
Проблема кроется в том, что приложение, занятое длительной работой (циклом) не выполняет обработку цикла сообщений. А система (ОС), невзирая на то, что Ваше приложение занято работой с циклом ("process") выполняет "постинг" сообщений WM_LBUTTONDOWN, WM_LBUTTONUP (например, при клике по вкладке PageControl), помещая эти сообщения в конец очереди сообщений приложения. После того, как длительная работа приложения окончена (Ваш цикл "process", секция "finally" и т.д.), приложение выполняет обработку ВСЕХ сообщений, находящихся в его цикле сообщений, в т.ч. и вышеозначенные сообщения.

Улавливаете мысль?

Таким образом, после строки PageControl1.Enabled:= True, приложение обработает сообщения WM_LBUTTONDOWN, WM_LBUTTONUP, что вызовет, собственно, переключение вкладки.


> Как сделать так, чтобы контролы не реагировали на активность
> пользователя?

Есть три пути:
1) смириться с тем, что это поведение by design самой ОС. Такой же "трюк" можно провернуть и в стандартных программах ОС (т.е. проблема не кроется в Делфи).
2) вынести длительную обработку чего бы то ни было в отдельный поток. Нужные компоненты "отключаем". В этом случае очередь сообщений приложения будет вовремя обрабатываться, и "задизейбленный контрол" не будет реагировать на клик по нему после окончания работы отдельного потока (и соответственного "включения" компонентов).
3) наплевать на пункт №2 и сабклассить каждый компонент, который Вы планируете "отключать". Сабклассинг позволит "поймать постные" WM_LBUTTONDOWN и WM_LBUTTONUP сообщения. Ну а дальше Вы знаете, что нужно делать ;)


starche   (06.01.22 07:53[2]

Здравствуйте!


> Игорян   (05.01.22 13:56) [1]


Спасибо. Пришел к такой же мысли. А нет ли варианта 4: в finally перед Enabled:= True почистить всю очередь сообщений?


Игорян   (08.01.22 21:25[3]


> А нет ли варианта 4: в finally перед Enabled:= True почистить
> всю очередь сообщений?

Есть. Думаю, Вам поможет метод Application.ProcessMessages, но там вроде как свои подводные камни, так что я бы не стал на него полагаться.
Дело в том, что этот метод форсирует обработку сообщений в очереди сообщений. Предугадать возможные последствия довольно сложно.

Если бы дело касалось меня, я бы вынес код длительного процесса в отдельный поток. Информации по этому вопросу в сети море.


Германн ©   (09.01.22 02:37[4]

Любые длительные математические вычисления очень любят чтобы их выполняли в отдельном потоке. Не связанном напрямую с главным потоком Дельфи VCL-приложения.


KSergey ©   (10.01.22 09:03[5]

> starche   (03.01.22 09:04)
> При нажатии кнопки Button1 запускается длительный процесс.
>  Если в это время нажать кнопку на ToolBar1 или заголовок PageControl1, то, после окончания процесса сработает обработчик нажатия кнопки и переключится вкладка PageControl1.

По-моему, это логично: пользователь куда-то ткнул - это в итоге (когда получилось) - сработало. Что тут не так?

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

Почему-то всегда в этих случаях сыпятся советы "вычисления в отдельный поток", но советчики не рассказывают "и что дальше?" В самом деле, завершение вычисления надо как-то "отловить", что -то с результатами сделать, а если это только часть процесса, которая и дальше потребует ввода данных от пользователя?

И вот тут мы должны вспомнить, на что же это все должно быть похоже. А похоже это все должно быть на модальные диалоговые окна!
В самом деле, модальное окно как раз делает то, что нам нужно: пользовательский ввод влияет только на контролы на этом окне, а все прочие контролы в приложении "отключаются". При этом приложение не "замораживается", отрисовывается и вообще ведёт себя очень прилично и логично.

Итого, я в своё время сделал такой вариант "для бедных": взял исходники .ShowModal(), из них взял "начало" и "конец", там как раз спецы борланд блокируют что нужно и оставляют только то, что надо обрабатывать. А "в середине" там фактически  Application.ProcesMessages() написан.
Тогда
- делаем форму "подождите, идут вычисления"
- вставляем код "начало"
- вставляем наши расчеты, в которых иногда выполняем "среднюю часть", чтобы приложение не зависало
- вставляем "конец".

Получается что и приложение не зависает, и пользователь понимает что происходит (есть плашка "подождите..."), и лишние клики "куда попал" не работают, и можно даже организовать кнопку "прервать вычисления" при надобности.


KSergey ©   (10.01.22 09:05[6]

> KSergey ©   (10.01.22 09:03) [5]

Все это конечно можно скрестить с отдельным потоком, если есть умение и понимание как.
Тогда будет достаточно обычного модального окна "Подождите..." с вызовом стандартного .ShowModal()


Игорян   (10.01.22 15:13[7]


> Почему-то всегда в этих случаях сыпятся советы "вычисления
> в отдельный поток", но советчики не рассказывают "и что
> дальше?" В самом деле, завершение вычисления надо как-то
> "отловить", что -то с результатами сделать, а если это только
> часть процесса, которая и дальше потребует ввода данных
> от пользователя?

В самом деле, а если ОП пишет "свою версию" Windows, что тогда делать? Наши советы тут не "прокатят".
На самом делестоит отталкиваться от условий выполнения программы, которые озвучивает ОП. И если у него из длительных работ в программе только лишь суммирование значений косинуса, то отдельный поток - это то, что доктор прописал.
"Отлов", как Вы сказали, завершения деятельности потока задача достаточно тривиальная. Разжевана не раз и не только на этом форуме.


> а) приложение не должно "замораживаться" (попробуйте попереключаться
> между вашим и другими приложениями во время "долгого цикла"
> - обнаружите, что ваше приложение еще и не отрисовывается,
>  а в заголовке его пишется "не отвечает")

Именно этой "заморозки" поток позволяет избежать.
Кстати, в заголовке собственно исходного приложения ничего не пишется. То, что Вы видите при "зависнувшем" приложении является ничем иным, какGhost Window.
Цитата из MSDN`a:
If a top-level window stops responding to messages for more than several seconds, the system considers the window to be not responding and replaces it with a ghost window that has the same z-order, location, size, and visual attributes. This allows the user to move it, resize it, or even close the application. However, these are the only actions available because the application is actually not responding.


> Итого, я в своё время сделал такой вариант "для бедных":
>  взял исходники .ShowModal(), из них взял "начало" и "конец",
>  там как раз спецы борланд блокируют что нужно и оставляют
> только то, что надо обрабатывать. А "в середине" там фактически
>  Application.ProcesMessages() написан.
> Тогда
> - делаем форму "подождите, идут вычисления"
> - вставляем код "начало"
> - вставляем наши расчеты, в которых иногда выполняем "среднюю
> часть", чтобы приложение не зависало
> - вставляем "конец".
>
> Получается что и приложение не зависает, и пользователь
> понимает что происходит (есть плашка "подождите..."), и
> лишние клики "куда попал" не работают, и можно даже организовать
> кнопку "прервать вычисления" при надобности.

Стремление сделать что-либо свое более, чем похвально, но зачем такие "извращения"? Сейчас уже у каждого (за малыми исключениями) на рабочей машине используется процессор с 2-мя и более ядрами. Почему бы не использовать этот механизм в работе программы? Зачем копать котлован обеденной ложкой, когда рядом стоит экскаватор?


KSergey ©   (11.01.22 09:19[8]

> Игорян   (10.01.22 15:13) [7]
>  которые озвучивает ОП. И если у него из длительных работ
> в программе только лишь суммирование значений косинуса,
> то отдельный поток - это то, что доктор прописал.
> "Отлов", как Вы сказали, завершения деятельности потока
> задача достаточно тривиальная. Разжевана не раз и не только
> на этом форуме.

А вы таки приведите примерчик, просто схематичный, как вы это видите. С продуманной логикой поведения интерфейса.
Разглагольствования "Разжевана не раз" - это слишком просто, но ни о чем.
И таки да, мне интересно посмотреть на полный (схематичный, конечно) вариант реализации такой логики поведения программы. Потому как там много нюансов, и не с тем, как запустить отдельный поток и сделать там вычисления, а как это все увязать в общую логику работы программы. Тогда будет полезная информация, с которой будет интересно ознакомиться (как делают другие), и, при желании, можно будет обсудить плюсы/минусы/улучшения.

> Стремление сделать что-либо свое более, чем похвально, но
> зачем такие "извращения"?

А затем, мой дорогой друг, что что я описал некую полную рабочую схему. Т.е. привнёс в мир информацию.
Теперь это можно обсуждать, критиковать, делать для себя выбор "я сделаю так / я никогда такую фигню делать не буду!", но в любом случае - это положительно наполненная информация.
В отличии от "Разжевана не раз".

Понимаешь?

Предлагаю поступить так же.


Игорян   (12.01.22 17:07[9]


> А вы таки приведите примерчик, просто схематичный, как вы
> это видите.

Рабочий пример описан ниже.
Основа: пустой/чистый VCL-проект.
Условия: те же, что и у ОП.
Добавления:
1) Метод "procedure DoTerminate(Sender: TObject);" в разделе "Public" формы;
Код метода:

procedure TForm1.DoTerminate(Sender: TObject);
begin
 PageControl1.Enabled := true;
 ToolBar1.Enabled := true;
 Button4.Enabled := true;
 Screen.Cursor := crDefault;

 if Sender is TTestThread then
   ShowMessage(TTestThread(Sender).OutputValue.ToString);
end;


2) Класс "TTestThread".
Код класса:
Объявление:
 
 TTestThread = class(TThread)
 private
   OutputValue: Single;

 protected
   procedure Execute; override;

 public
   constructor CreateThread;
 end;


Реализация:

constructor TTestThread.CreateThread;
begin
 Inherited Create(true);

 FreeOnTerminate := true;
 OutputValue := 0;
end;

procedure TTestThread.Execute;
var
 i: Integer;
begin
 for i:=0 to MaxInt div 20 do
  OutputValue:= OutputValue + cos(i/MaxInt);
end;


3) Код запуска потока:

procedure TForm1.Button4Click(Sender: TObject);
var
 Thread: TTestThread;
begin
 Thread := TTestThread.CreateThread;
 Thread.OnTerminate := DoTerminate;
 Thread.Start;

 Screen.Cursor := crHourGlass;
 PageControl1.Enabled := false;
 ToolBar1.Enabled := false;
 Button4.Enabled := false;
end;

Тестируйте на здоровье!

Теперь что касается Вашего ответа:

> Разглагольствования "Разжевана не раз" - это слишком просто,
>  но ни о чем.


Вы "бегаете" от темы к теме: в посте [5] Вы писали, цитата:

> В самом деле, завершение вычисления надо как-то "отловить",
>  что -то с результатами сделать, а если это только часть
> процесса, которая и дальше потребует ввода данных от пользователя?
>

На что я парировал Ваш ответ утверждением, цитата (пост [7]):

> "Отлов", как Вы сказали, завершения деятельности потока
> задача достаточно тривиальная. Разжевана не раз и не только
> на этом форуме.

Даже пример сейчас Вам написал! (Каюсь, вчера не мог ответить).

Но Вам вдруг захотелось, цитата:

> приведите примерчик, просто схематичный, как вы это видите. С продуманной логикой поведения интерфейса.

Что с Вами творится? Определитесь, чего Вам хочется: "отловить" завершение потока или иметь продуманную логику программы? Кроме того, насчет последнего: обычно такое на форумах не делается. Допускаю, есть фанаты своего дела, но это редкость. Как правило, за подобные "экзерсисы" солидные дяди в костюмах платят лохматым очкарикам заработную плату. Вы должны это понимать.


> Потому как там много нюансов, и не с тем, как запустить
> отдельный поток и сделать там вычисления, а как это все
> увязать в общую логику работы программы.

С нюансами согласен, следует грамотно планировать архитектуру программы, но повторюсь: за такое платят З\П. Как правило, немаленькую.

Мой первый ответ касающийся потоков был дан ОП только потому, что применение потока(-ов) идеально подходит под условия, им описанные. Для таких нужд - запустил/посчитал/вывел - использования потока хватит "за глаза".
Потоки используют для длительных операций, главным образом, не требующих участия пользователя либо вовлекающих его в процесс частично. Нечто вроде: пользователь ввел параметры, нажал кнопку "Старт" и пошел попивать чаек, пока поток "крутится", выполняя работу.


> Понимаешь?

Понимаете.
Вежливость - главное оружие вора! (с)


KSergey ©   (13.01.22 09:11[10]

> Игорян   (12.01.22 17:07) [9]

Да, вы правы, в Delphi в действительно есть полностью продуманный TThread.OnTerminate, который всё волшебно делает за нас как надо, оказывается.
Давно я не баловался дельфями, забыл как там классно всё уже продуманно.


версия для печати

Написать ответ

Ваше имя (регистрация  E-mail 







Разрешается использование тегов форматирования текста:
<b>жирный</b> <i>наклонный</i> <u>подчеркнутый</u>,
а для выделения текста программ, используйте <code> ... </code>
и не забывайте закрывать теги! </b></i></u></code> :)


Наверх

  Рейтинг@Mail.ru     Титульная страница Поиск, карта сайта Написать письмо