Niezależna obsługa Menu w WinAPI?

0

Witam,
Mam okno aplikacji C++ WinAPI z utworzonym Menu w pliku zasobów (*.rc). W funkcji WinMain obsługuję odmierzanie czasu, portów, pętlę komunikatów itp... Problem polega na tym, że po rozwinięciu Menu okna aplikacji, sterowanie zostaje zabrane z mojej aplikacji do "jakiejś innej systemowej" procedury obsługi Menu, i dopiero po wybraniu konkretnego polecenia z Menu, albo zamknięciu Menu, odzyskuję sterowanie z powrotem. Proszę o podpowiedź gdzie szukać rozwiązania, aby rozwinięcie Menu okna, nie wstrzymywało wykonywania aplikacji?
Pozdrawiam.
Marek

0

Pierwsze, co mi przychodzi do głowy (aczkolwiek nie jestem do końca przekonany, czy to optymalne rozwiązanie) to stworzyć wątek, który będzie odpowiadał za to, co Twoja apka tak na prawdę robi, który obsłuży cała tak zwaną "logikę biznesową".

Wszystko to, co jest związane z interface (typu obsługa komunikatów, interakcje z użytkownikiem itp) zostaw tak, jak jest aktualnie, ale obliczenia w tle, komunikację przez porty/sieć i inne rzeczy, których nie chcesz blokować, umieść w tym drugim wątku.

0

Osobny wątek zrobiłem dla "kompletnego" dialogu OtwórzPlik, to działa OK. Tylko że pętla mojej Apki obsługuje port COM wykorzystując do Timeoutów TIMERy z pętli komunikatów (m.in. WM_TIMER). Więc w utworzony wątek musiałbym "wsadzić" całą moją aplikację. Nawet próbowałem "wsadzić" tak moje CreateWindow, ale wtedy okno się nie wyświetla - chyba trzeba poszukać innego tropu. Podstawowy problem, to że nie wiem, choć przeszukałem MSDN, gdzie przekazywane jest sterowanie z mojej aplikacji w momencie "rozwinięcia" menu???

1

Jeśli timer multimedialny nie rozwiąże problemu i chcesz podczepić się pod wewnętrzną procedurę menu, to spójrz tu:
https://docs.microsoft.com/en-us/windows/win32/winmsg/about-window-classes

System tworzy okienko dla menu, którego nazwą klasy jest #32768.
Na początku programu utwórz okno z tej klasy (daj styl WS_POPUP). Dostaniesz uchwyt okna. I teraz możesz podmienić tę jego własną procedurę (SetClassLong). Nie zapomnij we własnej procedurze wywoływać procedurę oryginalną (CallWindowProc).
To działa na pewno, bo tak mam zrobione we wszystkich moich programach.

0

Dzięki Stefan. Timer multimedialny nie załatwi wszystkich problemów, bo w pętli WHILE mojej aplikacji WinMain także obsługuję nasłuchiwanie portów COM i oznaczanie ramek czasem rzeczywistym, od razu wyświetlanych na obszarze okna przez WM_PAINT. Więc nie chodzi tylko o jeden czy kilka timer'ów.
Z Subclassingiem klasy OKNA MENU może się udać. Tak na marginesie trochę mi to wolno idzie, bo program ma już ponad 6000 linii kodu (mimo, że są *.rc i wywaliłem co się dało do osobnych modułów), a rozwijam go tylko w wolnej chwili.
Mam jednak pytanko, w którym momencie zrobić tą podmiankę, bo przecież muszę najpierw zarejestrować klasę mojego OKNA GŁÓWNEGO, a nawet je utworzyć, żeby dobrać się do klasy jego MENU. Trochę nie kumam, co mógłbym tam PODMIENIĆ/DOPISAĆ? Nie mam zastrzeżeń do pętli obsługującej MENU. Bo mi chodzi tylko o to, że w momencie rozwinięcia (aktywacji) MENU Aplikacji, przestaje się wykonywać główna pętla WHILE.

2

Zrób to w WinMain jeszcze przed otwarciem okna głównego.
U mnie ten fragment wyglada tak:

WNDPROC oldMenuProc=0;
LRESULT newMenuProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
	switch(uMsg){
//----> Tu Twoje komunikaty
	}
	return CallWindowProc(oldMenuProc, hWnd, uMsg, wParam, lParam);
}
void InitMenuClass(HINSTANCE hInstance){
	HWND m=CreateWindow("#32768", "", WS_POPUP, 0,0,10,10,0,0,hInstance,0);
	if(m){
		oldMenuProc=(WNDPROC)SetClassLong(m, GCL_WNDPROC, (LONG)newMenuProc);
		DestroyWindow(m);
	}
}

W WinMain jest wywołanie

	InitMenuClass(hInstance);

Nie wiem co konkretnie chcesz uzyskać.
Pytałeś gdzie jest przenoszone sterowanie programem w momencie otwarcia menu. To sterowanie jest przenoszone do procedury okna, do której masz dostąp jak pokazałem wyżej.

Czy masz jakiś konkretny kod w głównej pętli komunikatów programu?
Jeśli tak, to chyba powinieneś przebudować program.

A jeśli nie, to główna pętla wywołuje Twoją procedurę okna z odpowiednim komunikatem (np. WM_TIMER).
Wtedy w WM_INITMENU (albo WM_INITMENUPOPUP), który wystąpi w momencie otwarcia menu zapuść SetTimer do tego okna przypisanego do menu.
Ale zwróć też uwagę, że SetTimer nie musi wysyłać komunikatów do okna, może też cyklicznie wykonywać dowolną inną procedurę. Może to rozwiąże problem?

Jeśli nie - to opisz problem dokładniej.

0

[Doprecyzowałem wcześniejszy wpis]
Tak, w głównej pętli WHILE mam odczytywanie buforów portów komunikacyjnych, zrzucanie ramek do struktury rejestrującej i wymuszanie odświeżania WM_PAINT, aby się wypisały w oknie. Po drodze jeszcze interpreter ramek itd.
OK, można podmienić (właściwie zedytować u siebie) procedurę MENU. Tylko, czy miałbym do niej skopiować całą moją Procedurę obsługi komunikatów? To ok 1500 linii. Poza tym wszystkie funkcje z mojej głównej pętli WHILE (w WinMain: czyli obsługa buforów odczytowych portów, czasu itd...) i tak się zatrzyma. Hmmm. Szukam dalej.

0

A po co kopiować 1500 linii?
Zrób z tego procedury. I tę procedurę (lub procedury), które odpowiadają za kontrolę w czasie wywołuj z różnych miejsc.
W głównej pętli komunikatów nie powinieneś mieć niczego poza TranslateMessage i DispatchMessage (i ewentualnie obsługą klawiszy skrótów).

0

...ale jak mam to rozumieć "z różnych miejsc". Czy mam to wywoływać w procedurze obsługi MENU? Nawet jak odczytam bufor (mam nową ramkę dopisaną do kolejki odebranych) i będę chciał ją napisać na oknie jako kolejny wiersz (to jest zrobione w WM_PAINT - wypisanie całej kolejki ramek), to przecież się nie napisze, bo WM_PAINT z mojej procedury okna jest zablokowane - wykonuje się tylko procedura MENU dopóki menu jest rozwinięte. Te procedury ciągłego nasłuchiwania portów (czyli sprawdzania czy wpadło coś do bufora odbiorczego, lub czy ustawiła się flaga ODEBRANO ZNAK/ZNAKI), powinny być wykonywane/sprawdzane maksymalnie często, i przez cały czas życia aplikacji, od startu do zamknięcia - po prostu NON STOP. Stąd uznałem, że najlepszym miejscem na to jest główna pętla programu - i TO SUPER CHODZI, jest przejrzyste, bez tworzenia żadnych dodatków. Lecz tylko do momentu gdy kliknę MENU :-(...
A może jest inny powód: Może blokuje się moja pętla programu bo czeka na jakąś obsługę. Czy ja nie powinienem mieć w mojej procedurze obsługi aplikacji jakiejś obsługi komunikatów od MENU? ...może powinienem obsługiwać jakieś WM_MENUSELECT albo INITMENU albo SYSCONTROL, czy coś w tym rodzaju. ...i jak tego nie obsługuję to procedura obsługi aplikacji nie zwraca wartości i się blokuje do czasu zwinięcia MENU?? Czy mógłbyś podpowiedzieć coś w tym tropie?
MSDN z jednej strony pisze, że moja główna pętla programu wykonuje się cały czas dla wszystkich okien utworzonych w tym wątku, a z drugiej strony pisze, że okno może być FOREGROUND pod warunkiem, że nie ma otwartego (rozwiniętego) menu. Bo wtedy, jak dobrze rozumiem, system operacyjny zabiera sterowanie z mojej aplikacji (ta się blokuje i instrukcje zawarte w pętli WHILE w WinMain się nie wykonują). Trochę się już pogubiłem, jak to właściwie jest.

0

Nie wiem co konkretnie robisz, ale czuję, że długa droga przed Tobą.
Komunikat WM_PAINT odświeża okno, na podstawie zebranych wcześniej (i gdzieś przechowywanych) danych.
W każdej chwili, kiedy okno dostanie WM_PAINT musisz mieć te dane pod ręką.
Czyli Twoje procedury czytania danych czytają je i gdzieś składują (w tablicach, kontenerach, itp.).
I to się dzieje niezależnie od tego, czy WM_PAINT jest czy go nie ma.

A WM_PAINT pobiera te dane i je wyświetla. Musisz tak to zorganizować, żeby to były dwa niezależne od siebie procesy.
Kiedy chcesz odświeżyć okno, wywołujesz po prostu Invalidate z uchwytem okna.

Czy zrobiłeś obsługę portów tak, jak to opisano tutaj?
https://docs.microsoft.com/pl-pl/windows/win32/devio/communications-resources
https://docs.microsoft.com/pl-pl/windows/win32/devio/communications-events
https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-waitcommevent

0

Z założenia miał to być prosty programik do komunikacji portami COM z bezpośrednim pisaniem ramek w oknie aplikacji (ale dla zwiększenia czytelności: pisaniem graficznym, tj, w WM_PAINT, bo różnicowanie wielkości i kolorów czcionek itd.), lecz (jak się potem okazało) także z wieloma Combobox'ami do konfigurowania różnych opcji transmisji: port, protokół, prędkość..., obsługa statystyk ramek itd... - stąd potrzeba obsługi okien, TIMER'ów itd.
Dane czytane i pisane do portów są zrzucane do dużej struktury przechowującej (w najprostszy sposób: w WinMain), skąd można je na różnie posortowany sposób wyciągać wg reguł selekcji - różne podzbiory/podrekordy - i tak wybrane są NA BIEŻĄCO pisane przez WM_PAINT (Invalidate) - stąd potrzebna CIĄGŁEJ obsługi komunikatów.
Obsługę portów zrobiłem wg takich zasad jak opisałeś, korzystając z odczytu bufora (np. dla pierwszego portu):

ClearCommError(hCOM[0],&COMERR[0],&COMSTA[0]);
ReadFile(hCOM[0],&ucBUFin[0][0],COMSTA[0].cbInQue,&byteread[0],NULL);
...

Napisałeś, że "Musisz tak to zorganizować, żeby to były dwa niezależne od siebie procesy."
Trzeba zauważyć, że teraz to świetnie działa w jednym procesie/wątku: w pętli WinMain.
Chyba raczej trzeba to tylko oddzielić od obsługi MENU okna. Przy czym wg MSDN:
OKNO utworzone w wątku posiada swoją własną Procedurę obsługi w tymże wątku, ale także własne Menu (dołączony zasób z pliku *.rc) - tzn. że ProceduraOkna (komunikaty) i jego MENU będą zawsze w tym samym wątku, a System Operacyjny w momencie rozwinięcia MENU odbiera wykonywanie pętli OKNA i przekazuje do pętli MENU - chyba że coś źle rozumiem.
I tu nie mam pomysłu :-(

2

Ty jesteś autorem i Ty decydujesz jaka jest struktura programu.
Ja Ci mogę tylko zasugerować, że silne wiązanie "silnika" z interfejsem nie jest dobrym rozwiązaniem.
Twój silnik (odczytywanie, gromadzenie, przetwarzanie danych) powinien działać bez interfejsu (czyli bez powiązania z WM_PAINT, z procedurą okna, itd.).
Twój silnik powinien mieć zestaw funkcji, które powinny odpowiadać na zapytania i wystawiać odpowiednie dane.

Twój interfejs powinien te pytania wysyłać do silnika i po otrzymaniu odpowiedzi wyświetlać te dane.

Jeśli zrobisz to tak, jak opisałem wyżej, to pytania, które zadajesz w ogóle nie wystąpią.
Takiego problemu, że menu coś tam blokuje przy prawidłowej strukturze programu w ogóle nie ma. Bo silnik i interfejs będą pracowały niezależnie od siebie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1