From: "Sean Dockery" Subject: Palettes and windowed controls Date: 06 Jan 2000 00:00:00 GMT Message-ID: <853a9v$qmo17@bornews.borland.com> X-Priority: 3 X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2615.200 Organization: Another Netscape Collabra Server User X-MSMail-Priority: Normal Newsgroups: borland.public.delphi.graphics Could someone please tell me how I can reliably get windowed controls to abide by changes to the system palette? We noticed a problem in one of our applications running in 256 colour mode. It uses the palette handling techniques demonstrated in Eric Engler's Delphi 1 palette handling application. The problem is that when a form is handling WM_PALETTECHANGED, embedded device contexts (ie: any TWinControl or descendant) don't use the system palette when they're repainted. We had narrowed the problem down to a custom control which displays graphics, but it appears that it is also a problem with graphic controls contained in a windowed control. I wrote a short-ish application that demonstrates the problem. It contains two paintboxes and one panel. One of the paintboxes is contained within the panel. Both paintboxes draw the same bitmap. The palette handling routines both use the bitmap's palette. There are three methods of how the palettes are handled; they are: 1) Original: These routines apply themselves to the form. These are Eric Engler's routines; I have probably changed variable names and comments, but the logic should be exactly the same. 2) Generic: These routines apply themselves to the panel first, then the form. 3) Specific: These routines apply themselves to the form and the panel at the same time. The palette handling method is determined in the FormCreate event. The "original" method is flawlessly reliable and consistent, except that it doesn't work when there are windowed controls. Both the "generic" and "specific" methods are all over the map as to their consistency and reliability. Sometimes they work perfectly; mostly they're out to lunch. Using the "original" method, the problem (again) is that when the WM_PALETTECHANGED message is processed (when activating an IE or Netscape window), the paintbox within the panel does not paint using the updated system palette. It uses it's original palette which, because it is mapping to the system palette, causes the image to look horrible. I confirmed this by examining the system palette index values when the application was active and inactive using Peter Beyersdorf's CurrentPalette application. The graphic that I used is a 256 (216) colour bitmap of web safe colours. You should be able to substitute any 256 colour bitmap in its place. If you don't have one or a means to create one, I'll email you the one that I used. (Its about 2K zipped.) With all that said: How can I reliably get windowed controls to abide by changes to the system palette? Here are the files for the application which demonstrates the problem: unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls; type TPaletteHandlingMethod = (phmOriginal, phmGeneric, phmSpecific); TForm1 = class(TForm) PaintBox1: TPaintBox; Panel1: TPanel; PaintBox2: TPaintBox; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormShow(Sender: TObject); procedure PaintBox1Paint(Sender: TObject); private { Private declarations } FBitmap: TBitmap; FPaletteHandlingMethod: TPaletteHandlingMethod; procedure ApplicationDeactivate(Sender: TObject); procedure WMPaletteChanged1(var Msg: TWMPaletteChanged); procedure WMQueryNewPalette1(var Msg: TWMQueryNewPalette); procedure WMPaletteChanged2(var Msg: TWMPaletteChanged); procedure WMQueryNewPalette2(var Msg: TWMQueryNewPalette); procedure WMPaletteChanged3(var Msg: TWMPaletteChanged); procedure WMQueryNewPalette3(var Msg: TWMQueryNewPalette); protected procedure WMPaletteChanged(var Msg: TWMPaletteChanged); message WM_PALETTECHANGED; procedure WMQueryNewPalette(var Msg: TWMQueryNewPalette); message WM_QUERYNEWPALETTE; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); var FileName: String; begin FPaletteHandlingMethod := phmSpecific; // one of (phmOriginal, phmGeneric, phmSpecific) FileName := ExtractFilePath(Application.ExeName) + 'web.bmp'; FBitmap := TBitmap.Create; FBitmap.LoadFromFile(FileName); Application.OnDeactivate := ApplicationDeactivate; end; procedure TForm1.FormDestroy(Sender: TObject); begin FBitmap.Free; end; procedure TForm1.FormShow(Sender: TObject); begin SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOMOVE); end; procedure TForm1.ApplicationDeactivate(Sender: TObject); begin SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE or SWP_NOSIZE or SWP_NOMOVE); end; procedure TForm1.PaintBox1Paint(Sender: TObject); begin (Sender as TPaintBox).Canvas.Draw(0, 0, FBitmap); end; procedure TForm1.WMQueryNewPalette(var Msg: TWMQueryNewPalette); begin case FPaletteHandlingMethod of phmOriginal: WMQueryNewPalette1(Msg); phmGeneric: WMQueryNewPalette2(Msg); phmSpecific: WMQueryNewPalette3(Msg); else WMQueryNewPalette1(Msg); end; end; procedure TForm1.WMPaletteChanged(var Msg: TWMPaletteChanged); begin case FPaletteHandlingMethod of phmOriginal: WMPaletteChanged1(Msg); phmGeneric: WMPaletteChanged2(Msg); phmSpecific: WMPaletteChanged3(Msg); else WMPaletteChanged1(Msg); end; end; procedure TForm1.WMPaletteChanged1(var Msg: TWMPaletteChanged); var DC: HDC; OldPalette: HPALETTE; begin // If the message was not generated by this window, respond to it // appropriately. if Msg.PalChg <> Handle then begin // Get the device context from the control's handle. I have found this // to be more reliable than Canvas.Handle. DC := GetDC(Handle); try // Select our palette into the device context. Save the palette that was // in effect. OldPalette := SelectPalette(DC, FBitmap.Palette, true); try // Realise the palette. Windows will map colours from our logical // palette into the system palette. The return value is the number of // unique colours that it created in the system palette on our behalf. // Note that this will not change the 20 colours reserved for Windows // itself. RealizePalette(DC); // UpdateColors is Microsoft's preferred method of refreshing an // on-screen Window if the system palette changes. UpdateColors(DC); finally // Restore the original palette into the device context before we // release it. SelectPalette(DC, OldPalette, false); end; finally // Release the device context that we got from Windows. ReleaseDC(Handle, DC); end; Msg.Result := 1; end // Because the message was generated by this window, it was in response // to a change that it made in the palette, so it should be ingored. else // Msg.PalChg = Handle Msg.Result := 0; end; procedure TForm1.WMPaletteChanged2(var Msg: TWMPaletteChanged); var DC: HDC; OldPalette: HPALETTE; Index: Integer; Control: TControl; begin // If the message was not generated by this window, respond to it // appropriately. if Msg.PalChg <> Handle then begin // handle it for embedded windowed controls. for Index := ControlCount - 1 downto 0 do begin Control := Controls[Index]; if Control is TWinControl then begin with TWinControl(Control) do begin DC := GetDC(Handle); try // Select our palette into the device context. Save the palette that was // in effect. OldPalette := SelectPalette(DC, FBitmap.Palette, true); try // Realise the palette. Windows will map colours from our logical // palette into the system palette. The return value is the number of // unique colours that it created in the system palette on our behalf. // Note that this will not change the 20 colours reserved for Windows // itself. RealizePalette(DC); // UpdateColors is Microsoft's preferred method of refreshing an // on-screen Window if the system palette changes. UpdateColors(DC); finally // Restore the original palette into the device context before we // release it. SelectPalette(DC, OldPalette, false); end; finally // Release the device context that we got from Windows. ReleaseDC(Handle, DC); end; end; end; end; // now for the form itself... DC := GetDC(Handle); try // Select our palette into the device context. Save the palette that was // in effect. OldPalette := SelectPalette(DC, FBitmap.Palette, true); try // Realise the palette. Windows will map colours from our logical // palette into the system palette. The return value is the number of // unique colours that it created in the system palette on our behalf. // Note that this will not change the 20 colours reserved for Windows // itself. RealizePalette(DC); // UpdateColors is Microsoft's preferred method of refreshing an // on-screen Window if the system palette changes. UpdateColors(DC); finally // Restore the original palette into the device context before we // release it. SelectPalette(DC, OldPalette, false); end; finally // Release the device context that we got from Windows. ReleaseDC(Handle, DC); end; Msg.Result := 1; end // Because the message was generated by this window, it was in response // to a change that it made in the palette, so it should be ingored. else // Msg.PalChg = Handle Msg.Result := 0; end; procedure TForm1.WMPaletteChanged3(var Msg: TWMPaletteChanged); var DC: HDC; OldPalette: HPALETTE; PanelDC: HDC; PanelOldPalette: HPALETTE; begin // If the message was not generated by this window, respond to it // appropriately. if Msg.PalChg <> Handle then begin // Get the device context from the control's handle. I have found this // to be more reliable than Canvas.Handle. DC := GetDC(Handle); PanelDC := GetDC(Panel1.Handle); try // Select our palette into the device context. Save the palette that was // in effect. OldPalette := SelectPalette(DC, FBitmap.Palette, true); PanelOldPalette := SelectPalette(PanelDC, FBitmap.Palette, true); try // Realise the palette. Windows will map colours from our logical // palette into the system palette. The return value is the number of // unique colours that it created in the system palette on our behalf. // Note that this will not change the 20 colours reserved for Windows // itself. RealizePalette(DC); RealizePalette(PanelDC); // UpdateColors is Microsoft's preferred method of refreshing an // on-screen Window if the system palette changes. UpdateColors(DC); UpdateColors(PanelDC); finally // Restore the original palette into the device context before we // release it. SelectPalette(DC, OldPalette, false); SelectPalette(PanelDC, PanelOldPalette, false); end; finally // Release the device context that we got from Windows. ReleaseDC(Handle, DC); ReleaseDC(Panel1.Handle, PanelDC); end; Msg.Result := 1; end // Because the message was generated by this window, it was in response // to a change that it made in the palette, so it should be ingored. else // Msg.PalChg = Handle Msg.Result := 0; end; procedure TForm1.WMQueryNewPalette1(var Msg: TWMQueryNewPalette); var DC: HDC; OldPalette: HPALETTE; DC2: HDC; OldPalette2: HPALETTE; ColorCount: Cardinal; TotalCount: Cardinal; Index: Integer; Control: TControl; begin // Get the device context from the control's handle. I have found this // to be more reliable than Canvas.Handle. DC := GetDC(Handle); try // Select our palette into the device context. Save the palette that was // in effect. OldPalette := SelectPalette(DC, FBitmap.Palette, false); try // Realise the palette. Windows will map colours from our logical // palette into the system palette. The return value is the number of // unique colours that it created in the system palette on our behalf. // Note that this will not change the 20 colours reserved for Windows // itself. ColorCount := RealizePalette(DC); finally // Restore the original palette into the device context before we // release it. SelectPalette(DC, OldPalette, false); end; finally // Release the device context that we got from Windows. ReleaseDC(Handle, DC); end; // If Windows changed the content of the system palette by creating any // colour entries during the RealizePalette call, we have to repaint // ourselves. if ColorCount > 0 then Invalidate; // Return the number of colours that Windows created for us. Msg.Result := ColorCount; end; procedure TForm1.WMQueryNewPalette2(var Msg: TWMQueryNewPalette); var DC: HDC; OldPalette: HPALETTE; ColorCount: Cardinal; TotalCount: Cardinal; Index: Integer; Control: TControl; begin TotalCount := 0; // handle embedded windowed controls... for Index := ControlCount - 1 downto 0 do begin Control := Controls[Index]; if Control is TWinControl then begin with TWinControl(Control) do begin // Get the device context from the control's handle. I have found this // to be more reliable than Canvas.Handle. DC := GetDC(Handle); try // Select our palette into the device context. Save the palette that was // in effect. OldPalette := SelectPalette(DC, FBitmap.Palette, false); try // Realise the palette. Windows will map colours from our logical // palette into the system palette. The return value is the number of // unique colours that it created in the system palette on our behalf. // Note that this will not change the 20 colours reserved for Windows // itself. ColorCount := RealizePalette(DC); Inc(TotalCount, ColorCount); finally // Restore the original palette into the device context before we // release it. SelectPalette(DC, OldPalette, false); end; finally // Release the device context that we got from Windows. ReleaseDC(Handle, DC); end; end; end; end; // now for the form... // Get the device context from the control's handle. I have found this // to be more reliable than Canvas.Handle. DC := GetDC(Handle); try // Select our palette into the device context. Save the palette that was // in effect. OldPalette := SelectPalette(DC, FBitmap.Palette, false); try // Realise the palette. Windows will map colours from our logical // palette into the system palette. The return value is the number of // unique colours that it created in the system palette on our behalf. // Note that this will not change the 20 colours reserved for Windows // itself. ColorCount := RealizePalette(DC); Inc(TotalCount, ColorCount); finally // Restore the original palette into the device context before we // release it. SelectPalette(DC, OldPalette, false); end; finally // Release the device context that we got from Windows. ReleaseDC(Handle, DC); end; // If Windows changed the content of the system palette by creating any // colour entries during the RealizePalette call, we have to repaint // ourselves. if TotalCount > 0 then Invalidate; // Return the number of colours that Windows created for us. Msg.Result := TotalCount; end; procedure TForm1.WMQueryNewPalette3(var Msg: TWMQueryNewPalette); begin // Because the palette is shared by the entire form, if Windows lets us have // any colours from the system palette, the whole form will be invalidated, // there is no point in handling Form1 and Panel1 separately. WMQueryNewPalette1(Msg); end; end. object Form1: TForm1 Left = 48 Top = 86 Width = 429 Height = 192 Caption = 'Form1' Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] OldCreateOrder = False OnCreate = FormCreate OnDestroy = FormDestroy OnShow = FormShow PixelsPerInch = 96 TextHeight = 13 object PaintBox1: TPaintBox Left = 8 Top = 16 Width = 180 Height = 120 OnPaint = PaintBox1Paint end object Panel1: TPanel Left = 200 Top = 8 Width = 209 Height = 145 Caption = 'Panel1' TabOrder = 0 object PaintBox2: TPaintBox Left = 16 Top = 8 Width = 180 Height = 120 OnPaint = PaintBox1Paint end end end -- Sean Dockery Cybersurf Corporation sean.dockery@cybersurf.net www.cybersurf.net