Implementing Custom Toggle Buttons in C# WinForms: A Manual Drawing Approach Based on OnPaint Events

Dec 07, 2025 · Programming · 7 views · 7.8

Keywords: C# | WinForms | Custom Controls | OnPaint Events | Toggle Buttons | Manual Drawing

Abstract: This paper provides an in-depth exploration of custom toggle button implementation in C# WinForms. After analyzing the limitations of standard CheckBox controls with Appearance set to Button, it focuses on the manual drawing method through overriding OnPaint and OnBackgroundPaint events. The article details how to achieve sunken effects when buttons are pressed, offers complete code examples and implementation steps, and discusses performance optimization and extensibility possibilities.

Introduction

In C# WinForms application development, creating interactive controls with specific visual effects is a common requirement. Toggle buttons, as important interactive elements in user interfaces, require precise visual feedback for optimal user experience. The standard WinForms control library provides the CheckBox control, which can simulate button behavior by setting its Appearance property to Button, but this approach has significant visual limitations.

Limitations of Standard Approaches

Using the CheckBox control with Appearance = System.Windows.Forms.Appearance.Button does create a toggle-like button control. However, this method presents several issues:

The following code demonstrates the standard approach:

CheckBox checkBox = new System.Windows.Forms.CheckBox();
checkBox.Appearance = System.Windows.Forms.Appearance.Button;

Custom Drawing Solution

To overcome the limitations of standard methods, custom drawing techniques can be employed. The core concept involves overriding control painting events to manually control visual presentation.

Implementation Principles

Custom drawing toggle buttons involves these key technical aspects:

  1. Inheriting from standard Button control or creating custom controls
  2. Overriding OnPaint method for foreground drawing control
  3. Overriding OnBackgroundPaint method (or related methods) for background drawing control
  4. Managing button states (normal, pressed, hover, etc.)
  5. Implementing visual algorithms for sunken effects

Core Implementation Code

Below is a basic framework for custom toggle button implementation:

public class CustomToggleButton : Button
{
    private bool _isPressed = false;

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        
        // Custom drawing logic
        if (_isPressed)
        {
            // Draw sunken effect
            DrawSunkenEffect(e.Graphics);
        }
        else
        {
            // Draw raised effect
            DrawRaisedEffect(e.Graphics);
        }
        
        // Draw text
        DrawButtonText(e.Graphics);
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        _isPressed = !_isPressed;
        this.Invalidate(); // Trigger redraw
        base.OnMouseDown(e);
    }

    private void DrawSunkenEffect(Graphics g)
    {
        // Implementation of sunken effect drawing logic
        // Using different border colors to simulate depth
        Rectangle rect = this.ClientRectangle;
        
        // Draw dark border (top-left)
        using (Pen darkPen = new Pen(Color.FromArgb(100, 100, 100)))
        {
            g.DrawLine(darkPen, rect.Left, rect.Top, rect.Right - 1, rect.Top);
            g.DrawLine(darkPen, rect.Left, rect.Top, rect.Left, rect.Bottom - 1);
        }
        
        // Draw light border (bottom-right)
        using (Pen lightPen = new Pen(Color.FromArgb(200, 200, 200)))
        {
            g.DrawLine(lightPen, rect.Right - 1, rect.Top, rect.Right - 1, rect.Bottom - 1);
            g.DrawLine(lightPen, rect.Left, rect.Bottom - 1, rect.Right - 1, rect.Bottom - 1);
        }
    }

    private void DrawRaisedEffect(Graphics g)
    {
        // Implementation of raised effect drawing logic
        // Opposite color scheme to sunken effect
        Rectangle rect = this.ClientRectangle;
        
        // Draw light border (top-left)
        using (Pen lightPen = new Pen(Color.FromArgb(200, 200, 200)))
        {
            g.DrawLine(lightPen, rect.Left, rect.Top, rect.Right - 1, rect.Top);
            g.DrawLine(lightPen, rect.Left, rect.Top, rect.Left, rect.Bottom - 1);
        }
        
        // Draw dark border (bottom-right)
        using (Pen darkPen = new Pen(Color.FromArgb(100, 100, 100)))
        {
            g.DrawLine(darkPen, rect.Right - 1, rect.Top, rect.Right - 1, rect.Bottom - 1);
            g.DrawLine(darkPen, rect.Left, rect.Bottom - 1, rect.Right - 1, rect.Bottom - 1);
        }
    }

    private void DrawButtonText(Graphics g)
    {
        StringFormat format = new StringFormat();
        format.Alignment = StringAlignment.Center;
        format.LineAlignment = StringAlignment.Center;
        
        using (Brush textBrush = new SolidBrush(this.ForeColor))
        {
            g.DrawString(this.Text, this.Font, textBrush, 
                         this.ClientRectangle, format);
        }
    }
}

Advanced Optimization Techniques

Beyond basic implementation, further optimizations include:

Performance Considerations

When implementing custom drawn controls, consider these performance aspects:

  1. Minimize creation and destruction of GDI+ objects in OnPaint method
  2. Use Invalidate method judiciously to avoid excessive redrawing
  3. Consider using cached bitmaps for complex drawings
  4. Ensure thread safety in drawing code

Extended Applications

Custom drawing techniques apply not only to toggle buttons but also to:

Conclusion

Implementing custom drawing through overriding OnPaint and OnBackgroundPaint events provides a highly flexible solution for toggle buttons in C# WinForms. This approach not only achieves precise sunken effects but also opens extensive possibilities for visual customization. Developers can adjust drawing logic according to specific requirements, creating unique interface elements that align with application design languages.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.