Skip to content

How To Create a Custom Button Control in Xojo – Part 2

In the first part of this tutorial, we built a basic custom button control using the DesktopCanvas class, giving it a custom appearance and handling the basic mouse events to provide visual feedback and raise a Pressed event when clicked.

In this second part, we will enhance our CanvasButton by adding more customization with new properties to:

  • set the button’s colors for different states (default, hovered, pressed)
  • adjust the corner radius of the button
  • implement a disabled state

Let’s get started!


Adding New Properties for Customization

We need to add several properties to our CanvasButton class to store the custom colors for different states, the border color, the text color, the corner radius, and the enabled state.

  1. In the Project Navigator, select your CanvasButton class.
  2. Go to Insert > Property (or right-click the class and select Add to “CanvasButton” > Property).
  3. Add the following properties with the specified names, types, and default values:
    • Name: BackgroundColor Type: Color Default Value: &c5e2d8b
    • Name: HoverColor Type: Color Default Value: &c729fcf
    • Name: HighlightColor Type: Color Default Value: &c628eff
    • Name: BorderColor Type: Color Default Value: &c525252
    • Name: TextColor Type: Color Default Value: &ceeeeee
    • Name: CornerRadius Type: Integer Default Value: 4

These properties will hold the values that determine the button’s appearance and behavior.

Updating Event Handlers for the Enabled State

We need to modify the existing mouse event handlers (MouseDownMouseEnterMouseExitMouseUp) to check if the button is Enabled before processing any mouse actions.

Select each of the following event handlers in the CanvasButton class and update the code as shown:

MouseDown(x As Integer, y As Integer) As Boolean

#Pragma unused x
#Pragma unused y

// Only process if the button is enabled.
If Enabled Then
  // Set internal state to indicate the button is being pressed.
  IsPressed = True
  // Refresh the control to show the pressed state visually.
  Refresh(False)
  // Return True to indicate that this event was handled.
  Return True
Else
  // If disabled, do not handle the event.
  Return False
End If

MouseEnter()

// Only process if the button is enabled.
If Enabled Then
  // Set internal state to indicate the mouse is hovering over the button.
  IsHovered = True
  // Refresh the control to show the hover state visually.
  Refresh(False)
End If

MouseExit()

// Only process if the button is enabled.
If Enabled Then
  // Set internal state to indicate the mouse is no longer hovering.
  IsHovered = False
  // Reset pressed state if mouse leaves while pressed (prevents accidental clicks).
  IsPressed = False
  // Refresh the control to revert from the hover state.
  Refresh(False)
End If

MouseUp(x As Integer, y As Integer) As Boolean

#Pragma unused x
#Pragma unused y

// Only process if the button is enabled.
If Enabled Then
  // Check if the button was pressed down AND the mouse is still hovering over it.
  If IsPressed And IsHovered Then
    // If true, the button was successfully clicked. Raise the custom Pressed event.
    RaiseEvent Pressed
  End If
  // Reset the pressed state regardless of whether the click was successful.
  IsPressed = False
  // Refresh the control to revert from the pressed state.
  Refresh(False)
End If

In these updated event handlers, we added an If Enabled Then check at the beginning. If the button is not enabled, the code inside the If block is skipped, preventing the button from reacting to mouse interactions. We also added #Pragma unused to the parameters x and y in MouseDown and MouseUp as they are not used in the code, which helps avoid compiler warnings. In MouseExit, we added IsPressed = False to ensure the pressed state is reset if the mouse leaves the button while the mouse button is held down.

Updating the Paint Event for Enhanced Appearance

The most significant changes will be in the Paint event where we will use the new properties to draw the button based on its current state (enabled/disabled, hovered, and pressed).

Select the Paint(g As Graphics, areas() As Rect) event handler and replace its content with the following code:

#Pragma unused areas

// Use the custom CornerRadius property.
Var currentCornerRadius As Integer = CornerRadius

// Declare variables for the colors used in drawing.
Var currentBgColor As Color
Var currentBorderColor As Color
Var currentTextColor As Color

// Determine colors based on the button's current state (enabled, pressed, hovered).
If Enabled Then
  If IsPressed Then
    // Use highlight color if pressed.
    currentBgColor = HighlightColor
    currentBorderColor = BorderColor
    currentTextColor = TextColor
  ElseIf IsHovered Then // Check for hover only if not pressed
    // Use hover color if hovered.
    currentBgColor = HoverColor
    currentBorderColor = BorderColor
    currentTextColor = TextColor
  Else
    // Use the custom background color for the default state.
    currentBgColor = BackgroundColor
    currentBorderColor = BorderColor
    currentTextColor = TextColor
  End If
Else
  // Use appropriate system or standard gray colors for the disabled state.
  currentBgColor = Color.LightGray
  currentBorderColor = Color.Gray
  currentTextColor = Color.DisabledTextColor // Use system disabled text color
End If

// Set the drawing color and draw the background shape with rounded corners.
g.DrawingColor = currentBgColor
g.FillRoundRectangle(0, 0, g.Width, g.Height, currentCornerRadius, currentCornerRadius)

// Set the drawing color and pen size for the border.
g.DrawingColor = currentBorderColor
g.PenSize = 2
// Draw the border shape just inside the background rectangle.
g.DrawRoundRectangle(1, 1, g.Width-2, g.Height-2, currentCornerRadius, currentCornerRadius)

// Enable anti-aliasing for smoother text rendering.
g.AntiAliasMode = Graphics.AntiAliasModes.HighQuality
g.AntiAliased = True
// Calculate the width and height of the button text.
Var tw As Double = g.TextWidth(ButtonText)
Var th As Double = g.TextHeight
// Calculate the X position to center the text horizontally.
Var tx As Double = (g.Width - tw) / 2
// Calculate the Y position to center the text vertically, with a small adjustment.
Var ty As Double = (g.Height + th) / 2 - 3
// Set the drawing color for the text.
g.DrawingColor = currentTextColor
// Draw the button text at the calculated centered position.
g.DrawText(ButtonText, tx, ty)

Let’s break down the changes in the Paint event:

  • We now use the CornerRadius property instead of a static constant for drawing the rounded rectangles.
  • We introduced an If Enabled Then ... Else block.
  • Inside the If Enabled Then block, we have a nested If IsPressed Then ... ElseIf IsHovered Then ... Else structure. This determines the currentBgColor:
    • If IsPressed is True, currentBgColor is set to HighlightColor.
    • If IsPressed is False but IsHovered is True, currentBgColor is set to HoverColor.
    • If neither IsPressed nor IsHovered is True, currentBgColor is set to BackgroundColor.
    • The currentBorderColor and currentTextColor are set directly from the BorderColor and TextColor properties when the button is enabled.
  • Inside the Else block (when Enabled is False), we set the colors to standard gray values (Color.LightGray for background, Color.Gray for border, and Color.DisabledTextColor for text) to give the button a disabled appearance.
  • Finally, the drawing commands use these currentBgColorcurrentBorderColorcurrentTextColor, and currentCornerRadius variables.

Using the Enhanced Custom Control

Now that our CanvasButton has more customization options and supports a disabled state, let’s see how to use these features.

  1. If you don’t already have an instance, drag the CanvasButton class from the Project Navigator onto a window in the Layout Editor.
  2. You can now access and set the new properties programmatically. For example, in a button’s Opening event, you could add code like this:
// Customize colors
Me.BackgroundColor = Color.RGB(200, 50, 50) // A shade of red
Me.HoverColor = Color.RGB(255, 100, 100) // A lighter red for hover
Me.HighlightColor = Color.RGB(150, 0, 0) // A darker red for pressed
Me.BorderColor = Color.Black
Me.TextColor = Color.White

// Adjust corner radius
Me.CornerRadius = 10

You can experiment with different values to see how they affect the button’s appearance. To test the disabled state, you can set the Enabled property to False.


Conclusion

We’ve now taken our custom button to the next level by adding extensive color customization, adjustable corners, and a disabled state.

This project can be downloaded from GitHub at: https://github.com/xolabsro/CanvasButton

Hope you enjoyed making the button your own! While it’s much more flexible now, there might be other features we could add down the road, so stay tuned!

Gabriel is a digital marketing enthusiast who loves coding with Xojo to create cool software tools for any platform. He is always eager to learn and share new ideas!