W świecie Qt, jaka jest różnica wydarzeń i sygnałów / slotów?
Czy jedno zastępuje drugie? Czy zdarzenia są abstrakcją sygnału / szczelin?
Odpowiedzi:
Dokumentacja Qt prawdopodobnie najlepiej to wyjaśnia:
W Qt zdarzenia są obiektami wywodzącymi się z
QEvent
klasy abstrakcyjnej , które reprezentują rzeczy, które wydarzyły się w aplikacji lub w wyniku zewnętrznej aktywności, o której aplikacja musi wiedzieć. Zdarzenia mogą być odbierane i obsługiwane przez dowolną instancjęQObject
podklasy, ale są one szczególnie istotne w przypadku widżetów. Ten dokument opisuje, w jaki sposób zdarzenia są dostarczane i obsługiwane w typowej aplikacji.
Zatem zdarzenia i sygnały / szczeliny są dwoma równoległymi mechanizmami realizującymi te same rzeczy. Ogólnie rzecz biorąc, zdarzenie zostanie wygenerowane przez zewnętrzną jednostkę (na przykład klawiaturę lub kółko myszy) i zostanie dostarczone przez pętlę zdarzeń QApplication
. Generalnie, jeśli nie skonfigurujesz kodu, nie będziesz generować zdarzeń. Możesz je filtrować QObject::installEventFilter()
lub obsługiwać zdarzenia w obiekcie podklasowym, nadpisując odpowiednie funkcje.
Sygnały i sloty są znacznie łatwiejsze do generowania i odbierania, a ponadto można połączyć dowolne dwie QObject
podklasy. Są one obsługiwane przez Metaclassę (zajrzyj do pliku moc_classname.cpp, aby uzyskać więcej informacji), ale większość komunikacji międzyklasowej, którą stworzysz, będzie prawdopodobnie używać sygnałów i gniazd. Sygnały mogą być dostarczane natychmiast lub odraczane przez kolejkę (jeśli używasz wątków).
Sygnał może zostać wygenerowany.
W Qt sygnały i zdarzenia są implementacjami wzorca Observer . Są używane w różnych sytuacjach, ponieważ mają różne mocne i słabe strony.
Przede wszystkim zdefiniujmy dokładnie, co rozumiemy przez „zdarzenie Qt”: funkcję wirtualną w klasie Qt, którą należy ponownie zaimplementować w swojej klasie bazowej, jeśli chcesz obsłużyć zdarzenie. Jest to związane ze wzorcem metody szablonu .
Zwróć uwagę, jak użyłem słowa „ uchwyt ”. Rzeczywiście, oto podstawowa różnica między intencją sygnałów a wydarzeniami:
Różnica polega na tym, że kiedy „obsługujesz” wydarzenie, bierzesz na siebie odpowiedzialność za „zareagowanie” zachowaniem, które jest przydatne poza klasą. Na przykład weźmy pod uwagę aplikację, która ma przycisk z numerem. Aplikacja musi pozwolić użytkownikowi skupić się na przycisku i zmienić numer, naciskając klawisze „w górę” i „w dół”. W przeciwnym razie przycisk powinien działać normalnie QPushButton
(można go kliknąć itp.). W Qt odbywa się to poprzez utworzenie własnego małego „komponentu” wielokrotnego użytku (podklasy QPushButton
), który jest ponownie implementowany QWidget::keyPressEvent
. Pseudo kod:
class NumericButton extends QPushButton
private void addToNumber(int value):
// ...
reimplement base.keyPressEvent(QKeyEvent event):
if(event.key == up)
this.addToNumber(1)
else if(event.key == down)
this.addToNumber(-1)
else
base.keyPressEvent(event)
Widzieć? Ten kod przedstawia nową abstrakcję: widget, który działa jak przycisk, ale ma dodatkowe funkcje. Bardzo wygodnie dodaliśmy tę funkcjonalność:
keyPressEvent
sygnał, musielibyśmy zdecydować, czy odziedziczyć, QPushButton
czy po prostu połączyć się zewnętrznie z sygnałem. Ale to byłoby głupie, ponieważ w Qt zawsze oczekuje się, że będziesz dziedziczyć, pisząc widżet z niestandardowym zachowaniem (nie bez powodu - wielokrotnego użytku / modułowości). Tworząc keyPressEvent
wydarzenie, przekazują swój zamiar, który keyPressEvent
jest tylko podstawowym budulcem funkcjonalności. Gdyby to był sygnał, wyglądałby jak obiekt skierowany do użytkownika, kiedy nie jest przeznaczony.keyPressEvent
był to sygnał.Projekt Qt jest dobrze przemyślany - sprawili, że wpadliśmy w otchłań sukcesu , ułatwiając zrobienie właściwej rzeczy i trudną do zrobienia złej (poprzez uczynienie keyPressEvent wydarzeniem).
Z drugiej strony rozważ najprostsze użycie QPushButton
- wystarczy utworzyć jego instancję i otrzymać powiadomienie, gdy zostanie kliknięty :
button = new QPushButton(this)
connect(button, SIGNAL(clicked()), SLOT(sayHello())
Jest to wyraźnie przeznaczone do wykonania przez użytkownika klasy:
QPushButton
każdym razem, gdy chcemy, aby jakiś przycisk powiadamiał nas o kliknięciu, wymagałoby to wielu podklas bez dobrego powodu! Widżet, który messagebox
po kliknięciu zawsze pokazuje „Witaj, świecie” , jest przydatny tylko w jednym przypadku - więc nie można go używać ponownie. Ponownie, nie mamy innego wyboru, jak tylko postąpić właściwie - łącząc się z nim zewnętrznie.clicked()
- lub podłączyć kilka sygnałów do sayHello()
. Z sygnałami nie ma zamieszania. W przypadku tworzenia podklas musiałbyś usiąść i przemyśleć kilka diagramów klas, dopóki nie zdecydujesz się na odpowiedni projekt.Należy pamiętać, że jednym z miejsc, QPushButton
emituje clicked()
w jego mousePressEvent()
realizacji. To nie znaczy clicked()
i mousePressEvent()
są wymienne - tylko, że są powiązane.
Tak więc sygnały i zdarzenia mają różne cele (ale są ze sobą powiązane, ponieważ oba pozwalają „zasubskrybować” powiadomienie o czymś, co się dzieje).
Jak dotąd nie podobają mi się odpowiedzi. - Skoncentruję się na tej części pytania:
Czy zdarzenia są abstrakcją sygnału / szczelin?
Krótka odpowiedź: nie. Długa odpowiedź nasuwa „lepsze” pytanie: w jaki sposób sygnały i zdarzenia są powiązane?
Bezczynna główna pętla (na przykład Qt) jest zwykle „blokowana” w wywołaniu select () systemu operacyjnego. To wywołanie sprawia, że aplikacja „usypia”, podczas gdy przekazuje kilka gniazd, plików lub cokolwiek do jądra prosząc o: jeśli coś się w nich zmieni, niech wywołanie select () powróci. - A jądro, jako pan świata, wie, kiedy to się dzieje.
Wynikiem tego wywołania funkcji select () może być: nowe dane w gnieździe łączą się z X11, przyszedł pakiet do portu UDP, na którym nasłuchujemy itd. - To nie jest ani sygnał Qt, ani zdarzenie Qt, a Główna pętla Qt sama decyduje, czy zamienia świeże dane w jedno, drugie, czy też je ignoruje.
Qt może wywołać metodę (lub kilka), taką jak keyPressEvent (), skutecznie zamieniając ją w zdarzenie Qt. Lub Qt emituje sygnał, który w efekcie wyszukuje wszystkie funkcje zarejestrowane dla tego sygnału i wywołuje je jedna po drugiej.
Jedna różnica między tymi dwoma koncepcjami jest tutaj widoczna: slot nie ma głosowania nad tym, czy inne automaty zarejestrowane dla tego sygnału zostaną wywołane, czy nie. - Zdarzenia są bardziej jak łańcuch, a program obsługi zdarzeń decyduje, czy przerywa ten łańcuch, czy nie. Pod tym względem sygnały wyglądają jak gwiazda lub drzewo.
Zdarzenie może wyzwolić sygnał lub całkowicie przekształcić go w sygnał (wystarczy go wyemitować i nie wywoływać „super ()”). Sygnał można przekształcić w zdarzenie (wywołanie programu obsługi zdarzenia).
Co abstrahuje, co zależy od przypadku: clicked () - sygnał streszcza zdarzenia myszy (przycisk opuszcza się i ponownie podnosi bez zbytniego poruszania się). Zdarzenia klawiatury to abstrakcje z niższych poziomów (takie rzeczy jak 果 lub é to kilka naciśnięć klawiszy w moim systemie).
Może focusInEvent () jest przykładem odwrotności: mógłby użyć (a tym samym abstrakcyjnego) sygnału clicked (), ale nie wiem, czy faktycznie tak jest.
Zdarzenia są wysyłane przez pętlę zdarzeń. Każdy program GUI wymaga pętli zdarzeń, niezależnie od tego, co napiszesz Windows lub Linux, używając Qt, Win32 lub dowolnej innej biblioteki GUI. Każdy wątek ma również własną pętlę zdarzeń. W Qt „GUI Event Loop” (główna pętla wszystkich aplikacji Qt) jest ukryta, ale zaczynasz ją wywoływać:
QApplication a(argc, argv);
return a.exec();
Komunikaty i inne aplikacje wysyłane do programu są wysyłane jako zdarzenia.
Sygnały i sloty to mechanizmy Qt. W procesie kompilacji z wykorzystaniem moc (kompilator meta-obiektowy) są one zamieniane na funkcje zwrotne.
Wydarzenie powinno mieć jednego odbiorcę, który powinien je wysłać. Nikt inny nie powinien dostać tego wydarzenia.
Wszystkie sloty podłączone do emitowanego sygnału zostaną wykonane.
Nie powinieneś myśleć o sygnałach jako o zdarzeniach, ponieważ jak możesz przeczytać w dokumentacji Qt:
Kiedy sygnał jest emitowany, gniazda do niego podłączone są zwykle wykonywane natychmiast, tak jak normalne wywołanie funkcji. Kiedy tak się dzieje, mechanizm sygnałów i gniazd jest całkowicie niezależny od pętli zdarzeń GUI.
Kiedy wysyłasz zdarzenie, musi poczekać jakiś czas, aż pętla zdarzeń wywoła wszystkie zdarzenia, które nadeszły wcześniej. Z tego powodu wykonanie kodu po wysłaniu zdarzenia lub sygnału jest inne. Kod następujący po wysłaniu zdarzenia zostanie uruchomiony natychmiast. W przypadku mechanizmów sygnałów i gniazd zależy to od typu połączenia. Zwykle zostanie wykonane po wszystkich slotach. Używając Qt :: QueuedConnection, zostanie to wykonane natychmiast, podobnie jak zdarzenia. Sprawdź wszystkie typy połączeń w dokumentacji Qt .
When you send an event, it must wait for time when event loop dispatch all events that came earlier. Because of this, execution of the cod after sending event or signal is different
Jest artykuł, który szczegółowo omawia przetwarzanie zdarzeń: http://www.packtpub.com/article/events-and-signals
Omawia różnicę między zdarzeniami a sygnałami tutaj:
Zdarzenia i sygnały to dwa równoległe mechanizmy służące do osiągnięcia tego samego. Ogólna różnica polega na tym, że sygnały są przydatne podczas korzystania z widgetu, podczas gdy zdarzenia są przydatne podczas wdrażania widgetu. Na przykład, kiedy używamy widżetu takiego jak QPushButton, bardziej interesuje nas jego sygnał clicked () niż niskopoziomowe naciśnięcie myszy lub zdarzenia naciśnięcia klawisza, które spowodowały wyemitowanie sygnału. Ale jeśli implementujemy klasę QPushButton, bardziej interesuje nas implementacja kodu dla zdarzeń myszy i klawiszy. Ponadto zwykle obsługujemy zdarzenia, ale otrzymujemy powiadomienia o emisji sygnałów.
Wydaje się, że jest to powszechny sposób mówienia o tym, ponieważ przyjęta odpowiedź zawiera niektóre z tych samych zwrotów.
Uwaga, proszę zapoznać się z pomocnymi komentarzami poniżej tej odpowiedzi Kuby Obera, które sprawiają, że zastanawiam się, czy może to być trochę uproszczone.
event
. Sygnały i szczeliny są konkretnie metodami, podczas gdy mechanizm połączenia jest strukturą danych, która pozwala sygnałowi wywołać jedną lub więcej szczelin wymienionych jako połączone z nią. Mam nadzieję, że widzicie, że mówienie o sygnale / szczelinach jako o jakimś „podzbiorze” lub „wariancie” wydarzeń jest nonsensem i na odwrót. Oni naprawdę są różne rzeczy, które zdarzają się być używany do podobnych celów w kontekście niektórych widżetów. Otóż to. Im bardziej uogólniasz, tym mniej pomocny stajesz się IMHO.
TL; DR: Sygnały i szczeliny to pośrednie wywołania metod. Zdarzenia to struktury danych. Więc są to zupełnie inne zwierzęta.
Jedynym momentem, w którym się łączą, jest wykonywanie wywołań szczelin poza granicami wątku. Argumenty wywołania gniazda są pakowane w strukturę danych i wysyłane jako zdarzenie do kolejki zdarzeń wątku odbierającego. W wątku odbierającym QObject::event
metoda rozpakowuje argumenty, wykonuje wywołanie i prawdopodobnie zwraca wynik, jeśli było to połączenie blokujące.
Jeśli jesteśmy skłonni uogólniać na zapomnienie, można by pomyśleć o zdarzeniach jako o sposobie wywołania metody obiektu docelowego event
. W pewnym sensie jest to wywołanie metody pośredniej - ale nie sądzę, aby było to pomocne w myśleniu o tym, nawet jeśli jest to prawdziwe stwierdzenie.
Zdarzenia (w ogólnym sensie interakcji użytkownik / sieć) są zwykle obsługiwane w Qt za pomocą sygnałów / gniazd, ale sygnały / gniazda mogą robić wiele innych rzeczy.
QEvent i jego podklasy to w zasadzie tylko małe standardowe pakiety danych dla frameworka do komunikacji z kodem. Jeśli chcesz w jakiś sposób zwrócić uwagę na mysz, wystarczy spojrzeć na QMouseEvent API, a projektanci biblioteki nie muszą wymyślać koła na nowo za każdym razem, gdy musisz dowiedzieć się, co zrobiła mysz w jakimś rogu API Qt.
Prawdą jest, że jeśli czekasz na jakieś zdarzenia (znowu w ogólnym przypadku), Twój automat prawie na pewno zaakceptuje podklasę QEvent jako argument.
Mając to na uwadze, sygnały i szczeliny z pewnością mogą być używane bez QEvents, chociaż przekonasz się, że pierwotnym impulsem do aktywacji sygnału często będzie jakaś interakcja użytkownika lub inna asynchroniczna aktywność. Czasami jednak twój kod po prostu osiągnie punkt, w którym odpalenie określonego sygnału będzie właściwą rzeczą do zrobienia. Na przykład odpalenie sygnału podłączonego do paska postępu podczas długiego procesu nie obejmuje QEvent do tego momentu.
Znalazłem to pytanie podczas czytania „Przetwarzania zdarzeń” Leow Wee Khenga. Mówi również:
Jasmine Blanchette mówi:
Głównym powodem, dla którego należy używać zdarzeń zamiast standardowych wywołań funkcji lub sygnałów i szczelin, jest to, że zdarzenia mogą być używane zarówno synchronicznie, jak i asynchronicznie (w zależności od tego, czy wywołujesz sendEvent (), czy postEvents ()), podczas wywoływania funkcji lub wywoływania gniazdo jest zawsze synchroniczne. Kolejną zaletą wydarzeń jest to, że można je filtrować.
Kolejna drobna uwaga pragmatyczna: wysyłanie lub odbieranie sygnałów wymaga dziedziczenia, QObject
podczas gdy obiekt dowolnego dziedziczenia może publikować lub wysyłać zdarzenie (ponieważ wywołujesz QCoreApplication.sendEvent()
lub postEvent()
) Zwykle nie jest to problem, ale: aby używać sygnałów PyQt dziwnie wymaga, QObject
aby być pierwszą superklasą, i możesz nie chcieć zmieniać kolejności dziedziczenia tylko po to, aby móc wysyłać sygnały).
Moim zdaniem wydarzenia są całkowicie zbędne i można by je wyrzucić. Nie ma powodu, dla którego sygnały nie mogłyby zostać zastąpione przez zdarzenia lub zdarzenia przez sygnały, z wyjątkiem tego, że Qt jest już ustawione tak, jak jest. Sygnały w kolejce są opakowane w zdarzenia, a zdarzenia mogą być opakowane w sygnały, na przykład:
connect(this, &MyItem::mouseMove, [this](QMouseEvent*){});
Zastąpiłby wygodną mouseMoveEvent()
funkcję znajdującą się w QWidget
(ale QQuickItem
już nie) i obsługiwałby mouseMove
sygnały, które menedżer sceny emitowałby dla elementu. Fakt, że sygnał jest emitowany w imieniu przedmiotu przez jakąś zewnętrzną jednostkę jest nieistotny i zdarza się dość często w świecie komponentów Qt, chociaż podobno jest to niedozwolone (komponenty Qt często omijają tę zasadę). Ale Qt to konglomerat wielu różnych decyzji projektowych i prawie rzucony w kamień ze strachu przed złamaniem starego kodu (co i tak zdarza się dość często).
QObject
, że w razie potrzeby propagują hierarchię nadrzędny-podrzędny. Połączenia sygnału / gniazda są po prostu obietnicą wywołania funkcji, bezpośrednio lub pośrednio, gdy zostanie spełniony określony warunek. Nie ma powiązanej hierarchii obsługi z sygnałami i gniazdami.
accepted
parametr odniesienia do sygnału i gniazd obsługujących go lub możesz mieć strukturę jako parametr odniesienia z accepted
polem. Ale Qt dokonał wyborów projektowych, które podjęła i są teraz ustalone w kamieniu.