Paradox Community
Search:

 Welcome |  What is Paradox |  Paradox Folk |  Paradox Solutions |
 Interactive Paradox |  Paradox Programming |  Internet/Intranet Development |
 Support Options |  Classified Ads |  Wish List |  Submissions 


Paradox Programming Articles  |  Beyond Help Articles  |  Tips & Tricks Articles  


Using Color Gradients
© 2004 Rick Kelly
www.crooit.com

Preface

The example OPAL (Paradox® 9) presented in this article is available as a download here. After downloading into the folder of your choice, make that folder :WORK: and run the included script for a demonstration.


Introduction

Ever wonder how to achieve the effect of color gradients with your Paradox® forms? Having an UIObject blend two colors together is an interesting effect and adds a bit of catchy eye appeal to your applications. Before I go on, there are some limitations to be aware of. Normally, Paradox® takes care of all those tedious details when it comes to rendering or painting parent and child windows and cooperates with Windows when messages are received that indicate regenerating or repainting a window is required. To fully integrate any custom painting requires that a window be subclassed. Subclassing is just a fancy way to describe a small piece of code that is inserted into the windows messaging flow and examines each message, altering or inserting messages as required. Subclassing a Paradox® form is beyond the scope of this article (perhaps a later followup?) and the methods presented are best used in modal windows and dialogs. One of best uses I’ve found is the initial application logon and an example of that usage is provided.

Color gradients were first introduced with Windows 98 and a common use was in the title bar background of windows when one color would slowly morph to another left to right.

The basic steps for applying a color gradient are:
  1. Determine the portion of form to be painted
  2. Retrieve device context of form for painting operations
  3. Get RGB values for our gradient colors
  4. Loop through the area to be painted one pixel width at a time
    1. Calculate the next area to be painted
    2. Calculate the gradient color to be applied
    3. Paint the area with the current gradient color
Before using any of our color gradient methods, we’ll also need to check if the system supports gradients before retrieving the current gradient colors to be used.


Check for Windows gradient support

Checking if the current version of Windows supports gradients is necessary if we want to automatically use the colors from the active color schema and can be omitted if custom colors are to be used. Gradient support can be directly interrogated using a win32 api call to SystemParametersInfo.
Const
  cnTrue                   = 1
;
; System Parameters Info
;
  cnSPIGetGradientCaptions = 4104
endConst

Uses "user32"
  SystemParametersInfo(uAction  cLong,
                       uParam   cLong,
                       lpvParam cPtr,
                       fuWinIni cLong) cLong [stdcall "SystemParametersInfoA"]
endUses

method GradientColorsAvailable() Logical
var
  liReturn  LongInt
  liParam   LongInt
endVar
  liParam = 0
  liReturn = SystemParametersInfo(
               cnSPIGetGradientCaptions,
               0,
               liParam,
               0)
  return (liReturn = cnTrue and liParam = cnTrue)
endMethod

Determine what gradient colors to use

The two gradient colors we’ll use are the color for the active window caption bar and the gradient color as defined in the current Windows color schema. Each color can be retrieved using the win32 api method GetSysColor.
Const
  cnColorActiveCaption            = 2
  cnColorGradientActiveCaption    = 27
endConst

Uses "user32"
  GetSysColor(lpIndex cLong) cLong [stdcall]
endUses

method GetWindowsColor(liWindowsColorIndex LongInt) LongInt
;
; Retrieves the current color of the specified display element.
; Display elements are the parts of a window and the display
; that appear on the system display screen.
;
; If an invalid index is used, the return value is zero
;
  return GetSysColor(liWindowsColorIndex)
endMethod

var
  liToColor    LongInt
  liFromColor  LongInt
endVar
  liToColor = GetWindowsColor(cnColorActiveCaption)
  switch
    case GradientColorsAvailable() = True :
      liFromColor = GetWindowsColor(cnColorGradientActiveCaption)
    otherwise :
;
; Use solid color (No gradient)
;
      liFromColor = liToColor
  endSwitch

Painting the gradient

Many different types of gradient shapes may be created. It’s all about screen coordinates and the appropriate mathematical calculations and we will present the two most common which are top-to-bottom (vertical) and left-to-right (horizontal). Basically, we will calculate a starting/ending pixel coordinate pair, adjust the color to be used and draw a one pixel width line all with native win32 api methods.
Uses "user32"
  FillRect(hdc    cLong,
           lpRect cLong,
           hBrush cLong) cLong [stdcall]
  SetRect(lpRect cLong,
          X1     cLong,
          Y1     cLong,
          X2     cLong,
          Y2     cLong) cLong [stdcall]
  GetDC(hWnd cLong) cLong [stdcall]
endUses
Uses "kernel32"
  GlobalAlloc(wFlags  cLong,
              dwBytes cLong) cLong [stdcall]
  GlobalFree(hMem cLong) cLong [stdcall]
endUses
Uses "gdi32"
  CreateSolidBrush(crColor cLong) cLong [stdcall]
  SelectObject(hdc     cLong,
               hObject cLong) cLong [stdcall]
  DeleteObject(hObject cLong) cLong [stdcall]
endUses

Proc cmGlobalAlloc(liSize LongInt) LongInt
  return GlobalAlloc(fromHex("0x40"),liSize)
endProc
Proc cmGlobalFree(var liPointer LongInt) LongInt
  return GlobalFree(liPointer)
endProc
Proc cmPaintGradientHorizontal(var siRed1          SmallInt,
                               var siGreen1        SmallInt,
                               var siBlue1         SmallInt,
                               var siRed2          SmallInt,
                               var siGreen2        SmallInt,
                               var siBlue2         SmallInt,
                               var liLeft          LongInt,
                               var liRight         LongInt,
                               var liTop           LongInt,
                               var liBottom        LongInt,
                               var liColorRect     LongInt,
                               var liDeviceContext LongInt)
var
  liBrush         LongInt
  liPreviousBrush LongInt
  liY             LongInt
  siCurrentRed    SmallInt
  siCurrentGreen  SmallInt
  siCurrentBlue   SmallInt
  nuAny           Number
endVar
  for liY from liLeft to liRight
    nuAny = cmDivide(liRight - liLeft,
                     liY - liLeft)
    siCurrentRed = smallInt(siRed1 +
                            cmDivide(siRed2 - siRed1,
                            nuAny))
    siCurrentGreen = smallInt(siGreen1 +
                              cmDivide(siGreen2 - siGreen1,
                              nuAny))
    siCurrentBlue = smallInt(siBlue1 +
                             cmDivide(siBlue2 - siBlue1,
                             nuAny))
    SetRect(liColorRect,
            liY,
            liTop,
            liRight,
            liBottom + 1)
    liBrush = CreateSolidBrush(rgb(siCurrentRed,
                                   siCurrentGreen,
                                   siCurrentBlue))
    liPreviousBrush = SelectObject(liDeviceContext,liBrush)
    FillRect(liDeviceContext,
             liColorRect,
             liBrush)
    SelectObject(liDeviceContext,
                 liPreviousBrush)
    DeleteObject(liBrush)
  endFor
endProc
Proc cmPaintGradientVertical(var siRed1          SmallInt,
                             var siGreen1        SmallInt,
                             var siBlue1         SmallInt,
                             var siRed2          SmallInt,
                             var siGreen2        SmallInt,
                             var siBlue2         SmallInt,
                             var liLeft          LongInt,
                             var liRight         LongInt,
                             var liTop           LongInt,
                             var liBottom        LongInt,
                             var liColorRect     LongInt,
                             var liDeviceContext LongInt)
var
  liBrush         LongInt
  liPreviousBrush LongInt
  liY             LongInt
  siCurrentRed    SmallInt
  siCurrentGreen  SmallInt
  siCurrentBlue   SmallInt
  nuAny           Number
endVar
  for liY from liTop to liBottom
    nuAny = cmDivide(liBottom - liTop,
                     liY - liTop)
    siCurrentRed = smallInt(siRed1 +
                            cmDivide(siRed2 - siRed1,
                            nuAny))
    siCurrentGreen = smallInt(siGreen1 +
                              cmDivide(siGreen2 - siGreen1,
                              nuAny))
    siCurrentBlue = smallInt(siBlue1 +
                             cmDivide(siBlue2 - siBlue1,
                             nuAny))
    SetRect(liColorRect,
            liLeft,
            liY,
            liRight,
            liY + 1)
    liBrush = CreateSolidBrush(rgb(siCurrentRed,
                               siCurrentGreen,
                               siCurrentBlue))
    liPreviousBrush = SelectObject(liDeviceContext,liBrush)
    FillRect(liDeviceContext,
             liColorRect,
             liBrush)
    SelectObject(liDeviceContext,
                 liPreviousBrush)
    DeleteObject(liBrush)
  endFor
endProc
Proc cmDivide(nuX1 Number,
              nuX2 Number) Number
  return iif(nuX2 = 0,0,nuX1 / nuX2)
endProc
Before we can use our low-level gradient routines, some setup work is necessary.

Identify portion of screen to be painted

We will support any UIObject as a target object to be painted, extracting the underlying screen position in twips converted to pixels and identified as a Windows rectangle.

Assume uiAny is our UIObject target object.
var
  liColorRect LongInt
  liLeft      LongInt
  liRight     LongInt
  liTop       LongInt
  liBottom    LongInt
  ptAny       Point
endVar
;
; Allocate Windows rectangle structure
;
  liColorRect = cmGlobalAlloc(16)
;
; Identify portion of screen to be painted
;
  ptAny = uiAny.Position
  ptAny = twipsToPixels(ptAny)
  liLeft = ptAny.x()
  liTop = ptAny.y()
  ptAny = uiAny.Size
  ptAny = twipsToPixels(ptAny)
  liRight = ptAny.x() + liLeft
  liBottom = ptAny.y() + liTop
Get Windows Device Context for GDI paint operations

Windows GDI painting operations require a device context handle. The client portion of our Paradox form is used.

Assume that liWindowClientHandle is a LongInt Type returned from OPAL method windowClientHandle().
var
  liDeviceContext  LongInt
endVar
;
; Retrieve device context for gdi operations
;
  liDeviceContext = GetDC(liWindowClientHandle)
Get RGB values for our gradient colors

Assume liFromColor and liToColor are our gradient colors and defined as a LongInt Type.
var
  siRed1   SmallInt
  siGreen1 SmallInt
  siBlue1  SmallInt
  siRed2   SmallInt
  siGreen2 SmallInt
  siBlue2  SmallInt
endVar
;
; Get RGB values for our gradient colors
;
  liFromColor.getRGB(siRed1,
                     siGreen1,
                     siBlue1)
  liToColor.getRGB(siRed2,
                   siGreen2,
                   siBlue2)
Putting everything together in one method and adding an option for requesting a vertical or horizontal gradient looks like:
method PaintGradient(liFromColor LongInt,
                     liToColor LongInt,
                     liWindowClientHandle LongInt,
                     var uiAny UIObject,
                     loPaintTopToBottom Logical)
;
; Paint a color gradient on UIObject
;
var
  liColorRect     LongInt
  liLeft          LongInt
  liRight         LongInt
  liTop           LongInt
  liBottom        LongInt
  liDeviceContext LongInt
  siRed1          SmallInt
  siGreen1        SmallInt
  siBlue1         SmallInt
  siRed2          SmallInt
  siGreen2        SmallInt
  siBlue2         SmallInt
  ptAny           Point
endVar
  liColorRect = cmGlobalAlloc(16)
;
; Identify portion of screen to be painted
;
  ptAny = uiAny.Position
  ptAny = twipsToPixels(ptAny)
  liLeft = ptAny.x()
  liTop = ptAny.y()
  ptAny = uiAny.Size
  ptAny = twipsToPixels(ptAny)
  liRight = ptAny.x() + liLeft
  liBottom = ptAny.y() + liTop
;
; Retrieve device context for gdi operations
;
  liDeviceContext = GetDC(liWindowClientHandle)
;
; Get RGB values for our gradient colors
;
  liFromColor.getRGB(siRed1,
                     siGreen1,
                     siBlue1)
  liToColor.getRGB(siRed2,
                   siGreen2,
                   siBlue2)
;
; Paint gradient
;
  delayScreenUpdates(Yes)
  switch
    case loPaintTopToBottom = True :
      cmPaintGradientVertical(siRed1,
                              siGreen1,
                              siBlue1,
                              siRed2,
                              siGreen2,
                              siBlue2,
                              liLeft,
                              liRight,
                              liTop,
                              liBottom,
                              liColorRect,
                              liDeviceContext)
    otherwise :
      cmPaintGradientHorizontal(siRed1,
                                siGreen1,
                                siBlue1,
                                siRed2,
                                siGreen2,
                                siBlue2,
                                liLeft,
                                liRight,
                                liTop,
                                liBottom,
                                liColorRect,
                                liDeviceContext)
  endSwitch
  delayScreenUpdates(No)
  cmGlobalFree(liColorRect)
endMethod
On our example logon form (a modal dialog), we have two boxes identified - RightDivider and LeftDivider which as positioned next to each other to form a separator band across the width of the form. We will take our gradient colors and paint a horizontal gradient across LeftDivider, reverse the colors and paint a horizontal gradient across RightDivider. The end result will be a band across the form that begins and ends with one color with the middle portion being another color all progressing smoothly from left to right.

logon


Conclusion

We now have methods that provide basic painting of gradients. Use the parts that work for you and spice up your forms remembering the restrictions noted in the beginning. If you add or improve to what is shown here, share it with the rest of us.

From my Paradox toolbox to yours!

Rick Kelly


Discussion of this article


 Feedback |  Paradox Day |  Who Uses Paradox |  I Use Paradox |  Downloads 


 The information provided on this Web site is not in any way sponsored or endorsed by Corel Corporation.
 Paradox is a registered trademark of Corel Corporation.


 Modified: 03 Jul 2004
 Terms of Use / Legal Disclaimer


 Copyright © 2001- 2004 Paradox Community. All rights reserved. 
 Company and product names are trademarks or registered trademarks of their respective companies. 
 Authors hold the copyrights to their own works. Please contact the author of any article for details.