Scepticalist
Scepticalist

Reputation: 3923

Powershell Forms - pausing loop with button

I have a forms-based script that does a lot of file copies, from various sources. The main loop for copying the files can run for a long time and I'd like to add a pause button to hold processing (until the pause button is pressed again)

Following the advice here, I'm using [System.Windows.Forms.Application]::DoEvents() to keep the form responsive and provide a way to break out the copy loop gracefully if needed.

I'm struggling to see how I can introduce a button to pause the "ForEach" loop instead of breaking it - can anyone point me in the right direction as my attempts to use Do-Until/While seems to hang the script in some way?

A vastly simplified sample of what I'm doing is below:

$StartCopyButton.Add_Click({
    $script:CancelLoop = $false
    $script:PauseToggle = $false
    $CancelButton.Enabled = $true
    $StartCopyButton.Enabled = $false

    Get-ChildItem -LiteralPath $Source -Recurse -File | ForEach {
        Copy-Item -Path $.FullName -Destination $NewDestination
        [System.Windows.Forms.Application]::DoEvents()
        If($script:CancelLoop -eq $true) {
            #Exit the loop
            Break;
        }
        If ($script:PauseToggle) {
            Do { 
            [System.Windows.Forms.Application]::DoEvents()
            } Until (!$script:PauseToggle)
        }
    }
    $CancelButton.Enabled = $false
    $StartCopyButton.Enabled = $true
})

$CancelButton.Add_Click({
    $script:CancelLoop = $true
})
$PauseButton.Add_Click({
    # Boolean change value to true/false
    $script:PauseToggle = !$script:PauseToggle
})

Upvotes: 1

Views: 1938

Answers (2)

marsze
marsze

Reputation: 17035

Actually, you need only one button for starting & pausing. Here is one very basic approach to illustrate my idea, NOT TESTED:

$script:CancelLoop = $false
$script:PauseLoop = $false
$CopyButton.Add_Click({
    # toggle the start/pause state when clicked
    $script:PauseLoop = -not $script:PauseLoop
    if ($script:PauseLoop) {
        $CancelButton.Enabled = $false
        $CopyButton.Text = "Start"
    }
    else {
        $CancelButton.Enabled = $true
        $CopyButton.Text = "Pause"
        # start / resume the loop
        Get-ChildItem $Source -Recurse -File | foreach {
            $newPath = Join-Path $NewDestination $_.Name
            # test if file was already copied
            # (might want to compare modified times too)
            if (-not (Test-Path $newPath)) {
                Copy-Item $_.FullName $newPath
            }
            [System.Windows.Forms.Application]::DoEvents()
            if ($script:CancelLoop -or $script:PauseLoop) {
                # exit loop if cancelled or paused
                break
            }
        }
        $CancelButton.Enabled = $false
        $CopyButton.Text = "Start"
    }
})

Another approach would be getting all files first, saving them in a collection, and then store the index and pause/resume at that point. But that bears the risk that the list of files might have changed in the meantime. So a really "safe" solution would be more complex.

Upvotes: 1

montonero
montonero

Reputation: 1721

You could check the Pause state and if it's true then do empty loop with DoEvents. Like this (didn't tested it though):

$StartCopyButton.Add_Click({
    $script:CancelLoop = $false
    $script:Pause = $false
    $CancelButton.Enabled = $true
    $StartCopyButton.Enabled = $false

    Get-ChildItem -LiteralPath $Source -Recurse -File | ForEach {
        Copy-Item -Path $.FullName -Destination $NewDestination
        [System.Windows.Forms.Application]::DoEvents()
        while ($script:Pause -and !$script:CancelLoop) {
            [System.Windows.Forms.Application]::DoEvents()
            sleep 0.1
        }
        If($script:CancelLoop -eq $true) {
            #Exit the loop
            Break;
        }
    }
    $CancelButton.Enabled = $false
    $StartCopyButton.Enabled = $true
})

$CancelButton.Add_Click({
    $script:CancelLoop = $true
})
$PauseButton.Add_Click({
    $script:Pause = !$script:Pause
})

Upvotes: 1

Related Questions