vb.netwinformscustom-controlsdouble-buffering

How to resolve unwanted flickering of Controls when the Form has a background Image?


Overview of problem

I am facing this issue when I try to hide some Labels, TextBoxes and a Button: all these Control flicker as they hide but other Control that are not involved are fine. I do not want this effect.
Also, this doesn't happen for Show(), only happens for Hide().

Flickering effect while hiding labels

What I have tried:

Removing the background image resolved this problem but I do want my background image visible.
When I searched the Web, I learnt how to create this effect intentionally, however I could not find a solution to remove it.
Others stated that this is just a hardware limitation however I do have a capable CPU.

VB.NET Code:

Its just some <element>.Hide()s

Private Sub GetTaskNameButton_Click(sender As Object, e As EventArgs) Handles GetTaskNameButton.Click
    GetTaskNameButton.Hide()
    TaskInputTextBox.Hide()
    TaskNameInputLabel.Hide()
    TaskTimeLabel.Hide()
    TaskHourLabel.Hide()
    TaskHoursBox.Hide()
    TaskMinutesLabel.Hide()
    TaskMinsBox.Hide()
    TasksCheckedListBox.Items.Add(TaskInputTextBox.Text + " >Time: " + TaskHoursBox.Text + "Hr " + TaskMinsBox.Text + "Min")
    TaskInputTextBox.Text = ""
    TaskHoursBox.Text = ""
    TaskMinsBox.Text = ""
End Sub

Solution

  • Nothing to do with CPU limitations. This is related to the rendering of the Background of a Form and the content of its child Controls.
    Read a description here: How to fix the flickering in User Controls.
    (but WS_EX_COMPOSITED won't help you here).

    Since you have some Controls that a combined functionality, you can build a UserControl to group the single Controls into a specialized entity that provide the functionality and contains all the logic required to perform this Task and notify when a choice has been made (all input values are validated and the submit Button is cliecked).

    To handle the transparency of this UserControl, you have to tweak its standard behavior a little bit, since just setting BackColor = Color.Transparent is not enough when you have a Form with a background Image: the Transparency would be simulated considering the Form's Background Color, not the content of the background Image (an Image is not a Color).

    You can make your UserControl actually transparent, preventing its background from being painted.

    When the Add Task Button is clicked, the UserControl can raise a public event, passing in a custom EventArgs object - after validation - the values entered.
    The Form can subscribe to this event and read the custom EventArgs Properties when the event is raised.

    This is how it looks like:

    UserControl Transparent

    The Image shown here has a size of 3840x2560 (it's freely downloadable from the Web).
    Try to resize the Form without double-buffering it :)

    A PasteBin of the complete UserControl, in case it's needed: Transparent UserControl


    Assuming the UserControl (AddNewTask) added to a Form is named AddNewTask1, you can add an event handler to its AddTaskClicked event, using the Designer or in code, in the Form Constructor:

    Public Class SomeForm
        Public Sub New()
            InitializeComponent()
            AddHandler AddNewTask1.AddTaskClicked, AddressOf OnTaskAdded
        End Sub
    
        Private Sub OnTaskAdded(sender As Object, e As AddNewTask.AddTaskEventArgs)
            Dim values As String = $"{e.TaskName}: Hours: {e.TaskHours}, Minutes: {e.TaskMinutes}"
        End Sub
    End Sub
    

    The AddNewTask UserControl:

    Public Class AddNewTask
    
        Private Const WS_EX_TRANSPARENT As Integer = &H20
        Private Const WS_CLIPCHILDREN As Integer = &H2000000
    
        Public Event AddTaskClicked As EventHandler(Of AddTaskEventArgs)
    
        Public Sub New()
            SetStyle(ControlStyles.Opaque Or ControlStyles.ResizeRedraw, True)
            SetStyle(ControlStyles.OptimizedDoubleBuffer, False)
            InitializeComponent()
        End Sub
    
        Protected Overrides ReadOnly Property CreateParams As CreateParams
            Get
                Dim cp As CreateParams = MyBase.CreateParams
                cp.Style = cp.Style And Not WS_CLIPCHILDREN
                cp.ExStyle = cp.ExStyle Or WS_EX_TRANSPARENT
                Return cp
            End Get
        End Property
    
        Private Sub btnAddTask_Click(sender As Object, e As EventArgs) Handles btnAddTask.Click
            Dim hours As UInteger
            Dim sHours = If(String.IsNullOrEmpty(txtHours.Text.Trim()), "0", txtHours.Text)
    
            If (Not UInteger.TryParse(sHours, hours)) Then
                ShowInputErrorMessage("Invalid Hours", txtHours)
                Return
            End If
    
            Dim minutes As UInteger
            Dim sMinutes = If(String.IsNullOrEmpty(txtMinutes.Text.Trim()), "0", txtMinutes.Text)
    
            If (Not UInteger.TryParse(sMinutes, minutes)) Then
                ShowInputErrorMessage("Invalid Minutes", txtMinutes)
                Return
            End If
    
            Hide()
            Dim args = New AddTaskEventArgs(txtTaskName.Text, hours, minutes)
            RaiseEvent AddTaskClicked(Me, args)
    
            txtHours.Clear()
            txtMinutes.Clear()
            txtTaskName.Clear()
            ActiveControl = txtTaskName
        End Sub
    
        Private Sub ShowInputErrorMessage(msg As String, ctrl As TextBox)
            MessageBox.Show(msg)
            ctrl.Select()
            ctrl.SelectAll()
        End Sub
    
        Public Class AddTaskEventArgs
            Inherits EventArgs
            Public Sub New(sTaskName As String, hours As UInteger, minutes As UInteger)
                TaskName = sTaskName
                TaskHours = hours
                TaskMinutes = minutes
            End Sub
            Public ReadOnly Property TaskName As String
            Public ReadOnly Property TaskHours As UInteger
            Public ReadOnly Property TaskMinutes As UInteger
        End Class
    End Class