На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! ПРАВИЛА РАЗДЕЛА · FAQ раздела Delphi · Книги по Delphi
Пожалуйста, выделяйте текст программы тегом [сode=pas] ... [/сode]. Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.
Следующие вопросы задаются очень часто, подробно разобраны в FAQ и, поэтому, будут безжалостно удаляться:
1. Преобразовать переменную типа String в тип PChar (PAnsiChar)
2. Как "свернуть" программу в трей.
3. Как "скрыться" от Ctrl + Alt + Del (заблокировать их и т.п.)
4. Как прочитать список файлов, поддиректорий в директории?
5. Как запустить программу/файл?
... (продолжение следует) ...

Вопросы, подробно описанные во встроенной справочной системе Delphi, не несут полезной тематической нагрузки, поэтому будут удаляться.
Запрещается создавать темы с просьбой выполнить какую-то работу за автора темы. Форум является средством общения и общего поиска решения. Вашу работу за Вас никто выполнять не будет.


Внимание
Попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
Повторная попытка - 60 дней. Последующие попытки бан.
Мат в разделе - бан на три месяца...
Модераторы: jack128, D[u]fa, Shaggy, Rouse_
Страницы: (5) 1 [2] 3 4 ... Последняя » все  ( Перейти к последнему сообщению )  
> TThread.Synchronize
    Немодальный Message box http://stackoverflow.com/a/23647706
    Цитата

    Take a look at the function CreateMessageDialog in unit Vcl.Dialogs. This returns a the standard VCL MessageDialog form which you can manipulate, if necessary, and then show, modal or normally, as you wish.

    А порочность вторичного потока с модальностью UI всё та же - надо уметь понимать есть ли у этого потока сейчас модальный диалог на экране и "нажать ему кнопочку", иначе из цикла прокачки сообщений особо и не выйти. А значит - не сделать graceful-завершение (т.е. либо поток остаётся жить вечно и приложение не сможет завершиться, мешая в том числе и нормальной перезагрузке ОС, либо TerminateThread со всеми минусами что описаны выше).

    Вообще, при всей своей кажущейся тривиальности, UI-поведение - тема очень и очень многогранная с точки зрения удобства пользователя (user experience, UX). Скажем, ранее я тоже считал, что любой диалог должен быть модальным, всё прочее ересь и неудобство. Но когда я посмотрел на Perforce-клиент (так называемый P4), то был удивлён насколько удобной может быть немодальность диалоговых окон при выполнении тех или иных операций: Сабмитишь? А вот можно зайти в список предыдущих коммитов. Бранчуешь или мёржишь? А вот тебе доступ к общему дереву, к консоли сообщений, к чему угодно. В этом свете стандартные модальные диалоги ОС начинают казаться чугунными кандалами, где можно максимум это лишь погреметь при желании цепью :D
      Цитата Jin X @
      в чём порочность?

      Порочность не в самом MessageBox, а в кривой логике взаимодействия потоков - с одной стороны тебе хочется автоудаления потока через FreeOnTerminate, "не вешаться" на OnTerminate и не "устанавливать какую-нибудь переменную вне TThread", а с другой стороны ты хочешь каким-то странным способом контролировать завершение потока и даже закрывать его MessageBox.
      Во-первых, не нужно никакого FreeOnTerminate. Лучше иметь ссылку на объект потока, в которой уже есть масса полезных свойств типа Finished, Handle, ThreadID, а также можно добавить свои, например признак того, что поток находится в состоянии показа MessageBox. Раз тебе все равно приходится контролировать состояние потока, то и удалить его объект не составит труда, когда установлен флаг Finished (и GetExitCodeThread не возвращает STILL_ACTIVE). Если поток находится в состоянии показа MessageBox, то вывести его из этого состояния можно простым PostThreadMessage(ThreadID,WM_QUIT,0,0). Если он может делать что-то еще, то можно использовать штатные средства Terminate\Terminated для корректного досрочного завершения.

      PS: Не только вызовы EnumThreadWindows/SendMessage(WM_CLOSE), но и PostThreadMessage(ThreadID,WM_QUIT,0,0) из главного потока по любому выглядят коряво. Лучше в самом классе потока создать метод типа CloseMessageBox (или ExitMessageLoop), в котором будет вызываться PostThreadMessage
      Сообщение отредактировано: leo -
        Камрады хорошо ответили, добавить особо нечего.
        Что касается немодальной мессаги - hWnd = 0 и всё :)
          Цитата Mr.Delphist @
          Немодальный Message box http://stackoverflow.com/a/23647706
          Не хочу я юзать VCL. Можно на WinAPI сделать, конечно. Но так мне видится проще.

          Цитата Mr.Delphist @
          А порочность вторичного потока с модальностью UI всё та же - надо уметь понимать есть ли у этого потока сейчас модальный диалог на экране и "нажать ему кнопочку", иначе из цикла прокачки сообщений особо и не выйти.
          Не вижу тут проблем. EnumThreadWindows - сработала, значит есть (к тому же, у меня есть отдельный флаг для этого). Кидаешь WM_CLOSE (даже если промахнёшься на микросекунду, ничего страшного всё равно не произойдёт).

          Цитата leo @
          Порочность не в самом MessageBox, а в кривой логике взаимодействия потоков - с одной стороны тебе хочется автоудаления потока через FreeOnTerminate, "не вешаться" на OnTerminate и не "устанавливать какую-нибудь переменную вне TThread", а с другой стороны ты хочешь каким-то странным способом контролировать завершение потока и даже закрывать его MessageBox.
          Я наоборот хотел, чтобы FreeOnTerminate устанавливал ThreadObj в nil. А суть этого вопроса вообще в том, чтобы случайно не обратиться к Synchronize уже освобождённого объекта. Но т.к. от Synchronize я отказался, то и вопрос отпал.
          А в самом потоке перед завершением устанавливаются флаги (вне TThread) - само собой.

          Цитата leo @
          Во-первых, не нужно никакого FreeOnTerminate. Лучше иметь ссылку на объект потока, в которой уже есть масса полезных свойств типа Finished, Handle, ThreadID, а также можно добавить свои, например признак того, что поток находится в состоянии показа MessageBox. Раз тебе все равно приходится контролировать состояние потока, то и удалить его объект не составит труда, когда установлен флаг Finished (и GetExitCodeThread не возвращает STILL_ACTIVE). Если поток находится в состоянии показа MessageBox, то вывести его из этого состояния можно простым PostThreadMessage(ThreadID,WM_QUIT,0,0). Если он может делать что-то еще, то можно использовать штатные средства Terminate\Terminated для корректного досрочного завершения.
          Постоянно контролировать мне его состояние не надо. Только при определённых условиях (принудительном закрытии MesageBox'а). Поэтому FreeOnTerminate пусть будет. Проблем с обнулением тоже нет, т.к. см. предыдущий коммент.
          А вот PostThreadMessage(ThreadID,WM_QUIT,0,0) - это проще, чем EnumThreadWindows :) И про GetExitCodeThread=STILL_ACTIVE может пригодиться ;)

          Добавлено
          Подскажите ещё... есть ли где-то список всех активных объектов моего класса? Или самому такой только создавать нужно?
          Для graceful-завершения как раз...

          Добавлено
          Цитата Jin X @
          Поэтому FreeOnTerminate пусть будет.
          Единственный минус - поток может завершиться между проверкой флага, что поток ещё работает и PostThreadMessage.
          Но это тоже маловероятно, т.к. здесь у меня всего несколько инструкций, а между завершением TThread.Execute и TThread.Free (по FreeOnTerminate) гораздо больше инструкций.

          Добавлено
          Мне надо сейчас проработать места, где поток может внезапно завершиться, когда не надо (между проверкой и действием).
          Вот думаю TThread.Suspend'ами разбавить (до проверки), т.к. всякие там InterlockedXXXX в данном случае не катят (неудобны).
            Цитата Jin X @
            Мне надо сейчас проработать места, где поток может внезапно завершиться, когда не надо (между проверкой и действием).
            Вот думаю TThread.Suspend'ами разбавить (до проверки)

            Госпидя-я... Решить проблему "внезапного завершения" можно только синхронными методами. А SuspendThread - та же опасная асинхронная операция прерывания потока в неопределенном \ непредсказуемом состоянии, как и TerminateThread. Ты вообще в msdn и дельфийскую справку заглядываешь? Зачем все эти кулхацкерские выкрутасы, если есть штатный синхронный способ контролировать завершение потока через OnTerminate? Раз сохранил какие-то ссылки на поток, то повесь ему OnTerminate для обнуления этих ссылок - и всё! Или тебе какая-то сектантская религия не позволяет использовать Synchronize ни под каким соусом?

            Цитата Jin X @
            А суть этого вопроса вообще в том, чтобы случайно не обратиться к Synchronize уже освобождённого объекта. Но т.к. от Synchronize я отказался, то и вопрос отпал.

            Метод Synchronize предназначен для вызова из самого потока TThread, а не из главного. Как поток может вызвать Synchronize, если он уже завершил Execute и "самоликвидировался"?!
            Вызывать Synchronize из главного потока допустимо, но не имеет смысла, т.к. это эквивалентно прямому вызову переданного метода без какой-либо "синхронизации" с потоком, которому принадлежит метод или который передан в качестве параметра в Synchronize(Th, Method).
              Цитата leo @
              Госпидя-я... Решить проблему "внезапного завершения" можно только синхронными методами. А SuspendThread - та же опасная асинхронная операция прерывания потока в неопределенном \ непредсказуемом состоянии, как и TerminateThread. Ты вообще в msdn и дельфийскую справку заглядываешь? Зачем все эти кулхацкерские выкрутасы, если есть штатный синхронный способ контролировать завершение потока через OnTerminate? Раз сохранил какие-то ссылки на поток, то повесь ему OnTerminate для обнуления этих ссылок - и всё! Или тебе какая-то сектантская религия не позволяет использовать Synchronize ни под каким соусом?
              Меня не именно завершение потока интересует (неверно выразился... вернее, _уже_ особо не интересует), а конкретные места в коде потока, где он выполняет что-то (устанавливает значения, вызывает метод и пр). Т.е. мне надо, чтобы это сделал либо доп.поток, либо метод, вызываемый из основного потока, а не оба сразу. А чем Suspend-то опасен?
              p.s. А вообще, без а-ля InterlockedXXXX проблематично это сделать. Думал двойной установкой переменных обойтись, но уж не буду дурью маяться (ошибиться легко, а смысла особого нет) :)
              p.p.s. Synchronize и OnTerminate (который тоже вызывается через Synchronize) мне не нравятся тем, что прога может быть, например, консольной, и тогда этого OnTerminate мы не дождёмся вообще никогда.

              Цитата leo @
              Метод Synchronize предназначен для вызова из самого потока TThread, а не из главного. Как поток может вызвать Synchronize, если он уже завершил Execute и "самоликвидировался"?!
              Вызывать Synchronize из главного потока допустимо, но не имеет смысла, т.к. это эквивалентно прямому вызову переданного метода без какой-либо "синхронизации" с потоком, которому принадлежит метод или который передан в качестве параметра в Synchronize(Th, Method).
              Это понятно. Я хотел вставить этот кусок в метод, который может вызываться как из доп. потока, так и из главного.

              Добавлено
              Кстати, Synchronize ждёт завершения вызова метода (или просто ставит в очередь)?
                Цитата Jin X @
                Кстати, Synchronize ждёт завершения вызова метода (или просто ставит в очередь)?

                Разумеется, ждет. Иначе это был бы не Synchronize, а что-то другое

                Цитата Jin X @
                А чем Suspend-то опасен?

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

                Цитата Jin X @
                Synchronize и OnTerminate (который тоже вызывается через Synchronize) мне не нравятся тем, что прога может быть, например, консольной, и тогда этого OnTerminate мы не дождёмся вообще никогда.

                Час от часу не легче. А вдруг немцы? (С)
                Synchronize в доп.потоке должна работать в паре с CheckSynchronize в главном потоке. В GUI-приложении вызов CheckSynchronize встроен в цикл обработки Application.Run (точнее в Application.Idle). В консольной проге встроенного механизма нет, поэтому нужно либо вызывать CheckSynchronize периодически, либо там, где нужно по логике работы проги, либо использовать другие методы синхронизации потоков.

                Цитата Jin X @
                Постоянно контролировать мне его состояние не надо. Только при определённых условиях (принудительном закрытии MesageBox'а).

                Какая разница - постоянно или однократно? Как ты узнаешь, что нужно закрыть MessageBox? Тебе сам поток как-то об этом сигнализирует (или разгневаннаый юзер) или ты по своей инициативе проверяешь наличие этого бокса? Если ты по любому хоть раз, как-то проверяешь состояние потока, то почему-бы его и не удалять из памяти при этой проверке? А если поток завершится раньше, и объект TThread повисит какое-то время памяти, то что в этом страшного?
                  Цитата leo @
                  Тем, что он прерывает выполнение потока в неопределенном состоянии, например, при захвате им критической секции. А крит.секции, как суслики - ты их не видишь, а они есть - сидят в потайных норах и дельфийского кода (например, в менеджере памяти), и WinAPI.
                  Вообще, SuspendThread это отладочная функция, которая предназначена для вызова из другого процесса, а не своего собственного.
                  Так, прервал Suspend выполнение потока в неопределённом состоянии, пусть даже при захвате критической секции... иии???
                  Что плохого-то в этом? Если я вскоре вызову ResumeThread ?

                  Цитата leo @
                  Synchronize в доп.потоке должна работать в паре с CheckSynchronize в главном потоке. В GUI-приложении вызов CheckSynchronize встроен в цикл обработки Application.Run (точнее в Application.Idle). В консольной проге встроенного механизма нет, поэтому нужно либо вызывать CheckSynchronize периодически, либо там, где нужно по логике работы проги, либо использовать другие методы синхронизации потоков.
                  Ну начинается... Т.е. я, делая модуль, реализующий какой-то функционал в фоновом режиме, должен написать: "Вызывайте CheckSynchronize почаще, пожалуйста, а то могут возникать глюки"? Зачем, если я могу всё это сделать без Synchronize? :)

                  Цитата leo @
                  Какая разница - постоянно или однократно? Как ты узнаешь, что нужно закрыть MessageBox? Тебе сам поток как-то об этом сигнализирует (или разгневаннаый юзер) или ты по своей инициативе проверяешь наличие этого бокса?
                  А зачем мне его закрывать? Есть MessageBoxTimeout, который сам закроет окно после определённого таймаута. И есть метод Close, который можно вызвать из программы и закрыть это окно.

                  Цитата leo @
                  Если ты по любому хоть раз, как-то проверяешь состояние потока, то почему-бы его и не удалять из памяти при этой проверке? А если поток завершится раньше, и объект TThread повисит какое-то время памяти, то что в этом страшного?
                  А какой в этом смысл? Зачем мне его удалять из памяти при "этой" проверке? И зачем потоку висеть в памяти какое-то время? Если можно просто сделать FreeOnTerminate := True, а перед завершением потока установить флаг во внешней переменной? Тем более, что устанавливать флаг всё равно нужно, т.к. при всё той же "этой" проверке мне надо как-то узнать, что поток ещё не удалён.
                  Для чего это нужно? Если можно сделать проще :)

                  Добавлено
                  leo, разговор уже ушёл далеко от первоначальной темы :)
                  И мне даже кажется, что мы уже немного говорим не о том.
                  Я как доделаю эту штуку (руки до неё всё никак не дойдут), выложу и можно будет подискутировать где какие косяки :)
                  Может, кому-то модуль и пригодится...

                  Добавлено
                  Собственно, я сейчас уже всё доделал, осталось только оттестить и отладить. Но это уже не сегодня :)
                    Цитата Jin X @
                    Что плохого-то в этом? Если я вскоре вызову ResumeThread ?

                    в том, что если ты засуспензил поток, в в момент, когда менеджер памяти залочен в крит секции, то ты ты сам не можешь использовать менеждер памяти. а менеджер памяти используется явно и не явно везде. ты разве что сложить пару чисел можешь без него, не меньше.
                      В данном случае я собираюсь (собирался... уже по-человечески, через InterlockedComparExchgane сделал) Suspend'ить доп. поток для проверки переменных, т.е. чтобы "сложить" (сравнить) пару чисел. Мне менеджер памяти не нужен был.
                        Если уж так надо суспендить поток, то делай через виндовые средства (эвенты, семафоры и т.д.) и WaitFor*. Ну или по-топорному, while ThreadPaused do Sleep(100). Это хотя бы безопасно.
                        А вообще чем дальше, тем меньше я понимаю, чего ты хочешь достичь. Неверная архитектура задачи видится мне.
                          Цитата Jin X @
                          leo, разговор уже ушёл далеко от первоначальной темы.
                          И мне даже кажется, что мы уже немного говорим не о том.

                          Во-первых, первоначальная тема (также как и упомянутая "Найти и закрыть MessageBox"), все больше напоминает сюжет "Саги об X, Y и Z", когда вместо вопроса по сути решаемой задачи, задаются и обсуждаются вопросы, которые не имеют к данной задаче никакого отношения или решают ее, мягко говоря, не лучшим, а то и извращенным способом. А о сути и нюансах самой задачи мы узнаем по крупицам в ходе обсуждения (например, тема начиналась с вопроса о Synchronize, а затем вдруг всплыло консольное приложение, в котором Synchronize без.доп телодвижений работать не может).
                          Мы говорим как раз "о том", но ты упорно придерживаешься выбранного ранее пути решения и приводишь в его оправдание совершенно нелогичные доводы.
                          Тебе сразу сказали, что FreeOnTermiate имеет смысл использовать только в случаях, когда 1) тебя совершенно не интересует "судьба" потока - как говорится, запустил и забыл, 2) используется стандартный способ контроля завершения потока через событие OnTerminate (или через свои примочки типа Post\SendMessage). В остальных случаях, когда возникает желание или необходимость контролировать завершение потока не через OnTerminate, задавать FreeOnTerminate = true, не имеет смысла, т.к. это не упрощает, а лишь усложняет логику взаимодействия. И все твои примочки и блуждания в трех соснах (от Synchronize до Terminate\SuspendTrhead и InterlockedXXX) это лишь подтверждают.
                          Ты в предыдущем посте несколько раз повторил "а зачем?", "проще" и т.п. Нет, не проще. Встречный вопрос: зачем "установить флаг во внешней переменной" "... всё равно нужно", если всё, что нужно уже есть в самой переменной TThread? Разве использовать несколько доп.переменных и самому их устанавливать проще, чем использовать готовый объект, в котором все устанавливается автоматически? Зачем доверять потоку удалять свой объект по FreeOnTerminate? Чтобы потом чесать репу - а удалился ли он уже, или находится в состоянии удаления, и не удалится ли на следующей строчке после проверки какого-то там флага, и городить ради этого огород с SuspendTread, шаманить с Interlocked-функциями и т.п.? Зачем все это? Не проще ли самому удалить объект потока, когда он завершился и больше не нужен (хоть в FormDestroy или в завершающем finally консольной проги)? Или ты считаешь, что любую мало-мальскую память нужно освобождать сразу после ее использования? Типа создал строку, передал ее куда-то - непременно вызови S:='', чтобы она не висела в памяти в ожидании автоочистки?

                          Добавлено
                          Цитата Jin X @
                          А если этот менеджер памяти мне приспичило использовать в тот момент, когда доп. поток залочит его в критической секции (без Suspend'а), то я смогу его использовать? Он будет ждать когда доп. поток его освободит что ли?

                          Разумеется. На то она и крит.секция, чтобы разрешать выполнять некоторый кусок кода только одному потоку. Поэтому, если ты остановишь поток в крит.секции менеджера памяти, то любое неявное выделение\освобождение памяти между Suspend и Resume (например, при операциях со строками) приведет к зависону.
                          Ты конечно можешь этого избежать, всё "оттестить и отладить", но по любому SuspendTread - это специальная функция, использовать которую в обычных\нормальных приложениях не рекомендуется. Поэтому, юзать ее ты можешь только "на собственный страх и риск для домашнего использования".
                          Сообщение отредактировано: leo -
                            leo, всё просто. Обсуждается тема unit'а по созданию немодального MessageBox, который висит в фоне и закрывается либо по таймауту, либо юзером, либо методом Close. Он может работать в консольном приложении, а не только в GUI. Вот и всё.

                            Цитата leo @
                            Ты в предыдущем посте несколько раз повторил "а зачем?", "проще" и т.п. Нет, не проще. Встречный вопрос: зачем "установить флаг во внешней переменной" "... всё равно нужно", если всё, что нужно уже есть в самой переменной TThread? Разве использовать несколько доп.переменных и самому их устанавливать проще, чем использовать готовый объект, в котором все устанавливается автоматически? Зачем доверять потоку удалять свой объект по FreeOnTerminate? Чтобы потом чесать репу - а удалился ли он уже, или находится в состоянии удаления, и не удалится ли на следующей строчке после проверки какого-то там флага, и городить ради этого огород с SuspendTread, шаманить с Interlocked-функциями и т.п.? Зачем все это? Не проще ли самому удалить объект потока, когда он завершился и больше не нужен (хоть в FormDestroy или в завершающем finally консольной проги)?
                            Я, честно говоря, вообще не понимаю, как ты предлагаешь организовать весь процесс. Смотри, логика проста до безобразия.
                            Есть метод Show, который создаёт поток (с FreeOnTerminate) и устанавливает переменную (в классе) Status в значение Displaying - таким образом и сам класс и использующий его код будет знать, что окно отображается. Поток создаёт сообщение MessageBoxTimeout, после завершения которого (и непосредственно перед выходом) устанавливает Status в другое значение (Timedout, Button или Breaked). Таким образом я (класс) знаю, есть ли работающий поток, а программа (вызывающая класс) знает, отображается ли сообщение в данный момент. Метод Close (принудительное закрытие) проверяет - Displaying ли сейчас окно? Если да, то посылает ему PostThreadMessage(WM_QUIT). Это всё.
                            Вопрос: зачем мне нужно сделать FreeOnTerminate := False и самому его освобождать? Единственным доводом в этом пользу может служить разве что "вдруг поток освободиться в Close между проверкой Displaying и PostThreadMessage, и тогда я обращусь к несуществующему Thread.Handle (при вызове PostThreadMessage)?" Но и тут есть простое решение: в этом случае поток может сделать сам себе FreeOnTerminate := False, а Close сделать Thread.Free. Это единственный момент, когда это может быть оправдано, на мой взгляд. Если ты другого мнения, пожалуйста, расскажи. Потому что пока я не понимаю смысла делать иначе :). Да, можно делать Free именно в Show, но чем это лучше?

                            Цитата leo @
                            Или ты считаешь, что любую мало-мальскую память нужно освобождать сразу после ее использования? Типа создал строку, передал ее куда-то - непременно вызови S:='', чтобы она не висела в памяти в ожидании автоочистки?
                            Вот это тут вообще ни причём. Другое дело, что если есть выбор (и это не усложняет код и не делает его менее надёжным), лучше освободить, не так ли?

                            Добавлено
                            Цитата leo @
                            В остальных случаях, когда возникает желание или необходимость контролировать завершение потока не через OnTerminate, задавать FreeOnTerminate = true, не имеет смысла, т.к. это не упрощает, а лишь усложняет логику взаимодействия. И все твои примочки и блуждания в трех соснах (от Synchronize до Terminate\SuspendTrhead и InterlockedXXX) это лишь подтверждают.
                            Блуждание произошло из-за того, что я вместо Interlocked зачем-то решил использовать двойные флаги и/или Suspend, чем сейчас и сам удивляюсь.
                            А Synchronize нужен был для оповещения основной программы о том, что "произошёл запуск" или "окно закрылось". Типа OnXXXX и иже с ними. Сначала хотел сделать 2 варианта: прямой вызов и через Synchronize, потом решил сделать только вызов напрямую, а программист уже сам решит: нужен ему Synchronize или нет и вызовет его.
                              Вот такой вопрос у меня ещё: если я выполняю какую-то операцию (например, PostThreadMessage) с потоком, который только что завершил свою работу, ничего страшного, как я понимаю, не произойдёт.
                              Но! Может ли возникнуть ситуация, что создастся поток с таким же Id и я закрою что-то другое?
                              Каким образом эти Id генерируются?
                                Цитата Jin X @
                                Обсуждается тема unit'а по созданию немодального MessageBox, который висит в фоне и закрывается либо по таймауту, либо юзером, либо методом Close. Он может работать в консольном приложении, а не только в GUI. Вот и всё.

                                Тьфу ты блин, сразу сказать не судьба была?
                                https://github.com/Fr0sT-Brutal/Delphi_DlgCountdown
                                1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0591 ]   [ 15 queries used ]   [ Generated: 17.05.24, 03:33 GMT ]