• Welcome to PlanetSquires Forums.
 

CXpButton

Started by Paul Squires, June 15, 2018, 11:14:53 PM

Previous topic - Next topic

Paul Squires

Hi Jose,

I'd like to ask your opinion... Over the years I've had many people wanting to be able to set the foreground and background colors for the standard pushbutton that FireFly used as a control. Given that it is a standard Windows control that didn't allow user specified backcolors, I usually pointed them in the direction of other user created custom controls. For the new designer, I am thinking of just using your CXpButton as the one and only push button for the designer. What do you think? It would make things so much easier and, more importantly, less confusing and more user friendly for novices. If this path is chosen, the only thing that I think would be needed is to modify the control so that the user can specify fore/back colors (when themes are disabled for the control).

Sound good???
Paul Squires
PlanetSquires Software
WinFBE Editor and Visual Designer

José Roca

I wrote it many years ago (XP times) as an exercise of how to write a custom control using visual styles. Today it is not longer needed since you can use image lists with buttons, and I translated it to FreeBasic mainly to practice to write custom controls using a class. I will leave it as it is (you don't need to include it in the designer) and write another with a different name (XP is already vintage) based on it to save coding, with all the changes that you like. I personally dislike coloured buttons, but it is not my business if other people like them.

Paul Squires

Awesome, thanks Jose! From using the control last night it seemed like the only things missing that really jumped out at me were the fore/back colors and the ability to use any type of image (not just icons and bitmaps) (png, jpg, etc) and to be able to set their transparency backcolor easily. Maybe you could integrate your AfxGdipIconFromRes function, etc.
Paul Squires
PlanetSquires Software
WinFBE Editor and Visual Designer

José Roca

PNGs and othe formats can already be used, e.g. instead of using LoadIcon you can use


pXpButton1.SetIcon AfxGdipImageFromFile(ExePath & "/Shutdown_48.png"), XPBI_NORMAL


or other GdiPlus wrappers.



José Roca

Methods to add support for other image formats. The optional parameter dimPercent canbe used to dim the imape and in combination with bGrayScale to get disabled gray images from a normal image, e.g.:


pXpButton1.SetImageFromFile(ExePath & "/Shutdown_48.png", XPBI_NORMAL)
' Reuse the normal image to get a grayed image
pXpButton1.SetImageFromFile(ExePath & "/Shutdown_48.png", XPBI_DISABLED, TRUE, 60, TRUE)



' ========================================================================================
' Loads an image from file and sets it as the image of the button.
' - wszPath = Full path of the bitmap file.
' - ImageState =
'      XPBI_NORMAL = 1
'      XPBI_HOT = 2
'      XPBI_DISABLED = 3
' - fRedraw       = TRUE or FALSE (redraws the button to reflect the changes)
' - dimPercent    = Percent of dimming (1-99)
' - bGrayScale    = TRUE or FALSE. Convert to gray scale.
' ========================================================================================
' ========================================================================================
'PRIVATE FUNCTION AfxGdipIconFromFile (BYREF wszFileName AS WSTRING, _
'   BYVAL dimPercent AS LONG = 0, BYVAL bGrayScale AS LONG = FALSE) AS HANDLE
'   FUNCTION = AfxGdipImageFromFile(wszFileName, dimPercent, bGrayScale, IMAGE_ICON, 0)
'END FUNCTION

' ========================================================================================
' Loads an image from a resource file and sets it as the image of the button.
' - hInstance     = [in] A handle to the module whose portable executable file or an accompanying
'                   MUI file contains the resource. If this parameter is NULL, the function searches
'                   the module used to create the current process.
' - wszImageName  = [in] Name of the image in the resource file (.RES). If the image resource uses
'                   an integral identifier, wszImage should begin with a number symbol (#)
'                   followed by the identifier in an ASCII format, e.g., "#998". Otherwise,
'                   use the text identifier name for the image. Only images embedded as raw data
'                   (type RCDATA) are valid. These must be icons in format .png, .jpg, .gif, .tiff.
' - ImageState =
'      XPBI_NORMAL = 1
'      XPBI_HOT = 2
'      XPBI_DISABLED = 3
' - fRedraw       = TRUE or FALSE (redraws the button to reflect the changes)
' - dimPercent    = Percent of dimming (1-99)
' - bGrayScale    = TRUE or FALSE. Convert to gray scale.
' ========================================================================================
PRIVATE SUB CXpButton.SetImageFromRes (BYVAL hInstance AS HINSTANCE, BYREF wszImageName AS WSTRING, BYVAL ImageState AS LONG = XPBI_NORMAL, BYVAL fRedraw AS LONG = FALSE, _
BYVAL dimPercent AS LONG = 0, BYVAL bGrayScale AS LONG = FALSE)
   DIM hIcon AS HICON = AfxGdipIconFromRes(hInstance, wszImageName, dimPercent, bGrayScale)
   this.SetIcon(hIcon, ImageState, fRedraw)
END SUB
' ========================================================================================


José Roca

Tip:

To solve the problem of different icon sizes depending of the DPI setting, you could add fields in the property list for the wanted width and height of the icon and, in the generated code, use:


pXpButton.SetImageSize pWindow.ScaleX(width), pWindow.ScaleY(height)


This way, the icon will we draw with the same relative size.

Paul Squires

Quote from: José Roca on June 16, 2018, 12:44:11 PM
Tip:

To solve the problem of different icon sizes depending of the DPI setting, you could add fields in the property list for the wanted width and height of the icon and, in the generated code, use:


pXpButton.SetImageSize pWindow.ScaleX(width), pWindow.ScaleY(height)


This way, the icon will we draw with the same relative size.

Sounds good. I will have those settings in the designer for sure. How about icon transparency... for example, when you use your gdi functions to load a png from the resource file and convert them to icon format would we lose and background transparency? Could, say, the topmost left pixel become the color used for transparency? It would be very versatile if you can load images of any type from the resource file and display in the button such that it blends nicely with whatever color the button is currently using as a background.
Paul Squires
PlanetSquires Software
WinFBE Editor and Visual Designer

José Roca

#7
PNG icons support transparency. Therefore you only need to use SetImageFromFile or SetImageFromRes. Nothing else. Gone are the times of bitmaps with a pink background :)

They also support the alpha channel.

José Roca

What I can't get is to change the background color permanently. Since the control is not ownerdraw, it does not receive the WM_CTLCOLORBTN message and Windows reverts to the system colors as soon at it processes DefWindowProcW. You may need a different control.

I will add the new SetImage functions to the original CXpButton as an improvement.

Paul Squires

Would forcing a setting where the control does not check for m_bIsThemed (basically set it be false) and then set properties to replace calls like GetSysColorBrush(COLOR_BTNFACE) to our own defined colors. Basically, ensure that the control draws using the GDI sections of the CXpButton.UxDrawPushButton function rather than the theme portions? Would that work?
Paul Squires
PlanetSquires Software
WinFBE Editor and Visual Designer

José Roca

I have tried that and the color changes when you click the button, but it reverts to system colors when you release it.

Paul Squires

Quote from: José Roca on June 16, 2018, 04:53:32 PM
I have tried that and the color changes when you click the button, but it reverts to system colors when you release it.
Ah, yes, I see what you mean. I just tried it as well. Looks like it is the DrawFrameControl api that is the culprit. It uses its own colors.

            uState = DFCS_BUTTONPUSH
            IF iStateId = PBS_HOT THEN uState = uState OR DFCS_HOT
            IF (lStyle AND BS_FLAT) = BS_FLAT THEN uState = uState OR DFCS_FLAT
            .DrawFrameControl hDc, @rcContent, DFC_BUTTON, uState


Like you said, looks like this will entail having to go full ownerdrawn which is too bad.
Paul Squires
PlanetSquires Software
WinFBE Editor and Visual Designer

José Roca

But changing the order seems to work. I will try and post a modified file.

José Roca

It works. Updated file and an example.

José Roca

A further modification to get it working when the button is toggled.


   ' // Draws the button
   IF m_bIsThemed THEN
      ' // Increase 1 pixel to include the edge
      .InflateRect @rc, 1, 1
      ' // Draws the theme-specified border and fills for the "iPartId" and "iStateId".
      .DrawThemeBackground(hTheme, hDc, BP_PUSHBUTTON, iStateId, @rc, NULL)
      ' // Gets the size of the content for the theme-defined background
      .GetThemeBackgroundContentRect(hTheme, hDc, BP_PUSHBUTTON, iStateId, @rc, @rcContent)
   ELSE
      ' // Uses GDI to draw the button
      rcContent = rc
      IF bIsFocused THEN
         IF m_bIsToggle = FALSE OR bIsPressed = FALSE THEN
            .FrameRect hDc, @rcContent, GetSysColorBrush(COLOR_WINDOWTEXT)
         END IF
         .InflateRect @rcContent, -1, -1
      END IF
      IF m_bIsToggle THEN
         IF iStateId = PBS_PRESSED THEN
            .DrawEdge hDc, @rcContent, EDGE_SUNKEN, BF_RECT OR BF_MIDDLE OR BF_SOFT
         ELSE
            IF (lStyle AND BS_FLAT) = BS_FLAT THEN
               .DrawEdge hDc, @rcContent, EDGE_RAISED, BF_RECT OR BF_MIDDLE OR BF_SOFT OR BF_FLAT
            ELSE
               .DrawEdge hDc, @rcContent, EDGE_RAISED, BF_RECT OR BF_MIDDLE OR BF_SOFT
            END IF
         END IF
      ELSE
         IF bIsPressed THEN
            .FrameRect hDc, @rcContent, GetSysColorBrush(COLOR_BTNSHADOW)
         ELSE
            uState = DFCS_BUTTONPUSH
            IF iStateId = PBS_HOT THEN uState = uState OR DFCS_HOT
            IF (lStyle AND BS_FLAT) = BS_FLAT THEN uState = uState OR DFCS_FLAT
            .DrawFrameControl hDc, @rcContent, DFC_BUTTON, uState
         END IF
      END IF
      IF m_hBkgBrush = NULL THEN
         .FillRect hDc, @rcContent, GetSysColorBrush(COLOR_BTNFACE)
      ELSE
         .FillRect hDc, @rcContent, m_hBkgBrush
      END IF
   END IF


What I have done is to move FillRect to the bottom.