Progressbar example starting at wrong value

This topic contains 9 replies, has 2 voices, and was last updated by  Paul_dH 1 year, 10 months ago.

  • Author
    Posts
  • #31832

    Paul_dH
    Participant

    Hi All,

    I've edited a copy of MCP Mag: progress-bar-to-a-graphical-status-box with the following as a result:

    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 
    
    [System.Windows.Forms.Application]::EnableVisualStyles();
    
    $form1 = New-Object System.Windows.Forms.Form
    $form1.Text = "Datacopy to SharePoint"
    $form1.Height = 125
    $form1.Width = 400
    
    $form1.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedSingle 
    $form1.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
    
    $label1 = New-Object system.Windows.Forms.Label
    $label1.Location = New-Object System.Drawing.Size(10,10)
    $label1.Size = New-Object System.Drawing.Size(360,15)
    $label1.Text = ""
    $form1.controls.add($label1)
    
    $label2 = New-Object system.Windows.Forms.Label
    $label2.Location = New-Object System.Drawing.Size(10,65)
    $label2.Size = New-Object System.Drawing.Size(150,15)
    $label2.Text = ""
    $form1.controls.add($label2)
    
    $label3 = New-Object system.Windows.Forms.Label
    $label3.Location = New-Object System.Drawing.Size(285,65)
    $label3.Size = New-Object System.Drawing.Size(100,15)
    $label3.Text = ""
    $form1.controls.add($label3)
    
    $progressBar1 = New-Object System.Windows.Forms.ProgressBar
    $progressBar1.Location = New-Object System.Drawing.Size(10,40)
    $progressBar1.Size = New-Object System.Drawing.Size(360,20) 
    $progressbar1.Value = 0
    $progressBar1.Name = "Progress"
    $progressBar1.Style="Continuous"
    $form1.Controls.Add($progressBar1)
    
    [void] $form1.Show()
    [void] $form1.Focus()
    
    #run code and update the status form
    $SPOSource = "D:\Downloads\Office365\Edit\SPO-CreateSource.csv"
    
    # Count is set to -1, this is because line 1 contains the headers of the CSV file
    $ItemsTotal=-1
    $StartCount=0
    
    $ReadFileStream = New-Object IO.StreamReader $SPOSource
    while($ReadFileStream.ReadLine() -ne $null){ $ItemsTotal++ }
    $ReadFileStream.Close()
    
    $ItemsLeft = $ItemsTotal
    
    Start-Sleep -Seconds 2
    
    Import-CSV $SPOSource | ForEach-Object {
    	$Destination = $_.Destination
    	
    	$StartCount++
    	$ItemsLeft--
    	[int]$ItemPercent = ($StartCount/$ItemsTotal)*100
    	$progressbar1.Value = $ItemPercent
    	
    	$label1.Text="Total: $ItemsTotal, working on: $Destination"
    	$label2.Text="Progress: $ItemPercent%"
    	$label3.Text="Sources to go: $ItemsLeft"
    
    	$form1.Refresh()
    	
    	Start-Sleep -Seconds 3
    }
    
    $form1.Refresh()
    Start-Sleep -Seconds 3
    
    $form1.Close()
    

    I'm trying to count the lines in a CSV file and then perform a task with it. The only thing is that the start of the progressbar is wrong, the percentage is displaying the correct status but the bar starts at 25% or 33% (depending how many lines the CSV contains)

    How can I get the counter right?

    Thanks in advance 🙂

  • #31836

    Curtis Smith
    Participant

    Here is an example:

    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    
    [System.Windows.Forms.Application]::EnableVisualStyles();
    
    $form1 = New-Object System.Windows.Forms.Form
    $form1.Text = "Datacopy to SharePoint"
    $form1.Height = 125
    $form1.Width = 400
    
    $form1.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedSingle
    $form1.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
    
    $progressBar1 = New-Object System.Windows.Forms.ProgressBar
    $progressBar1.Location = New-Object System.Drawing.Point(15, 15)
    $progressBar1.Size = New-Object System.Drawing.Size(360,20)
    $progressbar1.Value = 0
    $progressBar1.Name = "Progress"
    $progressBar1.Style="Continuous"
    $form1.Controls.Add($progressBar1)
    
    [void] $form1.Show()
    [void] $form1.Focus()
    
    $records = Import-Csv input.csv
    $processed = 0
    ForEach ($record in $records) {
        $record.name # do work here
        Start-Sleep -Milliseconds 125
        $processed++
        $percentage = ($processed/($records.count))*100
        $progressBar1.Value = $percentage
        $form1.Refresh()
    }
    $form1.Close()
    
  • #31844

    Paul_dH
    Participant

    Hi @Curtis Smith,

    I've tried your sample like you posted, but the bar went to fast to see the difference 😛

    When I edited your sample so it contained the labels also and the start-sleep, the problem still persists.The bar starts at 0% but the process is at 50% at that moment. This means that the bar comes to 66% and then quits.

    Maybe this has something to do with the first line in the CSV file, this line should get ignored when building up the progressbar.

    Thanks for your help though 🙂

  • #31848

    Curtis Smith
    Participant

    can you post your modified code with the additional fields added This worked flawlessly on my system. If you can, also paste some sanitized content for the CSV. When posting use the (pre /pre) tags so the code is formatted well in the post.

  • #31849

    Paul_dH
    Participant

    This is the modified code sample:

    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    
    [System.Windows.Forms.Application]::EnableVisualStyles();
    
    $form1 = New-Object System.Windows.Forms.Form
    $form1.Text = "Datacopy to SharePoint"
    $form1.Height = 125
    $form1.Width = 400
    
    $form1.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedSingle
    $form1.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
    
    $label1 = New-Object system.Windows.Forms.Label
    $label1.Location = New-Object System.Drawing.Size(10,10)
    $label1.Size = New-Object System.Drawing.Size(360,15)
    $label1.Text = ""
    $form1.controls.add($label1)
    
    $label2 = New-Object system.Windows.Forms.Label
    $label2.Location = New-Object System.Drawing.Size(10,65)
    $label2.Size = New-Object System.Drawing.Size(150,15)
    $label2.Text = ""
    $form1.controls.add($label2)
    
    $label3 = New-Object system.Windows.Forms.Label
    $label3.Location = New-Object System.Drawing.Size(285,65)
    $label3.Size = New-Object System.Drawing.Size(100,15)
    $label3.Text = ""
    $form1.controls.add($label3)
    
    $progressBar1 = New-Object System.Windows.Forms.ProgressBar
    $progressBar1.Location = New-Object System.Drawing.Size(10,40)
    $progressBar1.Size = New-Object System.Drawing.Size(360,20)
    $progressbar1.Value = 0
    $progressBar1.Name = "Progress"
    $progressBar1.Style="Continuous"
    $form1.Controls.Add($progressBar1)
    
    [void] $form1.Show()
    [void] $form1.Focus()
    
    $records = Import-Csv "C:\TEMP\input.csv"
    $processed = 0
    
    ForEach ($record in $records) {
        $record.name # do work here
        Start-Sleep -Seconds 2
        $processed++
        $percentage = ($processed/($records.count))*100
        $progressBar1.Value = $percentage
    	$label1.Text="Total: $records.count, working on: $Destination"
    	$label2.Text="Progress: $percentage%"
    	$label3.Text="Sources to go: $ItemsLeft"
        $form1.Refresh()
    }
    
    Start-Sleep -Seconds 2
    $form1.Close()
    

    This is the content of the CSV file:

    Source,Destination
    C:\Dropbox,Dropbox
    C:\Office365
    
  • #31855

    Curtis Smith
    Participant

    Ok, this is what I get.

    1) The process starts and does in initial for load gets the content and begins sleeping
    At this point it appears that the for is just taking a little while to load, but what is actually happening is the start-sleep cmdlet is using the application thread to sleep, so the application cannot draw the form completely.
    2) after it sleeps it increments the processed count and updates the fields, some of which need some work (see below) and the progressbar value.
    3) Then it loops and starts sleeping again. The labels get updated, but the progress bar takes longer to draw and again the start-sleep cmdlet get the application thread to so the progress bar cannot be drawn.
    4) sleep finishes and form is able to finish drawing the 50% progress
    5) processed gets incremented, fields and progressbar value updated
    6) loop ends for 2 records and sleep beings.
    Again the labels get updated but not enough time to update progressbar before sleep begins so it just shows 50%.
    7) after sleep finishes form is closed and you never see it go to 100%

    So the issue is that additional processing is cause the form to not be able to be updated since it is single threaded.

    This appears to be a common issue and here is an innovative solution (http://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/)

    Label Issues:

    $label1.Text="Total: $records.count, working on: $Destination"

    In order to get the count property inside of a "" string, you will need to use $() so that powershell knows it needs to do some calculation, not just return a variable since this is the property of an object. As well, you are not setting your $Destination variable anywhere, but you can get it as a property of your $records object like the count using the $() subexpression.

    $label1.Text="Total: $($records.count), working on: $($record.Destination)"
    $label3.Text="Sources to go: $ItemsLeft"

    You are not calculating $ItemsLeft anywhere in your script. You can either do that somewhere before trying to use the variable, or you can calulate it with a subexpression inside "" like on the $label1

    $label3.Text="Sources to go: $($records.count - $processed)"
  • #31870

    Paul_dH
    Participant

    Hi Curtis,

    I've been working al evening on you input and came up with the following script as a result.

    Could you perhaps screen the script for flaws? It works on a Runspace and with WPF now.

    Thanks again! It was a real leaning journey 🙂

    I've created a link to my OneDrive because I can't attach files on my post and the PRE tag is killing my comments and XAML code... Download link to script

    Regards, Paul

    Source of the CSV file (note that not all are containing a destination, this is on purpouse)

    Source,Destination
    D:\Docs\Huis,Temp-Huis
    D:\Docs\Boetes,Temp-Boetes
    D:\Docs\Recepten,Temp-Recepten
    D:\Temp
    D:\Downloads,Downloads
    C:\Users
    C:\Windows,Windows
    
  • #31873

    Curtis Smith
    Participant

    Looks really good. The only problem that I saw is that sometimes the script tried to update the progressbar after processing the first item, before the runspace finished drawing the progressbar which resulting in the following error:

    You cannot call a method on a null-valued expression.
    At line:25 char:9
    +         $syncHash.ProgressBar.Dispatcher.invoke([action]{$syncHash.ProgressBar.V ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
        + FullyQualifiedErrorId : InvokeMethodOnNull
    

    This will likely never be an issue since you will be doing some work before trying to update the progress bar instead of updating it almost immediately like in the test code. If you wanted, however, you could add a short sleep after the $processed=0 to give time to render. 1 milisecond was long enough in my testing, but again, you will doing other work here so probably not needed.

    $processed = 0
    Start-Sleep -Milliseconds 1
    
    ForEach ($record in $records) {
    

    The last thing to note is that you may want to see if you can hide X on the dialog box or somehow detect if it has been closed and restart it. As it is, if the form is close the whole script stops processing.

  • #31883

    Paul_dH
    Participant

    Seems there is some more work to do, when I start the script on a Windows 7 x64 computer with PowerShell 4.0 the form isn't closable. The form makes the process hang, so I think I have to add a closing event for that. It also should be made possible to close the form from the script in the end.

    Unfortunately the next day I can work on the little project is Saturday 🙁

    In the end I would like to add a details option to the form that flips out and shows the current progress, as in "Currently copying..............................Status: Done"

    To be continued 🙂

  • #32884

    Paul_dH
    Participant

    If someone might be interested, I've updated the script a bit.

    Have fun!

    Link

You must be logged in to reply to this topic.