Artykuł przesłany przez Marcusa Junglasa
Podczas programowania modułu obsługi zdarzeń w Delphi (np Na kliknięcie zdarzenie TButton), przychodzi czas, kiedy aplikacja musi być przez jakiś czas zajęta, np. kod musi napisać duży plik lub skompresować niektóre dane.
Jeśli to zrobisz, zauważysz to Twoja aplikacja wydaje się być zablokowana. Nie można już przenieść formularza, a przyciski nie wykazują oznak życia. Wygląda na rozbity.
Powodem jest to, że aplikacja Delpi jest jednowątkowa. Kod, który piszesz, reprezentuje tylko kilka procedur, które są wywoływane przez główny wątek Delphi za każdym razem, gdy ma miejsce zdarzenie. Przez resztę czasu główny wątek obsługuje komunikaty systemowe i inne rzeczy, takie jak funkcje obsługi formularzy i komponentów.
Jeśli więc nie zakończysz obsługi zdarzeń wykonując dłuższą pracę, uniemożliwisz aplikacji obsługę tych wiadomości.
Częstym rozwiązaniem tego typu problemów jest wywoływanie „Aplikacja. ProcessMessages ”. „Aplikacja” jest globalnym obiektem klasy TApplication.
Aplikacja. Wiadomości procesowe obsługują wszystkie oczekujące wiadomości, takie jak ruchy okien, kliknięcia przycisków i tak dalej. Jest to powszechnie używane jako proste rozwiązanie, aby utrzymać działanie aplikacji.
Niestety mechanizm „ProcessMessages” ma swoje własne cechy, co może powodować duże zamieszanie!
Co robi ProcessMessages?
PprocessMessages obsługuje wszystkie oczekujące komunikaty systemowe w kolejce komunikatów aplikacji. System Windows używa komunikatów do „rozmowy” ze wszystkimi uruchomionymi aplikacjami. Interakcja użytkownika jest przekazywana do formularza za pośrednictwem wiadomości, a „ProcessMessages” obsługuje je.
Na przykład, jeśli mysz porusza się na TButton, ProgressMessages robi wszystko, co powinno się zdarzyć w przypadku tego zdarzenia, takie jak odmalowanie przycisku do stanu „wciśniętego” i, oczywiście, wywołanie procedury obsługi OnClick (), jeśli została przypisana.
Na tym polega problem: każde wywołanie ProcessMessages może ponownie zawierać odwołanie rekurencyjne do dowolnej procedury obsługi zdarzeń. Oto przykład:
Użyj następującego kodu dla funkcji obsługi nawet przycisku OnClick („praca”). Oświadczenie for symuluje długie zadanie przetwarzania z kilkoma wywołaniami do ProcessMessages co jakiś czas.
Jest to uproszczone dla lepszej czytelności:
{w MyForm:}
WorkLevel: liczba całkowita;
{OnCreate:}
WorkLevel: = 0;
procedura TForm1.WorkBtnClick (Sender: TObject);
var
cykl: liczba całkowita;
zaczynać
inc (WorkLevel);
dla cykl: = 1 do 5 robić
zaczynać
Memo1.Lines. Dodaj („- Praca” + IntToStr (WorkLevel) + ”, Cykl” + IntToStr (cykl);
Podanie. ProcessMessages;
sen (1000); // lub jakaś inna praca
koniec;
Memo1.Lines. Dodaj („Praca” + IntToStr (WorkLevel) + „zakończone.”);
dec (WorkLevel);
koniec;
BEZ „ProcessMessages” w notatce zapisywane są następujące wiersze, jeśli przycisk został naciśnięty DWUKROTNIE w krótkim czasie:
- Praca 1, cykl 1
- Praca 1, cykl 2
- Praca 1, cykl 3
- Praca 1, cykl 4
- Praca 1, cykl 5
Praca 1 została zakończona.
- Praca 1, cykl 1
- Praca 1, cykl 2
- Praca 1, cykl 3
- Praca 1, cykl 4
- Praca 1, cykl 5
Praca 1 została zakończona.
Podczas gdy procedura jest zajęta, formularz nie wykazuje żadnej reakcji, ale system Windows umieścił drugie kliknięcie w kolejce wiadomości. Zaraz po zakończeniu „OnClick” zostanie ponownie wywołane.
W TYM „ProcessMessages” dane wyjściowe mogą być bardzo różne:
- Praca 1, cykl 1
- Praca 1, cykl 2
- Praca 1, cykl 3
- Praca 2, cykl 1
- Praca 2, cykl 2
- Praca 2, cykl 3
- Praca 2, cykl 4
- Praca 2, cykl 5
Praca 2 została zakończona.
- Praca 1, cykl 4
- Praca 1, cykl 5
Praca 1 została zakończona.
Tym razem formularz wydaje się działać ponownie i akceptuje każdą interakcję użytkownika. Tak więc przycisk jest wciśnięty do połowy podczas pierwszej funkcji „robotniczej PONOWNIE”, która zostanie obsłużona natychmiast. Wszystkie zdarzenia przychodzące są obsługiwane jak każde inne wywołanie funkcji.
Teoretycznie podczas każdego wywołania „ProgressMessages” DOWOLNA ilość kliknięć i wiadomości użytkownika może się zdarzyć „na miejscu”.
Uważaj więc na swój kod!
Inny przykład (w prostym pseudokodzie!):
procedura OnClickFileWrite ();
var mój_plik: = TFileStream;
zaczynać
myfile: = TFileStream.create ('myOutput.txt');
próbować
podczas BytesReady> 0 robić
zaczynać
mój plik. Write (DataBlock);
dec (BytesReady, sizeof (DataBlock));
DataBlock [2]: = # 13; {linia testowa 1}
Podanie. ProcessMessages;
DataBlock [2]: = # 13; {linia testowa 2}
koniec;
Wreszcie
myfile.free;
koniec;
koniec;
Ta funkcja zapisuje dużą ilość danych i próbuje „odblokować” aplikację za pomocą „ProcessMessages” za każdym razem, gdy zapisywany jest blok danych.
Jeśli użytkownik ponownie kliknie przycisk, ten sam kod zostanie wykonany podczas zapisywania pliku. Tak więc pliku nie można otworzyć drugi raz, a procedura kończy się niepowodzeniem.
Być może aplikacja wykona pewne odzyskiwanie po błędzie, takie jak zwolnienie buforów.
Jako możliwy wynik „Datablock” zostanie zwolniony, a pierwszy kod „nagle” podniesie „Naruszenie dostępu” podczas uzyskiwania do niego dostępu. W takim przypadku: linia testowa 1 będzie działać, linia testowa 2 ulegnie awarii.
Lepszy sposób:
Aby to ułatwić, możesz ustawić cały formularz „enabled: = false”, który blokuje wszystkie dane wejściowe użytkownika, ale NIE pokazuje tego użytkownikowi (wszystkie przyciski nie są wyszarzone).
Lepszym sposobem byłoby ustawienie wszystkich przycisków na „wyłączone”, ale może to być skomplikowane, jeśli chcesz na przykład zachować jeden przycisk „Anuluj”. Musisz również przejść przez wszystkie komponenty, aby je wyłączyć, a kiedy zostaną one ponownie włączone, musisz sprawdzić, czy niektóre powinny pozostać w stanie wyłączonym.
Mógłbyś wyłącz kontrolki potomne kontenera, gdy zmieni się właściwość Enabled.
Jak sugeruje nazwa klasy „TNotifyEvent”, należy jej używać wyłącznie do krótkotrwałych reakcji na zdarzenie. Dla czasochłonnego kodu najlepszym sposobem jest umieszczenie całego „wolnego” kodu w osobnym wątku.
Jeśli chodzi o problemy z „PrecessMessages” i / lub włączaniem i wyłączaniem komponentów, użycie a drugi wątek wydaje się wcale nie zbyt skomplikowane.
Pamiętaj, że nawet proste i szybkie linie kodu mogą się zawiesić na kilka sekund, np. otwieranie pliku na stacji dysków może wymagać poczekania, aż dysk zostanie zakończony. Nie wygląda to zbyt dobrze, jeśli aplikacja wydaje się zawieszać, ponieważ dysk jest zbyt wolny.
Otóż to. Następnym razem dodasz „Aplikacja. ProcessMessages ”, pomyśl dwa razy;)