Tworzę program, w którym wszystkie kontrolki dziedziczące po TCustomListBox
, TComboBox
czy wszelkie TMenu
/TPopupMenu
itd. malowane są ręcznie; W każdej z metod malujących korzystam z rekordu przechowującego potrzebne kolory:
type
TApplicationColors = packed record
Font: packed record
Normal,
Hot,
Disabled: TColor;
end;
Background: packed record
NormalOrDisabled,
Hot: TAppBackColors;
end;
Misc: packed record
MenuBar,
Line: TColor;
end;
end;
Uzupełnienie tego rekordu realizuję przy starcie aplikacji (po prostu pobieram kolory takie jak clMenuHighlight
, clHighlight
, clWindowText
itd. i wpisuję je do odpowiednich pól w rekordzie);
I teraz potrzebuję zareagować na zmianę schematu systemowych kolorów, czyli na nowo uzupełnić ww. rekord nowymi kolorami, jakie od chwili zmiany obowiązują w systemie; Wszystkie kontrolki, które są automatycznie malowane (czyli ręcznie ich nie maluję), TEdit
czy TSpinEdit
od razu po zmianie kolorów obsługują już nowe kolory; Te które maluję ręcznie (w zdarzeniach OnDrawItem
), niestety nie; Dlatego wychwytuję komunikat WM_SYSCOLORCHANGE
i aktualizuję rekord kolorów; Wykorzystuję następującą procedurę do obsługi komunikatów:
private
procedure MyOnMessage(var AMsg: TMsg; var AHandled: Boolean);
{...}
procedure TEngine.MyOnMessage(var AMsg: TMsg; var AHandled: Boolean);
const
WM_SYSTEM_COLORS_CHANGED = Cardinal($31B);
begin
case AMsg.message of
WM_SYSTEM_COLORS_CHANGED: { aktualizacja rekordu kolorów };
end;
end;
{...}
constructor TEngine.Create();
begin
inherited Create();
Application.OnMessage := MyOnMessage;
{...}
end;
Sprawdzałem jaki komunikat ląduje w metodzie MyOnMessage
po zmianie kolorów w systemie - dostaję cztery komunikaty, kolejno (kody szesnastkowe):
$31B
$F
$31B
$F
Komunikat o kodzie $F
to pewnie WM_PAINT
, bo trafia do obsługi non-stop, nawet podczas przesuwania kursorem po formularzu (ten jest nieistotny); Za to $31B
pokazuje się tylko po zmianie kolorów w systemie; Normalnie komunikat WM_SYSCOLORCHANGE
ma wartość 21
(hex $15
), jednak po zmianie systemowych kolorów takowy nie dociera do metody MyOnMessage
, więc tak jak sugerował @olesio - możliwe, że został po drodze przetłumaczony;
W każdym razie kominikat o kodzie $31B
dociera do tej metody bez problemu; Dlaczego trafia dwa razy - nie mam pojęcia, jednak na MSDN jest napisane, że ten komunikat wędruje po kontrolkach aż wróci z powrotem i wtedy dalej już nie jest wysyłany, tak więc AHandled
nie można ustawiń na True
, bo pół aplikacji nie zostanie przemalowane, no i tego nie robię;
Podczas, gdy komunikat ten trafia do MyOnMessage
- wykonuję jedynie aktualizację rekordu kolorów; Jednak aktualizacja nie działa prawidłowo, ponieważ najwidoczniej podczas otrzymania tego komunikatu nowe kolory jeszcze nie zostały w systemie zaktualizowane i zostają bez zmian; Kolejne ustawienie kolorów i obsłużenie komunikatu ustawia mi poprzednie kolory w rekordzie; Opiszę krok po kroku jak to wygląda (pod WinXP):
* Uruchomienie aplikacji i pobranie aktualnych kolorów (Oliwkowy)
Schemat: Oliwkowy
W rekordzie: Oliwkowy
* Zmiana na Niebieski
Schemat: Niebieski
W rekordzie: Oliwkowy
* Zmiana na Srebrny
Schemat: Srebrny
W rekordzie: Niebieski
* Zmiana na Oliwkowy
Schemat: Oliwkowy
W rekordzie: Srebrny
itd.
Więc zawsze po zmianie do rekordu wpisywane są poprzednie kolory, a nie bieżące; Myślałem, że coś namieszałem w swoim programie, więc napisałem mały dla testów - wynik ten sam - takie samo zachowanie; Wygląda to tak, jakby najpierw system wysyłał komunikat, a później zmieniał kolory w systemie;
Nie chcę kombinować i opóźniać np. Sleep
'em czy tym bardziej pobierać kolory z systemu przy każdym wywołaniu każdej metody OnDrawItem
; Myślę, że jest jakieś normalne rozwiązanie bez kombinowania;
Moje pytanie brzmi: W jaki sposób poprawnie zareagować na zmianę kolorów w systemie tak, by kontrolki ręcznie malowane także pomalowały się nowymi kolorami?
Na MSDN napisane jest, że system wysyła dodatkowo komunikat WM_PAINT
, jednak ręczne wysłanie tego komunikatu nie pomaga, bo kontrolki do malowania (ręcznego) dalej wykorzystują stary schemat; Na to wychodzi, że po odebraniu komunikatu o zmianie kolorów w systemie do rekordu wpisywane są nadal stare, a po obsłudze tego komunikatu dopiero zmiany zostawały faktycznie wprowadzone;
Co ciekawe, jeśli wpiszę takie coś:
procedure TEngine.MyOnMessage(var AMsg: TMsg; var AHandled: Boolean);
const
WM_SYSTEM_COLORS_CHANGED = Cardinal($31B);
begin
case AMsg.message of
WM_SYSTEM_COLORS_CHANGED:
begin
Application.MessageBox( {treść dowolna} );
{ aktualizacja rekordu kolorów }
end;;
end;
end;
Wtedy zadziała, jakby tym MessageBox
'em opóźniło się pobranie nowych kolorów do rekordu i system zdążył je wprowadzić; Dziwne, ale prawdziwe; Nie chcę wykorzystywać MessageBox
'a bo myślę, że można to zrobić po ludzku; Poza tym mam własne okno powiadomień;
W załączniku podaję źródło tego małego testowego programu, który ma identyczną metodę obsługującą komunikaty; Jeśli znacie rozwiązanie to przedstawcie je na kodzie z tego programu - jak zadziała to odpowiednio przerobię głowny projekt;
To w sumie tyle, proszę o jakieś wskazówki, z góry Bóg zapłać...
EDIT: Sleep
nie pomaga, widać po deaktywacji okna przez MessageBox
zmiany zostają wprowadzone, ale dlaczego - nie wiem...