Powershell GUI PictureBox not able to change photos

Viewed 45

I am new to Powershell. I am trying to create picture box that will load a lot of images in a folder. I have a folder that contain 10 images. but i want my picturebox to display it dynamically every second or 3 seconds

here is my code so far.

####################################### Form settings ##############################################
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")  

    $Form = New-Object System.Windows.Forms.Form
    $Form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
    $Form.Anchor = "Top,Bottom,Left,Right"
    $Form.Size = New-Object System.Drawing.Size(1920,1600) 
    $Form.AutoScale = $True 
    $Form.StartPosition = "CenterScreen" #loads the window in the center of the screen
    $Form.BackgroundImageLayout = "Zoom"
    $Form.MinimizeBox = $True
    $Form.MaximizeBox = $False
    $Form.WindowState = "Normal"
    $Form.SizeGripStyle = "Auto"
    $Form.AutoSizeMode = New-Object System.Windows.Forms.AutoSizeMode
    $Form.SizeGripStyle = "Show"
    $Form.BackColor = "LightGray"
    ###################################################################################################
    ###################################################################################################
    ######################################### Image Folder ############################################
 
    $ImagePreview = New-Object System.Windows.Forms.PictureBox
    $ImagePreview.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::StretchImage
    $ImagePreview.Location = New-Object System.Drawing.Size(965,110) 
    $ImagePreview.Size = New-Object System.Drawing.Size(295,370)
    $ImagePreview.BackColor = "White"
    $Form.Controls.Add($ImagePreview)
 
    Function ImageFolder($ImagePreview)
    {
        $ImageItem = Get-Item "C:\Newfolder" 
        $ImagePreview.ImageLocation = $ImageItem
    }

    $TimerImageFolder = New-Object System.Windows.Forms.Timer
    $TimerImageFolder.Interval  = 3000
    $TimerImageFolder.Add_Tick({$ImageFolder $ImagePreview})
    $TimerImageFolder.Enabled = $True
    
    $ImageGroupBox = New-Object System.Windows.Forms.GroupBox
    $ImageGroupBox.Location = New-Object System.Drawing.Size(940,70) 
    $ImageGroupBox.size = New-Object System.Drawing.Size(350,440)
    $ImageGroupBox.text = "Preview"
    $ImageGroupBox.BackColor = "DimGray"
    $ImageGroupBox.ForeColor = "White"
    $Form.Controls.Add($ImageGroupBox)

    ####################################### Result ##############################################
    $Form.Add_Shown({$Form.Activate()})
    [void] $Form.ShowDialog()

I don't know which part is wrong. Thank You so much for the help Guys

1 Answers

What you're attempting to do is a cumbersome task from PowerShell. As you may note, it requires the instantiation of a powershell instance where initialize the Forms.Timer, this is needed because we need the .Tick event to happen on a separate thread, otherwise, the form would freeze on each triggering of this event.

The code shared below is, as a minimal reproduction of what you're trying to achieve, I have tried to simplified as much as possible but, as stated before, this is no easy task from PowerShell.

Below example as well as another example using an API to load the pictures from the Internet instead of from Disk can be found on this Gist. There is also a gif demo there.

using namespace System.Windows.Forms
using namespace System.Drawing
using namespace System.Management.Automation.Runspaces

Add-Type -AssemblyName System.Drawing, System.Windows.Forms

[Application]::EnableVisualStyles()

$event = @{}
# Get the paths of all the pictures in the `pictures` folder
$pictures = (Get-ChildItem .\pictures -Filter *.png).FullName

# basic form
$form = [Form]@{
    ClientSize    = [Size]::new(1280, 720)
    StartPosition = 'CenterScreen'
}

# basic picturebox, note it includes a `Name` so it's easier to find
# from inside the `powershell` instance
$pictureBox = [PictureBox]@{
    Name      = 'myPictureBox'
    Anchor    = 'top, left'
    SizeMode  = [PictureBoxSizeMode]::StretchImage
    Location  = [Point]::new(20, 20)
    Size      = [Size]::new($form.Width - 60, $form.Height - 80)
    Image     = [Drawing.Image]::FromFile($pictures[0])
}
$form.Controls.Add($pictureBox)

# event to resize the picturebox when the form is resized
$form.Add_Resize({
    $pictureBox.Size = [Size]::new($this.Width - 60, $this.Height - 80)
})

# initialize a `powershell` instance where we can handle the the Slide Show
# in PowerShell we require to perform this action from a different thread,
# else the form will freeze every 3000 milliseconds
$ps = [powershell]::Create().AddScript({
    param($pictures, $form)

    [ref] $ref = 1
    $timer = [Windows.Forms.Timer]@{
        Interval = 3000
    }

    # find the pictureBox in the Form controls
    $pictureBox = $form.Controls.Find('myPictureBox', $false)[0]

    # this Tick Event swaps the pictures when triggered
    $timer.Add_Tick({
        $pictureBox.Image = [Drawing.Image]::FromFile($pictures[$ref.Value++ % $pictures.Count])
    })

    # Start the Timer
    $timer.Enabled = $true

    # this `while` loop keeps this thread running until the form is closed
    while($form.DialogResult -eq 'None') {
        # we perform `Application.DoEvents()` so the form is updated on each loop iteration,
        # without it we wouldn't see the picturebox updated
        [Windows.Forms.Application]::DoEvents()
    }
    $form.DialogResult
    # here we pass the list of paths and the form instance itself to this thread
}).AddParameters(@{ pictures = $pictures; form = $form })

# when the form is shown
$form.Add_Shown({
    # bring it to front
    $this.Activate()
    # and add this AsyncResult to the `$events` hashtable
    $event['AsyncResult'] = $ps.BeginInvoke()
})

# display the form (this blocks the current thread)
$null = $form.ShowDialog()
# when the form is closed, stop the runspace
$ps.EndInvoke($event['AsyncResult'])
# and dispose everything
$form, $ps | ForEach-Object Dispose
Related