Powershell WPF observablecollection not updating wpf - wpf

I am using a observablecollection to insert data for bindings on wpf GUI but it's not updating.
$global:myCollection = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
[string]$global:MyLabelContent = 'Test'
$myCollection.Insert(0,[ref]$MyLabelContent)
This is the binding function i am using (from link below)
Function Set-Binding {
Param(
$Target,
$TargetProperty,
$Path,
$Source
)
$Binding = New-Object System.Windows.Data.Binding
$Binding.Path = $Path
$Binding.Mode = [System.Windows.Data.BindingMode]::TwoWay
If ($Source)
{
$Binding.Source = $Source
}
[void][System.Windows.Data.BindingOperations]::SetBinding($Target,$TargetProperty,$Binding)
}
Here is the actuall binding call:
Set-Binding -Target $MyLabel -TargetProperty $([System.Windows.Controls.Label]::ContentProperty) -path "[0].Value" -Source $MyCollection
And here's how i change the label content
[string]$global:MyLabelContent = $mystring
If i close and open the window again the label value is correctly set, so the binding is actually working...
What am i doing wrong? i'm already using the same system for menuitems checkboxes and it works.
https://smsagent.blog/2017/02/03/powershell-deepdive-wpf-data-binding-and-inotifypropertychanged/

Related

How would I open a manpage in GridView by clicking on a WinForms button?

I am trying to open up a manpage (Get-Help alias) when I click on a button using Powershell and WinForms.
I have a text box that allows you to input a cmdlet or help topic and when you press a button, it should open up the manpage documentation in GridView. Currently, it opens the GridView and grabs the correct help docs but something messes up along the way and I think it has to do with interpretation before it is passed off to GridView.
Here is what I have:
[void][Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
$form = New-Object Windows.Forms.Form
$input = New-Object Windows.Forms.TextBox
$input.Size = '100,20'
$input.Location = '10,20'
$button = New-Object Windows.Forms.Button
$button.Size = '100,20'
$button.Location = '10,60'
$button.Add_Click({
Invoke-Expression ("man " + ($global:input.Text)) | Out-GridView
})
$form.Controls.AddRange(#($input, $button)
$form.Add_Shown({$form.Activate()})
$form.ShowDialog()
What happens is the GridView opens but the title shows Invoke-Expression ("man " + ($input.Text)) | Out-GridView and the contents are the generic default information for manpages.
I tried to attach the Invoke-Expression to a variable and then pipe the variable out to GridView. I tried to set (Get-Help ($input.Text)) to a variable and then pipe it to GridView. I even tried to initialize the $input.Text property by putting $input.Text = '' just after the $input.Location property.
I really think its how the Powershell engine is interpreting the expression but I don't know how to tell it to work the way I am wanting it to.
What am I doing wrong here?
EDIT: Ok, I just realized something. I think the $input.Text property is not getting populated correctly.
What I did was added [Windows.Forms.MessageBox]::Show($input.Text) in the Click event for $button and commented out the Invoke-Expression. What it should do is open a message box and place within it what is typed in the text box ($input.Text). The message box is blank. I'm thinking that it may have to do with scoping but the $input.Text should be $global and accessible from within the Click event on the button control item.
I messed around with it after typing that last paragraph and I realized that the $input.Text property is populated correctly and is accessible in the $global scope. What I did was add [Windows.Forms.MessageBox]::Show($input.Text) at the very end of the script (after $form.ShowDialog()) and it showed exactly what I typed in the text box.
So, why is it that I can't see the $input text box properties? I haven't had this issue with some of the other WinForms apps I have built in Powershell.
Thanks for the insight.
$input is an automatic variable used in the context of the Powershell pipeline, which is why it was empty. Powershell populates it by itself, therefore overwriting anything you put in it. Rename $input to any other available name and it should work.
E.g.:
[void][Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
$form = New-Object Windows.Forms.Form
$box = New-Object Windows.Forms.TextBox
$box.Size = '100,20'
$box.Location = '10,20'
$button = New-Object Windows.Forms.Button
$button.Size = '100,20'
$button.Location = '10,60'
$button.Add_Click({
[Windows.Forms.MessageBox]::Show($box.Text)
Invoke-Expression ("man " + ($box.Text)) | Out-GridView
})
$form.Controls.AddRange(#($box, $button))
$form.Add_Shown({$form.Activate()})
$form.ShowDialog()

Dynamically add and remove content in GUI form

I am trying to create in PowerShell a GUI window with dynamic content. I need to:
create a window with a random count of buttons (or other clickable items)
after a click button and related text label will be removed from the window
IMPORTANT: I cannot use a list or datagrid.
I have the following code but it still returns only last item value.
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$form = New-Object System.Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(400,400)
$Array = New-Object System.Collections.ArrayList
$Array.add('AAA')
$Array.add('BBB')
$Array.add('CCC')
$Array.add('DDD')
foreach ($item in $Array) {
New-Variable -Force -Name "button$membershipCount" -Value (New-Object System.Windows.Forms.Button)
$thisButton = Get-Variable -ValueOnly -Include "button$membershipCount"
$thisButton.Location = New-Object System.Drawing.Size(175,(35+26*$membershipCount))
$thisButton.Size = New-Object System.Drawing.Size(250,23)
$thisButton.Text = $item
$thisButton.Add_Click({(Write-Host $thisButton.Text | Out-Null)})
$form.Controls.Add($CHANGEButton)
}
I also tried Invoke-Expression, but it doesn't return expected results:
Invoke-Expression -Command "`$thisButton.Add_Click({`$x=`"$($item)`";`write-host $x})"
Or any better idea how can I get details which button was clicked since the number of buttons is random?
The problem is that the scriptblock inside add_Click() now contains a reference to $thisbutton, which at runtime will have been replaced with the last value in the foreach loop - this is expected behavior.
You can do 1 of 2 things here.
1. Capture the $thisButton.Text (or $item) value in a closure:
# Piping to Out-Null has zero effect here, just remove it
$thisButton.Add_Click({Write-Host $thisButton.Text}.GetNewClosure())
2. Use the event arguments to determine which button was clicked at runtime:
$thisButton.Add_Click({param($Sender,$EventArgs) Write-Host $Sender.Text})

PictureBox in the Background?

I want to display a GIF as background in my Form that I created with PowerShell.
I already figured out how to display the GIF and it works great, but of course all my Buttons and Textboxes etc. disappear behind it.
Background images are assigned via the BackgroundImage property of the form:
Add-Type -Assembly System.Windows.Forms
$form = New-Object Windows.Forms.Form
$img = [Drawing.Image]::FromFile('C:\path\to\your.gif')
$form.BackgroundImage = $img
$form.BackgroundImageLayout = 'Tile'
$form.ShowDialog()
See here for more information about using forms in PowerShell.
For displaying an animated GIF (you should've mentioned that in your question) it seems a PictureBox element is required. You can get it behind other elements by adding it after other elements (new elements are placed behind/below existing elements):
$img = [Drawing.Image]::FromFile('C:\path\to\your.gif')
$picbox = New-Object Windows.Forms.PictureBox
$picbox.Width = $img.Size.Width
$picbox.Height = $img.Size.Height
$picbox.Image = $img
$form.Controls.Add($otherElement)
$form.Controls.Add($yetAnotherElement)
$form.Controls.Add($picbox)
or by sending it to the back via the SendToBack() method:
$img = [Drawing.Image]::FromFile('C:\path\to\your.gif')
$picbox = New-Object Windows.Forms.PictureBox
$picbox.Width = $img.Size.Width
$picbox.Height = $img.Size.Height
$picbox.Image = $img
$form.Controls.Add($picbox)
$form.Controls.Add($otherElement)
$form.Controls.Add($yetAnotherElement)
$picbox.SendToBack()

Use of CheckedChanged using Windows Forms and Powershell assistance

I'm using WinForms with PowerShell. In my tool, I'd like a checkbox that when checked, will display a message next to it, and when unchecked, it will remove the message.
I've gotten this far (I'm sure there's a much better way to do it). This makes the message pop-up, but it doesn't go away when you uncheck the box. Any help would be appreciated. Thanks!
$Checkbox_Errors.Add_CheckStateChanged({ ### Checkbox_Errors is the name of the checkbox
if ($Checkbox_Errors.Checked -eq $true)
{
$ErrorWarning1 = New-Object System.Windows.Forms.Label
$ErrorWarning1.Text = "WARNING: May take 3-5 Minutes" ### When checked, this is what it should display
$ErrorWarning1.ForeColor = "Red"
$ErrorWarning1.AutoSize = $True
$ErrorWarning1.Location = new-object System.Drawing.Point(170,13)
$groupbox.Controls.Add($ErrorWarning1)
}
})
$Checkbox_Errors.Add_CheckStateChanged({
if ($Checkbox_Errors.Unchecked -eq $true)
{
$ErrorWarning1 = New-Object System.Windows.Forms.Label
$ErrorWarning1.Text = "" ### I attempted this, where it would re-write
$ErrorWarning1.ForeColor = "Red"
$ErrorWarning1.AutoSize = $True
$ErrorWarning1.Location = new-object System.Drawing.Point(170,13)
$groupbox.Controls.Add($ErrorWarning1)
}
})
It would be simpler to create the label with the rest of your form elements. If you're using a designer, you can just drag it on with the rest of the controls. Then set the label's Visible property to $False initially to hide it.
$ErrorWarning1 = New-Object System.Windows.Forms.Label
$ErrorWarning1.Text = "WARNING: May take 3-5 Minutes"
$ErrorWarning1.ForeColor = "Red"
$ErrorWarning1.AutoSize = $True
$ErrorWarning1.Location = new-object System.Drawing.Point(170,13)
$ErrorWarning1.Visible = $False # This line hides the label initially
$groupbox.Controls.Add($ErrorWarning1)
Now in your event handler, instead of generating the label, just show or hide it based on the state of the checkbox:
$ErrorWarning1.Visible = $Checkbox_Errors.Checked
The label will always exist, but it will only be visible when the checkbox is checked.

call OpenFileDialog from powershell

When I run the following, PowerShell hangs waiting for the dialog to close, even though the dialog is never displayed:
[void] [Reflection.Assembly]::LoadWithPartialName( 'System.Windows.Forms' )
$d = New-Object Windows.Forms.OpenFileDialog
$d.ShowDialog( )
Calling ShowDialog on a Windows.Forms.Form works fine. I also tried creating a Form and passing it as the parent to $d.ShowDialog, but the result was no different.
I was able to duplicate your problem and found a workaround. I don't know why this happens, but it has happened to others.
If you set the ShowHelp property to $true, you will get the dialog to come up properly.
Example:
[void] [Reflection.Assembly]::LoadWithPartialName( 'System.Windows.Forms' )
$d = New-Object Windows.Forms.OpenFileDialog
$d.ShowHelp = $true
$d.ShowDialog( )
Good Luck!
It appears to me that the dialog is actually opening just fine, but it's behind the powershell console window. Unfortunately it doesn't show in the taskbar, so there's no indication that it's there unless you move the powershell window or Alt+Tab. It also appears that the ShowHelp workaround didn't have any effect for me.
EDIT Here's a way to do do it using your secondary-form idea. The basic idea is to create a new form which opens the OpenFileDialog from inside its Shown event. The key is calling Activate on the form before opening the dialog, so that the form comes to the front and the dialog appears. I moved the form offscreen by setting the Location to an offscreen value, but you could alternatively set Form.Visible = $false from inside the Shown event.
[void] [Reflection.Assembly]::LoadWithPartialName( 'System.Windows.Forms' )
$ofn = New-Object System.Windows.Forms.OpenFileDialog
$outer = New-Object System.Windows.Forms.Form
$outer.StartPosition = [Windows.Forms.FormStartPosition] "Manual"
$outer.Location = New-Object System.Drawing.Point -100, -100
$outer.Size = New-Object System.Drawing.Size 10, 10
$outer.add_Shown( {
$outer.Activate();
$ofn.ShowDialog( $outer );
$outer.Close();
} )
$outer.ShowDialog()
Apparently this has something to do with Multi-Threaded Apartment (MTA) mode.
It appears to work fine in Single-Threaded Apartment (-STA) mode.
See also: Could you explain STA and MTA?

Resources