• Welcome to PlanetSquires Forums.
 

CWindow RC03

Started by José Roca, April 28, 2016, 08:05:53 AM

Previous topic - Next topic

José Roca

CWindow release candidate 3.

Added support for subclassing using SetWindowSubclass.

For the uIdSubclass parameter you can use any positive value except &hFFFFFFFF. CWindow uses the reserved default value of &hFFFFFFF to know if it has to use the old way of subclassing or the new way with SetWindowSubclass.

For more information about subclassing controls see:
https://msdn.microsoft.com/en-us/library/bb773183%28VS.85%29.aspx

José Roca

#1
The following example demonstrates how to subclass a button using the new support for SetWindowSubclass.


' ########################################################################################
' Microsoft Windows
' File: CW_ButtonSubclass2.fbtpl
' Contents: CWindow with a subclassed button
' Subclasses the control using SetWindowSubclass.
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2016 Jose Roca. Freeware. Use at your own risk.
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
' EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
' MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ########################################################################################

#define unicode
#INCLUDE ONCE "windows.bi"
#INCLUDE ONCE "Afx/CWindow.inc"

USING Afx.CWindowClass

CONST IDC_BUTTON = 1001

DECLARE FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
                          BYVAL hPrevInstance AS HINSTANCE, _
                          BYVAL szCmdLine AS ZSTRING PTR, _
                          BYVAL nCmdShow AS LONG) AS LONG

   END WinMain(GetModuleHandleW(""), NULL, COMMAND(), SW_NORMAL)

' ========================================================================================
' Window procedure
' ========================================================================================
FUNCTION WndProc (BYVAL hWnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT

   DIM pWindow AS CWindow PTR

   SELECT CASE uMsg

      CASE WM_CREATE
         EXIT FUNCTION

      CASE WM_COMMAND
         SELECT CASE LOWORD(wParam)
            ' // If ESC key pressed, close the application sending an WM_CLOSE message
            CASE IDCANCEL
               IF HIWORD(wParam) = BN_CLICKED THEN
                  SendMessageW hwnd, WM_CLOSE, 0, 0
                  EXIT FUNCTION
               END IF
         END SELECT

      CASE WM_SIZE
         IF wParam <> SIZE_MINIMIZED THEN
            ' // Resize the buttons
            pWindow = CAST(CWindow PTR, GetPropW(hwnd, "CWINDOWPTR"))
            pWindow->MoveWindow GetDlgItem(hwnd, IDC_BUTTON), pWindow->ClientWidth - 120, pWindow->ClientHeight - 50, 75, 23, CTRUE
         END IF

    CASE WM_DESTROY
         PostQuitMessage(0)
         EXIT FUNCTION

   END SELECT

   FUNCTION = DefWindowProcW(hWnd, uMsg, wParam, lParam)

END FUNCTION
' ========================================================================================

' ========================================================================================
' Processes messages for the subclassed Button window.
' ========================================================================================
FUNCTION Button_SubclassProc ( _
   BYVAL hwnd   AS HWND, _                 ' // Control window handle
   BYVAL uMsg   AS UINT, _                 ' // Type of message
   BYVAL wParam AS WPARAM, _               ' // First message parameter
   BYVAL lParam AS LPARAM, _               ' // Second message parameter
   BYVAL uIdSubclass AS UINT_PTR, _        ' // The subclass ID
   BYVAL dwRefData AS DWORD_PTR _          ' // Pointer to reference data
   ) AS LRESULT

   SELECT CASE uMsg

      CASE WM_GETDLGCODE
         ' // All keyboard input
         FUNCTION = DLGC_WANTALLKEYS
         EXIT FUNCTION

      CASE WM_LBUTTONDOWN
         MessageBoxW(GetParent(hwnd), "Click", "FreeBasic", MB_OK)
         EXIT FUNCTION

      CASE WM_KEYDOWN
         SELECT CASE LOWORD(wParam)
            CASE VK_ESCAPE
               SendMessageW(GetParent(hwnd), WM_CLOSE, 0, 0)
               EXIT FUNCTION
         END SELECT

      CASE WM_DESTROY
         ' // REQUIRED: Remove control subclassing
         RemoveWindowSubclass hwnd, @Button_SubclassProc, uIdSubclass

   END SELECT

   FUNCTION = DefSubclassProc(hwnd, uMsg, wParam, lParam)

END FUNCTION
' ========================================================================================

' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
                  BYVAL hPrevInstance AS HINSTANCE, _
                  BYVAL szCmdLine AS ZSTRING PTR, _
                  BYVAL nCmdShow AS LONG) AS LONG

   ' // Set process DPI aware
   AfxSetProcessDPIAware

   DIM pWindow AS CWindow
   pWindow.Create(NULL, "CWindow with a subclassed button", @WndProc)
   pWindow.SetClientSize(500, 320)
   pWindow.Center

   ' // Add a subclassed button without position or size (it will be resized in the WM_SIZE message).
   pWindow.AddControl("Button", pWindow.hWindow, IDC_BUTTON, "Click me", , , , , , , , _
      CAST(WNDPROC, @Button_SubclassProc), IDC_BUTTON, CAST(DWORD_PTR, @pWindow))

   

   FUNCTION = pWindow.DoEvents(nCmdShow)

END FUNCTION
' ========================================================================================


José Roca

#2
Two of the added benefits are that you can use the same subclass procedure for several controls and identify them thanks to the uIdSubclass (for example passing the ID of the control), and that you can pass a pointer to reference data.

José Roca

SetWindowSubclass, GetWindowSubclass, RemoveWindowSubclass and DefSubclassProc were made available for the first time in ComCtl32.dll version 6; therefore, can only be used with Windows XP or superior.

ComCtl32.dll version 6 is Unicode only. The common controls supported by ComCtl32.dll version 6 should not be subclassed (or superclassed) with ANSI window procedures.

José Roca

#4
Next version will include a little class to create tooltips.


' ########################################################################################
' CToolTip class
' Creates a tooltip control
' ########################################################################################
TYPE CTooltip

   Private:
      m_hTooltip AS HWND   ' // Tooltip handle
      m_hwndCtrl AS HWND   ' // Handle of the control
      m_cbSize AS LONG     ' // Size of the TOOLINFO structure
      m_bBalloon AS LONG   ' // Balloon
      m_bCentered AS LONG  ' // Centered

   Public:
      DECLARE CONSTRUCTOR (BYVAL hwndCtrl AS HWND, BYREF wszText AS CONST WSTRING = "", BYVAL bBalloon AS LONG = 0, BYVAL bCentered AS LONG = 0)
      DECLARE DESTRUCTOR
      DECLARE PROPERTY hTooltip () AS HWND
      DECLARE PROPERTY TipText (BYREF wszText AS CONST WSTRING)
      DECLARE SUB SetTitle (BYVAL hIcon AS HANDLE, BYREF wszTitle AS CONST WSTRING)

END TYPE
' ========================================================================================

' ========================================================================================
' CTooltip constructor
' Usage example: DIM pTooltip AS CTooltip = CTooltip(hButton, "This is a tooltip", CTRUE, CTRUE)
' ========================================================================================
CONSTRUCTOR CTooltip (BYVAL hwndCtrl AS HWND, BYREF wszText AS CONST WSTRING = "", BYVAL bBalloon AS LONG = 0, BYVAL bCentered AS LONG = 0)
   ' // Creates the tooltip control
   DIM dwStyle AS DWORD = WS_POPUP OR TTS_ALWAYSTIP
   IF bBalloon THEN dwStyle = dwStyle OR TTS_BALLOON
   IF .IsWindow(hwndCtrl) THEN
      m_hTooltip = .CreateWindowExW(0, "tooltips_class32", "", dwStyle, 0, 0, 0, 0, NULL, NULL, NULL, NULL)
      IF m_hTooltip THEN
      END IF
   END IF
   ' // Registers the window with the tooltip control
   ' // 32-bit: The size of the TOOLINFO structure is of 48 bytes in
   '    version 6 of comctl32.dll, and of 44 bytes in lower versions.
   ' // 64-bit: The size of the TOOLINFO structure is of 72 bytes in
   '    version 6 of comctl32.dll, and of 64 bytes in lower versions.
   DIM tti AS TOOLINFOW
#ifdef __FB_64BIT__
   IF AfxComCtlVersion < 600 THEN m_cbSize = 64 ELSE m_cbSize = 72
#else
   IF AfxComCtlVersion < 600 THEN m_cbSize = 44 ELSE m_cbSize = 48
#endif
   tti.cbSize = m_cbSize
   tti.uFlags = TTF_IDISHWND OR TTF_SUBCLASS
   IF bCentered THEN tti.uFlags = tti.uFlags OR TTF_CENTERTIP
   m_hwndCtrl = hwndCtrl
   tti.hwnd = .GetParent(m_hwndCtrl)
   tti.hinst = .GetModuleHandleW(NULL)
   ' // The length of the string must not exceed of 80 characters, including the terminating null
   DIM wszTooltipText AS WSTRING * 80
   wszTooltipText = wszText
   tti.lpszText = @wszTooltipText
   tti.uId = CAST(UINT_PTR, m_hwndCtrl)
   .SendMessageW m_hTooltip, TTM_ADDTOOLW, 0, CAST(LPARAM, @tti)
   m_bBalloon = bBalloon
   m_bCentered = bCentered
END CONSTRUCTOR
' ========================================================================================

' ========================================================================================
' CTooltip destructor
' ========================================================================================
DESTRUCTOR CTooltip
   ' // Removes the tool from the tooltip control.
   DIM tti AS TOOLINFOW
   tti.cbSize = m_cbSize
   tti.hwnd = .GetParent(m_hwndCtrl)
   tti.uId = CAST(UINT_PTR, m_hwndCtrl)
   .SendMessageW(m_hTooltip, TTM_DELTOOLW, 0, CAST(LPARAM, @tti))
END DESTRUCTOR
' ========================================================================================

' ========================================================================================
' Returns the handle of the tooltip control
' ========================================================================================
PROPERTY CTooltip.hTooltip () AS HWND
   PROPERTY = m_hTooltip
END PROPERTY
' ========================================================================================

' ========================================================================================
' Sets/replaces the text of a tooltip control
' ========================================================================================
PROPERTY CTooltip.TipText (BYREF wszText AS CONST WSTRING)
   ' // The length of the text must not exceed of 80 characters, including the terminating null.
   DIM wszTooltipText AS WSTRING * 80
   wszTooltipText = wszText
   DIM tti AS TOOLINFOW
   tti.cbSize = m_cbSize
   tti.hwnd = .GetParent(m_hwndCtrl)
   tti.uId = CAST(UINT_PTR, m_hwndCtrl)
   ' // Retrieve the tooltip information
   .SendMessageW(m_hTooltip, TTM_GETTOOLINFOW, 0, CAST(LPARAM, @tti))
   IF .SendMessageW(m_hTooltip, TTM_GETTOOLINFOW, 0, CAST(LPARAM, @tti)) THEN
      tti.lpszText = @wszTooltipText
      .SendMessageW(m_hTooltip, TTM_SETTOOLINFOW, 0, CAST(LPARAM, @tti))
   END IF
END PROPERTY
' ========================================================================================

' ========================================================================================
' Set icon and/or title
' Usage example: pTooltip.SetTitle CAST(HANDLE, TTI_INFO), "Tooltip title"
' ========================================================================================
SUB CTooltip.SetTitle (BYVAL hIcon AS HANDLE, BYREF wszTitle AS CONST WSTRING)
   .SendMessageW m_hTooltip, TTM_SETTITLEW, CAST(WPARAM, hIcon), CAST(LPARAM, @wszTitle)
END SUB
' ========================================================================================


This part is very important:


#ifdef __FB_64BIT__
   IF AfxComCtlVersion < 600 THEN m_cbSize = 64 ELSE m_cbSize = 72
#else
   IF AfxComCtlVersion < 600 THEN m_cbSize = 44 ELSE m_cbSize = 48
#endif


because the size of the TOOLINFO structure should be different depending on the version of the CommCtrl library being used (5.xx without a manifest, 6.xx with a manifest).

Usage is very simple:


   ' // Add a button
   DIM hButton AS HWND = pWindow.AddControl("Button", pWindow.hWindow, IDCANCEL, "&Close", 350, 250, 75, 23)
   ' // Add a tooltip to the button
   DIM pTooltip AS CTooltip = CTooltip(hButton, "This is a button with a tooltip", TRUE, TRUE)
   ' // Optional: Set the icon and title
   pTooltip.SetTitle CAST(HANDLE, TTI_INFO), "Tooltip title"
   ' // To change the text, use:
'   pTooltip.TipText = "This is a modified text"


José Roca

#5
Will add a few methods more, like one to set the max width.

Other options less often used can be set with SendMessageW <pToolTip>.hTooltip, <message>, <wparam>, <lparam>.

José Roca

#6
I have written a function to allow to use .png icons in toolbars, etc. Have had some problems until I realized that BYTE is signed in FreeBasic and had to use UBYTE.

Because for some reason GDI+ makes gray icons darker that it should, I have added the option to specify a dimming percentage and gray conversion.

A manifest must be used to preserve the alpha channel.

Will write another one to load it from resources.


' ========================================================================================
' Loads an image from a file using GDI+, converts it to an icon and returns the icon handle.
' Parameter:
' - wszFileName = [in] Path of the image to load and convert.
' - dimPercent = Percent of dimming (1-99)
' - bGrayScale = TRUE or FALSE. Convert to gray scale.
' Return Value:
'   If the function succeeds, the return value is the handle of the created icon.
'   If the function fails, the return value is NULL.
' ========================================================================================
FUNCTION AfxGdiplusCreateHICONFromFile (BYREF wszFileName AS WSTRING, _
   BYVAL dimPercent AS LONG = 0, BYVAL bGrayScale AS LONG = FALSE) AS HICON

   DIM hStatus AS GpStatus, token AS ULONG_PTR
   DIM pImage AS GpImage PTR, hIcon AS HICON
   DIM ImageWidth AS LONG, ImageHeight AS LONG, x AS LONG, y AS LONG
   DIM pixColor AS GDIP_BGRA, iColor AS LONG, rFactor AS SINGLE

   ' // Initialize Gdiplus
   token = AfxGdiplusInit
   ' // Load the image from file
   GdipLoadImageFromFile(wszFileName, @pImage)
   IF pImage = NULL THEN EXIT FUNCTION
   ' // Get the image width and height
   GdipGetImageWidth(pImage, @ImageWidth)
   GdipGetImageHeight(pImage, @ImageHeight)
   ' // Dim or/and gray the image
   IF dimPercent > 0 AND dimPercent < 100 THEN rFactor = dimPercent / 100
   IF rFactor <> 0 OR bGrayScale <> 0 THEN
      FOR y = 0 TO ImageWidth - 1
         FOR x = 0 TO ImageHeight - 1
            ' // Get the pixel color
            GdipBitmapGetPixel(CAST(GpBitmap PTR, pImage), x, y, @pixColor.color)
            IF dimPercent > 0 THEN
               pixColor.red   = (255 - pixColor.red) * rFactor + pixColor.red
               pixColor.green = (255 - pixColor.green) * rFactor + pixColor.green
               pixColor.blue  = (255 - pixColor.blue) * rFactor + pixColor.blue
            END IF
            IF bGrayScale THEN
               ' Note: The sum of the percentages for the three colors should add tp up 1
               iColor = 0.299 * pixColor.red + 0.587 * pixColor.green + 0.114 * pixColor.blue
               pixColor.Color = GDIP_BGRA (iColor, iColor, iColor, pixColor.alpha)
            ELSE
               pixColor.color = GDIP_ARGB(pixColor.alpha, pixColor.red, pixColor.green, pixColor.Blue)
            END IF
            ' // Set the modified pixel color
            GdipBitmapSetPixel(CAST(GpBitmap PTR, pImage), x, y, pixColor.color)
         NEXT
      NEXT
   END IF
   ' // Crete icon from image
   hStatus = GdipCreateHICONFromBitmap(CAST(GpBitmap PTR, pImage), @hIcon)
   ' // Free the image
   IF pImage THEN GdipDisposeImage pImage
   ' // Shut down Gdiplus
   GdiplusShutdown token
   ' // Return the handle of the icon
   FUNCTION = hIcon

END FUNCTION
' ========================================================================================


José Roca

What about 256-bit icons? We are ready for upcoming very high resolution monitors :)

José Roca

A good thing is that we don't need to have icons of different sizes for different DPI settings. We can have only one and use something like...


   DIM cx, cy AS LONG
   cx = 16 : cy = 16
   IF pWindow.IsProcessDPIAware THEN
      IF pWindow.DPI => 120 AND pWindow.DPI < 144 THEN
         cx = 20 : cy = 20
      ELSEIF pWindow.DPI => 144 AND pWindow.DPI < 192 THEN
         cx = 24 : cy = 24
      ELSEIF pWindow.DPI => 192 THEN
         cx = 32 : cy = 32
      END IF
   END IF

   hImageList = ImageList_Create(cx, cx, ILC_COLOR32 OR ILC_MASK, 4, 0)
   IF hImageList THEN
      ImageList_ReplaceIcon(hImageList, -1, AfxGdiplusCreateHICONFromFile(ExePath & "\arrow_left_64.png"))
      ImageList_ReplaceIcon(hImageList, -1, AfxGdiplusCreateHICONFromFile(ExePath & "\arrow_right_64.png", 60, -1))
      ImageList_ReplaceIcon(hImageList, -1, AfxGdiplusCreateHICONFromFile(ExePath & "\home_64.png"))
      ImageList_ReplaceIcon(hImageList, -1, AfxGdiplusCreateHICONFromFile(ExePath & "\save_64.png", 60, -1))
   END IF
[code]

José Roca

This new function can be used to create an icon from an image stored in a buffer. Can be used with gray images because it does not darken the image as GdipLoadImageFromFile does.


' ========================================================================================
' Converts an image stored in a buffer into an icon and returns the icon handle.
' Parameters:
' - pBuffer = [in] Pointer to the buffer
' - bufferSize = Size of the buffer
' Return Value:
'   If the function succeeds, the return value is the handle of the created icon.
'   If the function fails, the return value is NULL.
' Usage example:
'   DIM wszFileName AS WSTRING * MAX_PATH
'   wszFileName = ExePath & "\arrow_left_256.png"
'   DIM bufferSize AS SIZE_T_
'   DIM nFile AS LONG
'   nFile = FREEFILE
'   OPEN wszFileName FOR BINARY AS nFile
'   IF ERR THEN EXIT FUNCTION
'   bufferSize = LOF(nFile)
'   DIM pBuffer AS UBYTE PTR
'   pBuffer = CAllocate(1, bufferSize)
'   GET #nFile, , *pBuffer, bufferSize
'   CLOSE nFile
'   IF pBuffer THEN
'      ImageList_ReplaceIcon(hImageList, -1, AfxGdiplusCreateHICONFromRawData(pBuffer, ImageSize))
'      DeAllocate(pBuffer)
'   END IF
' ========================================================================================
FUNCTION AfxGdiplusCreateHICONFromBuffer (BYVAL pBuffer AS ANY PTR, BYVAL bufferSize AS SIZE_T_) AS HICON

   DIM hStatus AS GpStatus, token AS ULONG_PTR
   DIM pImage AS GpImage PTR, hIcon AS HICON
   DIM ImageWidth AS LONG, ImageHeight AS LONG, x AS LONG, y AS LONG
   DIM pixColor AS GDIP_BGRA, iColor AS LONG, rFactor AS SINGLE
   DIM pImageStream AS IStream PTR, hGlobal AS HGLOBAL, pGlobalBuffer AS LPVOID

   ' // Initialize Gdiplus
   token = AfxGdiplusInit
   IF token = NULL THEN EXIT FUNCTION
   ' // Allocate memory to hold the image
   hGlobal = GlobalAlloc(GMEM_MOVEABLE, bufferSize)
   IF hGlobal THEN
      ' // Lock the memory
      pGlobalBuffer = GlobalLock(hGlobal)
      IF pGlobalBuffer THEN
         ' // Copy the image from the binary string file to global memory
         CopyMemory(pGlobalBuffer, pBuffer, bufferSize)
         ' // Create an stream in global memory
         IF CreateStreamOnHGlobal(hGlobal, FALSE, @pImageStream) = S_OK THEN
            IF pImageStream THEN
               ' // Create a bitmap from the data contained in the stream
               hStatus = GdipCreateBitmapFromStream(pImageStream, @pImage)
               ' // Crete icon from image
               hStatus = GdipCreateHICONFromBitmap(CAST(GpBitmap PTR, pImage), @hIcon)
               ' // Free the image
               IF pImage THEN GdipDisposeImage pImage
               pImageStream->lpVtbl->Release(pImageStream)
            END IF
         END IF
         ' // Unlock the memory
         GlobalUnlock pGlobalBuffer
      END IF
      ' // Free the memory
      GlobalFree hGlobal
   END IF

   ' // Shut down Gdiplus
   GdiplusShutdown token
   ' // Return the handle of the icon
   FUNCTION = hIcon

END FUNCTION
' ========================================================================================


José Roca

As the above function has not problem with gray images, I will rename the first one as AfxGdiplusCreateHICONFromFileEx and implemented this new one.


' ========================================================================================
' Loads an image from a file, converts it to an icon and returns the icon handle.
' Parameters:
' - wszFileName = [in] Path of the image to load and convert.
' Return Value:
'   If the function succeeds, the return value is the handle of the created icon.
'   If the function fails, the return value is NULL.
' ========================================================================================
FUNCTION AfxGdiplusCreateHICONFromFile (BYREF wszFileName AS WSTRING) AS HICON

   DIM fd AS WIN32_FIND_DATAW
   DIM hFind AS HANDLE

   ' // Check for the existence of the file
   IF LEN(wszFileName) = 0 THEN EXIT FUNCTION
   hFind = FindFirstFileW(@wszFileName, @fd)
   IF hFind = INVALID_HANDLE_VALUE THEN EXIT FUNCTION
   FindClose hFind
   ' // Make sure that is not a directory or a temporary file
   IF (fd.dwFileAttributes AND FILE_ATTRIBUTE_DIRECTORY) = FILE_ATTRIBUTE_DIRECTORY OR _
      (fd.dwFileAttributes AND FILE_ATTRIBUTE_TEMPORARY) = FILE_ATTRIBUTE_TEMPORARY THEN
      EXIT FUNCTION
   END IF

   ' // Open the file and store its contents into a buffer
   DIM nFile AS LONG, bufferSize AS SIZE_T_
   nFile = FREEFILE
   OPEN wszFileName FOR BINARY AS nFile
   IF ERR THEN EXIT FUNCTION
   bufferSize = LOF(nFile)
   DIM pBuffer AS UBYTE PTR
   pBuffer = CAllocate(1, bufferSize)
   GET #nFile, , *pBuffer, bufferSize
   CLOSE nFile
   IF pBuffer THEN
      FUNCTION = AfxGdiplusCreateHICONFromBuffer(pBuffer, bufferSize)
      DeAllocate(pBuffer)
   END IF

END FUNCTION
' ========================================================================================


James Fuller

Jose,
I am doing some experimenting and noticed this:
Quote
' ########################################################################################
' File: CWindow.inc
' Version: 1.0
' Release candidate 2
' Contents: A wrapper class to create a SDK main window and add controls to it.
' Operating system: Microsoft Windows
' Compiler: FreeBasic 32/64-bit, Unicode.
' Freeware. Use at your own risk.
' Copyright (c) 2016 Jose Roca. All Rights Reserved.
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
' EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
' MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' #######################################################################################
Did you inadvertently upload rc2 or not update the header info.

James

José Roca

#12
Forgot to update the number. Sorry.

BTW is "dimming" the appropriate word for what I'm doing, i.e. reducing the saturation of color?

José Roca

I'm going to shorten a little the name of the functions, e.g. AfxGdipIconFromFile instead of AfxGdiplusCreateHICONFromFile.

Barry Gordon

Hi,

> BTW is "dimming" the appropirate word for what I'm doing, i.e. reducing the saturation of color?

So why not 'desaturate' then?

Cheers
Barry