sighclone
sighclone

Reputation: 114

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

Upvotes: 2

Views: 2002

Answers (1)

Jimi
Jimi

Reputation: 32223

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.

  • Use SetStyle() to set ControlStyles.Opaque, so the background is not painted.
  • Set ControlStyles.OptimizedDoubleBuffer to False: this UserControl cannot use DoubleBuffering of course, otherwise we're back at square one (the Parent Control's background is used to build the BufferedGraphcs object)
  • Override CreateParams to add WS_EX_TRANSPARENT to the extended styles of the UserControl Window, so the System won't interfere, letting other windows behind the UserControl draw their content first (but we won't draw ours after).
  • The WS_CLIPCHILDREN Style needs to be removed (since the base class, Control, adds it) otherwise the UserControls's child Controls may disappear when the Form is resized.

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.

  • Since your Form has a BackgroudImage, set DoubleBuffered = True.

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

Upvotes: 2

Related Questions