Winforms ProgressBar Takes time to Render

前端 未结 7 1630
清歌不尽
清歌不尽 2021-01-15 10:12

I have noticied that when using the PorgressBar. If I set the value to x, the value displayed is not immediately updated, it takes a small amount of time to draw it as the b

相关标签:
7条回答
  • 2021-01-15 10:49

    I just tried this out and can see exactly what you mean. Unfortunately after spending a little while seeing if the DrawToBitmap functions on the progress bar might help, I've come up short.

    The next step would be to create a custom progress bar that exposes events for when rendering has completed.

    For a reasonable example on how to create a custom progress bar, try here: http://msdn.microsoft.com/en-us/library/system.windows.forms.progressbarrenderer(v=VS.100).aspx

    A quick scan over the code looks like you should be able to plug in an 'OnRendered' event or similar on or around the calls to 'DrawHorizontalChunks' (or 'DrawVerticalChunks').

    Probably not the answer you was after, but at least gives you the control you need if you pursue it?

    Note: I haven't tried this myself, so please don't send me hate mail if you spend all day on this to find you get the same results...

    Good Luck!

    EDIT:

    Wasn't happy with my response, seemed a bit lazy... The following uses a custom progress bar as I described. It has a couple basic properties for setting Max/Min values, Performing steps, and setting the value directly. I've tested this by changing the sleep interval to various amounts, in all cases the form displayed the progress bar as full before closing. Note the new OnRendered event.

    Imports System
    Imports System.Drawing
    Imports System.Windows.Forms
    Imports System.Windows.Forms.VisualStyles
    
    Public Class Form1
        Inherits Form
        Private WithEvents bar1 As ProgressBarWithRender = New ProgressBarWithRender()
    
    
        Public Sub New()
            InitializeComponent()
            Me.Size = New Size(500, 500)
            bar1.Location = New Point(100, 100)
            bar1.Width = 300
            bar1.Height = 50
            bar1.Maximum = 30
            bar1.Step = 1
            Controls.Add(bar1)
        End Sub
    
        Public Sub OnRendered(ByVal valueRendered As Integer) Handles bar1.OnRendered
            If valueRendered = bar1.Maximum Then
                ' We know everything has been drawn
                Me.Close()
            End If
        End Sub
    
    
        <STAThread()> _
        Public Shared Sub Main()
            ' The call to EnableVisualStyles below does not affect
            ' whether ProgressBarRenderer.IsSupported is true; as 
            ' long as visual styles are enabled by the operating system, 
            ' IsSupported is true.
            Application.EnableVisualStyles()
            Application.Run(New Form1())
    
        End Sub 'Main
    
        Private Sub Form1_Click(sender As Object, e As System.EventArgs) Handles Me.Click
            For i = 1 To 30
                bar1.PerformStep()
                Threading.Thread.Sleep(10)
            Next
        End Sub
    
    End Class 'Form1
    
    Public Class ProgressBarWithRender
        Inherits Control
    
        Public Delegate Sub RenderedEventArgs(ByVal valueRendered As Integer)
        Public Event OnRendered As RenderedEventArgs
    
        Private ProgressBarRectangles() As Rectangle
    
        Public Property [Step] As Integer
    
        Public Property InnerPadding As Integer = 3
    
        Private _Maximum As Integer
        Public Property Maximum As Integer
            Get
                Return _Maximum
            End Get
            Set(value As Integer)
                _Maximum = value
                CalculateTickSizes()
            End Set
        End Property
    
        Private _Minimum As Integer
        Public Property Minimum As Integer
            Get
                Return _Minimum
            End Get
            Set(value As Integer)
                _Minimum = value
                CalculateTickSizes()
            End Set
        End Property
    
        Private _Value As Integer
        Public Property Value As Integer
            Get
                Return _Value
            End Get
            Set(newValue As Integer)
                If newValue < Me.Value AndAlso newValue > 0 Then
                    Throw New NotImplementedException("ProgressBarWithRender does not support decrementing the value")
                End If
                Me._Value = newValue
            End Set
        End Property
    
        Public Sub PerformStep()
            ' Ensure step doesn't exceed boundaries
            If Value + [Step] > Maximum Then
                Value = Maximum
            ElseIf Value + [Step] < Minimum Then
                Value = Minimum
            Else
                Value += [Step]
            End If
    
            ' We are limited by the Renderers Chunk Width, so we possibly can't draw every step if there is a high maximum
            Dim g As Graphics = Me.CreateGraphics
            ProgressBarRenderer.DrawHorizontalChunks(g, ProgressBarRectangles(Value - Minimum))
            RaiseEvent OnRendered(Value)
    
        End Sub
    
        Protected Overrides Sub OnPaint(e As System.Windows.Forms.PaintEventArgs)
            MyBase.OnPaint(e)
            If Not ProgressBarRenderer.IsSupported Then
                Throw New NotImplementedException("Progress Bar Rendering is not supported")
            End If
            ProgressBarRenderer.DrawHorizontalBar(e.Graphics, ClientRectangle)
        End Sub
    
        Private Sub CalculateTickSizes()
            ' Changing the Maximum will change the tick rectangle size
            ProgressBarRectangles = New Rectangle(Maximum) {}
            Dim chunkThickness As Integer = ProgressBarRenderer.ChunkThickness + (ProgressBarRenderer.ChunkSpaceThickness * 2)
            Dim tickThickness As Double = ((ClientRectangle.Width - (InnerPadding * 2)) - (ProgressBarRenderer.ChunkSpaceThickness * 2)) / (Maximum - Minimum)
            If tickThickness < chunkThickness Then
                Debug.Print("This will go wrong because we can't draw small enough chunks...")
            End If
            For i As Integer = 0 To Maximum
                Dim filledRectangle As Integer = CInt(tickThickness * i)
                ProgressBarRectangles(i) = New Rectangle(ClientRectangle.X + InnerPadding,
                                                         ClientRectangle.Y + InnerPadding,
                                                         filledRectangle,
                                                         ClientRectangle.Height - (InnerPadding * 2))
            Next
        End Sub
    
    End Class
    
    0 讨论(0)
  • 2021-01-15 10:58

    I have a similar problem. I would prefer to sue the standard progress bar in order to have a typical design in the application.

    It is true that it needs time to update, and since DoEvents does not work on its own, I would recommend to do the loading by using a backgroundworker. When finished and it still doesn't work, then either add a doevents, or a small delay. But I guess your solution to ad a delay of 100ms would be best, since it requirest the fewest changes and is still working. what about just adding 10ms delay to it. The other way would be: try progressBar.Invalidate (force a repaint), add code to paint, which checks if paint is executed, and then close the form. I guess DoEvents won't work in paint, since paint needs to finish. so you could enabl a timer with 100ms interval and it would close the window.

    about everything less 1/24s is invisible for humans is bu..sh.. there are liquids in the eye which see even mcuh shorter things. It is a difference if we can immediately react to something, but since the information is 'burned' on the surface of the eyes until it will be 'read' by nerves, it is not lost. there are even problems with 60Hz, and I am sure most people know that annoying 60Hz crt problem. LCD does not flicker that intense, but if you need a high framerate, and 60 is not enough, then you can still 'see' the problems with 60 and it looks mcuh better with 100 or higher.

    0 讨论(0)
  • 2021-01-15 11:03

    The problem is that you're in a single threaded program and the thread needs time to update the display.

    Add the line

    Application.DoEvents()
    

    Before closing the UpdateProgress sub.

    And you can get rid of the last two refresh.

    0 讨论(0)
  • 2021-01-15 11:06

    This is my code based on Matt Wilko's suggestion:

    Imports System.Net
    Imports System.IO
    Imports System.Text.RegularExpressions
    Public Class Form1
    Dim client As New WebClient
    Public Sub New()
    
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
    
        ' Add any initialization after the InitializeComponent() call.
        AddHandler client.DownloadStringCompleted, AddressOf client_DownloadStringCompleted
        AddHandler client.DownloadProgressChanged, AddressOf client_DownloadProgressChanged
    
    End Sub
    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        ProgressBar1.Value = 0
        ProgressBar1.Visible = True
        client.DownloadStringAsync(New Uri("http://somewebsite.com"), Nothing)
    End Sub
    Private Sub client_DownloadProgressChanged(ByVal sender As Object, ByVal e As System.Net.DownloadProgressChangedEventArgs)
        If ProgressBar1.Value < e.ProgressPercentage Then
            ProgressBar1.Value = e.ProgressPercentage
        End If
    End Sub
    Private Sub client_DownloadStringCompleted(ByVal sender As Object, ByVal e As System.Net.DownloadStringCompletedEventArgs)
        ProgressBar1.Value = 100
        Timer1.Enabled = True
    End Sub
    
    Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick
        Static waitToCloseProgressBar As Integer
        If ProgressBar1.Value = 100 Then
            If waitToCloseProgressBar > 6 Then
                Timer1.Enabled = False
                waitToCloseProgressBar = 0
                ProgressBar1.Visible = False
            Else
                waitToCloseProgressBar = waitToCloseProgressBar + 1
            End If
        End If
    End Sub
    End Class
    
    0 讨论(0)
  • 2021-01-15 11:07

    I have good results with progressbar lag, setting the value with this function:

    Private Sub SetProgressNoAnimation(ByVal value As Integer)
        ' To get around the progressive animation, we need to move the 
        ' progress bar backwards.
        If (value = progressBarCarga.Maximum) Then
            ' Special case as value can't be set greater than Maximum.
            progressBarCarga.Maximum = (value + 1)
            ' Temporarily Increase Maximum
            progressBarCarga.Value = (value + 1)
            ' Move past
            progressBarCarga.Maximum = value
            ' Reset maximum
        Else
            progressBarCarga.Value = (value + 1)
            ' Move past
        End If
        progressBarCarga.Value = value
        ' Move to correct value
    End Sub
    

    More information:

    https://derekwill.com/2014/06/24/combating-the-lag-of-the-winforms-progressbar/

    0 讨论(0)
  • 2021-01-15 11:08

    I found that using PerformStep() instead of setting the value did not have this rendering delay - still had to call Application.DoEvents() though.

    0 讨论(0)
提交回复
热议问题