Powershell Form GUI with draggable objects - forms

I want to create a PowerShell based GUI with windows forms to display items in different containers and enable the user to drag items (control objects) from one container to another. Because of some bad flickering I copied the answer from this question and converted the code into PowerShell:
How to double buffer .NET controls on a form?
The problem is, that the objects are "smearing" on the form when I start dragging them and I can't find how to solve this.
class Dictionary : System.Collections.DictionaryBase
{
Dictionary() {}
[Bool]Exists([System.Object] $Key) {return ($Key -in $this.Keys) }
}
function SetDoubleBuffered()
{
param([System.Windows.Forms.Control] $TargetControl)
[System.Reflection.PropertyInfo] $DoubleBufferedProp = [System.Windows.Forms.Control].GetProperty("DoubleBuffered", [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance)
$DoubleBufferedProp.SetValue($TargetControl, $True, $Null)
}
function Button_MouseDown()
{
[CmdletBinding()]
param(
[parameter(Mandatory=$True)][System.Object] $Sender,
[parameter(Mandatory=$True)][System.EventArgs] $EventArguments
)
$Sender.Tag.DragStart = $False
$Sender.Tag.StartX = $EventArguments.X
$Sender.Tag.StartY = $EventArguments.Y
}
function Button_MouseUp()
{
[CmdletBinding()]
param(
[parameter(Mandatory=$True)][System.Object] $Sender,
[parameter(Mandatory=$True)][System.EventArgs] $EventArguments
)
$Sender.Tag.DragStart = $False
$Sender.Tag.StartX = 0
$Sender.Tag.StartY = 0
}
function Button_MouseMove()
{
[CmdletBinding()]
param(
[parameter(Mandatory=$True)][System.Object] $Sender,
[parameter(Mandatory=$True)][System.EventArgs] $EventArguments
)
if ($EventArguments.Button.value__ -gt 0)
{
if (-not $Sender.Tag.DragStart)
{
if ([System.Math]::sqrt([System.Math]::Pow($Sender.Tag.StartX - $EventArguments.X, 2) + [System.Math]::Pow($Sender.Tag.StartY - $EventArguments.Y, 2)) -ge 10)
{
$Sender.Tag.DragStart = $true
}
}
else
{
$Sender.Left = $Sender.Left + ($EventArguments.X - $Sender.Tag.StartX)
$Sender.Top = $Sender.Top + ($EventArguments.Y - $Sender.Tag.StartY)
}
}
else
{
$Sender.Tag.DragStart = $False
$Sender.Tag.StartX = 0
$Sender.Tag.StartY = 0
}
}
function OpenForm()
{
$Form = [System.Windows.Forms.Form]::new()
$Form.Text = "DragTest"
$Form.Width = 900
$Form.Height = 900
$Button = [System.Windows.Forms.Button]::new()
$Button.Left = 10
$Button.Top = 30
$Button.Height = 20
$Button.Width = 100
$Button.Text = "MyButton"
$Button.Name = "SomeButton"
$Button.Tag = [Dictionary]::new()
$Button.Add_MouseDown({Button_MouseDown -Sender $Button -EventArguments $_})
$Button.Add_MouseUp({Button_MouseUp -Sender $Button -EventArguments $_})
$Button.Add_MouseMove({Button_MouseMove -Sender $Button -EventArguments $_})
$Form.Controls.Add($Button)
SetDoubleBuffered -TargetControl $Form
SetDoubleBuffered -TargetControl $Button
$Form.ShowDialog() | Out-Null
}
cls
OpenForm

Related

No PowerShell Form datagrid selected

I have created a script that must select all the items in a powershell form, based on a variable. If the variable $bolCheckAllNumberedItems has the value '$False, then the grid should not be selected, in case the variable $bolCheckAllNumberedItems has the value '$True', the whole grid should be selected.
The code I have:
Clear-Host
$bolCheckAllNumberedItems = $True
$frmTest = New-Object 'System.Windows.Forms.Form'
$btnOk = New-Object 'System.Windows.Forms.Button'
$chkAllItems = New-Object 'System.Windows.Forms.CheckBox'
$DataGridForTest = New-Object 'System.Windows.Forms.DataGridView'
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
$frmTest.Controls.Add($btnOk)
$frmTest.Controls.Add($chkAllItems)
$frmTest.Controls.Add($DataGridForTest)
$frmTest.AutoScaleDimensions = New-Object System.Drawing.SizeF(6, 13)
$frmTest.AutoScaleMode = 'Font'
$frmTest.ClientSize = New-Object System.Drawing.Size(284, 261)
$frmTest.Name = 'frmTest'
$frmTest.StartPosition = 'CenterScreen'
$frmTest.Text = 'Test'
#
# btnOk
#
$btnOk.Location = New-Object System.Drawing.Point(186, 220)
$btnOk.Name = 'btnOk'
$btnOk.Size = New-Object System.Drawing.Size(75, 23)
$btnOk.TabIndex = 1
$btnOk.Text = '&Ok'
$btnOk.UseVisualStyleBackColor = $True
$btnOk.add_Click($btnOk_Click)
#
# chkAllItems
#
$chkAllItems.Location = New-Object System.Drawing.Point(22, 22)
$chkAllItems.Name = 'chkAllItems'
$chkAllItems.Size = New-Object System.Drawing.Size(104, 24)
$chkAllItems.TabIndex = 1
$chkAllItems.Text = 'Select all items'
$chkAllItems.UseVisualStyleBackColor = $True
$chkAllItems.add_CheckedChanged($chkAllItems_CheckedChanged)
#
# DataGridForTest
#
$DataGridForTest.ColumnHeadersHeightSizeMode = 'AutoSize'
$DataGridForTest.Location = New-Object System.Drawing.Point(22, 52)
$DataGridForTest.Name = 'DataGridForTest'
$DataGridForTest.Size = New-Object System.Drawing.Size(240, 150)
$DataGridForTest.TabIndex = 0
$DataGridForTest.TabStop = $False
$DataGridForTest.ColumnCount = 1
$DataGridForTest.ColumnHeadersVisible = $true
$DataGridForTest.Columns[0].Name = "Number"
$DataGridForTest.Columns[0].width = 100
$DataGridForTest.ScrollBars = 'Vertical'
$DataGridForTest.ReadOnly = $True
$DataGridForTest.AllowUserToAddRows = $False
$DataGridForTest.AllowUserToDeleteRows = $False
$DataGridForTest.AllowUserToResizeColumns = $False
$DataGridForTest.AllowUserToResizeRows = $False
$DataGridForTest.SelectionMode = "FullRowSelect"
$DataGridForTest.MultiSelect = $true
#----------------------------------------------
$ArrayNumbers = #("one","two","three","for","five","six","seven","eight","nine","ten")
[system.collections.arraylist]$ArrayWithHeader = #()
ForEach ($object in $ArrayNumbers)
{
$value = [pscustomobject]#{'Number' = $object}
$ArrayWithHeader.Add($value) | Out-Null
$value = $null
}
$ArrayWithHeader | foreach {$DataGridForTest.Rows.Add($_."Number") | Out-Null }
if($bolCheckAllNumberedItems)
{
$chkAllItems.Checked = $true
$DataGridForTest.SelectAll()
}
$chkAllItems.Add_Click({
if($chkAllItems.Checked)
{
$DataGridForTest.SelectAll()
}
else
{
$DataGridForTest.ClearSelection()
}
})
$btnOk.add_Click({$frmTest.Close()})
[void]$frmTest.ShowDialog()
What is wrong with my code? Help is appreciated and with kind regards,
The Sting Pilot
I think the easiest way of doing this is to start your script with a small function you can call inside the checkbox Click() event aswell as in an added form event called Add_Shown()
function Select-Grid ([bool]$selectAll) {
$chkAllItems.Checked = $selectAll
if ($selectAll) {
$DataGridForTest.SelectAll()
}
else {
$DataGridForTest.ClearSelection()
}
}
This way you can remove these lines:
if($bolCheckAllNumberedItems)
{
$chkAllItems.Checked = $true
$DataGridForTest.SelectAll()
}
and instead write
# call the helper function with the Checked value of the $chkAllItems checkbox
$chkAllItems.Add_Click({ Select-Grid $chkAllItems.Checked })
# this is an added event that fires when the form is first shown
$frmTest.Add_Shown( { Select-Grid $bolCheckAllNumberedItems } )
[void]$frmTest.ShowDialog()
# very important! dispose of the form when you are finished with it
$frmTest.Dispose()
An alternative approach is to again remove these lines:
if($bolCheckAllNumberedItems)
{
$chkAllItems.Checked = $true
$DataGridForTest.SelectAll()
}
and change the event $chkAllItems.Add_Click() to use a different event:
$chkAllItems.Add_CheckedChanged({
if($chkAllItems.Checked) {
$DataGridForTest.SelectAll()
}
else {
$DataGridForTest.ClearSelection()
}
})
Activate that event when the form is first shown and finish the code:
$frmTest.Add_Shown( { $chkAllItems.Checked = $bolCheckAllNumberedItems } )
[void]$frmTest.ShowDialog()
# and not forget to get rid of the form from memory
$frmTest.Dispose()
Using the CheckedChanged() method does not need an extra helper function as with the first approach.

Populate ComboBox2 depending on ComboBox1 selection

Few days of searching and trying multiple options, but nothing is working actually. With the previous code posted.
I've imported all my objects from XAML (all set in variables), I don't know if that would be a problem for that. I don't think since everything else seems to work properly.
I just want my Tab2CBB2 to show values depending on the selection of Tab1CBB1. Anyone could help ? (I haven't paste the entire code, neither the paths but you can probably help me with that). Note that those are two of my multiple tries. Thanks
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$xaml = #" ...
"#
#Read XAML (Parse it)
$reader=(New-Object System.Xml.XmlNodeReader $XAML)
$Window=[Windows.Markup.XamlReader]::Load( $reader )
$listQA14 = Get-ChildItem $pathQA14 -name
$listQA15 = Get-ChildItem $pathQA15 -name
$listDBQA = Get-ChildItem $pathDBQA -name
$Tab1CBB1_SelectedIndexChanged= {
$Tab1CBB2.Items.Clear() # Clear the list
$Tab1CBB2.Text = $null # Clear the current entry
Switch ($Tab1CBB1.Text) {
'QA14' {
$ListQA14 | ForEach { $Tab1CBB2.Items.Add($_) }
}
'QA15' {
$ListQA15 | ForEach { $Tab1CBB2.Items.Add($_) }
}
'ARIELDBQA' {
$ListDBQA | ForEach { $Tab1CBB2.Items.Add($_) }
}
}
}
$Tab1CBB1.add_SelectedIndexChanged($Tab1CBB1_SelectedIndexChanged)
#Displays the Window
$Window.ShowDialog() | out-null
Here is another option tried :
#ComboBox1------
$ItemsCBB1 = #('ARIELDBQA','QA14','QA15')
foreach ($Item in $ItemsCBB1) {
$Tab1CBB1.Items.Add($Item)
}
#ComboBox2-------
if ($Tab1CBB1.SelectedItem -eq 'QA14') {
$Tab1CBB2.Items.Clear()
foreach ($Serie in $listQA14) {
$Tab1CBB2.Items.Add($Serie)
}
}
elseif ($Tab1CBB1.SelectedItem -eq 'QA15') {
$Tab1CBB2.Items.Clear()
foreach ($Serie in $listQA15) {
$Tab1CBB2.Items.Add($Serie)
}
}
elseif ($Tab1CBB1.SelectedItem -eq 'listDBQA') {
$Tab1CBB2.Items.Clear()
foreach ($Serie in $listDBQA) {
$Tab1CBB2.Items.Add($Serie)
}
}
Tried this also : $Selection = $Tab1CBB1.SelectedItem.ToString()
Variable $selection put after 'if', but not working
Note that when I indicate what the current selection would be, it is working properly. The problem seems to come from 'recording' the selection a the time of clicking... Thnaks !
Basically, all you have to do is inside the $Tab1CBB1_SelectedIndexChanged scriptblock use the various lists with script scoping.
Without that, the variables are unknown inside the script block.
$Tab1CBB1_SelectedIndexChanged = {
$Tab1CBB2.Items.Clear() # Clear the list
$Tab1CBB2.Text = $null # Clear the current entry
switch ($Tab1CBB1.Text) {
'QA14' { $script:ListQA14 | ForEach-Object { $Tab1CBB2.Items.Add($_) } }
'QA15' { $script:ListQA15 | ForEach-Object { $Tab1CBB2.Items.Add($_) } }
'ARIELDBQA' { $script:ListDBQA | ForEach-Object { $Tab1CBB2.Items.Add($_) } }
}
}
Another method could be to dynamically get the items to enter in the combobox, especially since these are file lists and can change while your form is being used:
$Tab1CBB1_SelectedIndexChanged = {
$Tab1CBB2.Items.Clear() # Clear the list
$Tab1CBB2.Text = $null # Clear the current entry
switch ($Tab1CBB1.Text) {
'QA14' { $path = $script:pathQA14 ; break }
'QA15' { $path = $script:pathQA15 ; break }
'ARIELDBQA' { $path = $script:pathDBQA }
}
Get-ChildItem -Path $path -Name | ForEach-Object { $Tab1CBB2.Items.Add($_) }
}
A simple example of what you seem to be after is something like this.
### Powershell populate ComboBox 2 based on ComboBox 1 Selection
Add-type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$labelDoubleClickOnAnyItem = New-Object 'System.Windows.Forms.Label'
$listbox2 = New-Object 'System.Windows.Forms.ListBox'
$listbox1 = New-Object 'System.Windows.Forms.ListBox'
$buttonOK = New-Object 'System.Windows.Forms.Button'
$form1_Load={
$items=1..9 |
ForEach-Object {"List item $_"}
$listbox1.Items.AddRange($items)
}
$listbox1_MouseDoubleClick=[System.Windows.Forms.MouseEventHandler]{
$listbox2.Items.Add($listbox1.SelectedItem)
$listbox1.Items.Remove($listbox1.SelectedItem)
}
$listbox2_MouseDoubleClick=[System.Windows.Forms.MouseEventHandler]{
$listbox1.Items.Add($listbox2.SelectedItem)
$listbox2.Items.Remove($listbox2.SelectedItem)
}
$form1.Controls.Add($labelDoubleClickOnAnyItem)
$form1.Controls.Add($listbox2)
$form1.Controls.Add($listbox1)
$form1.Controls.Add($buttonOK)
$form1.AcceptButton = $buttonOK
$form1.ClientSize = '378, 388'
$form1.FormBorderStyle = 'FixedDialog'
$form1.MaximizeBox = $False
$form1.MinimizeBox = $False
$form1.Name = 'form1'
$form1.StartPosition = 'CenterScreen'
$form1.Text = 'Form'
$form1.add_Load($form1_Load)
$labelDoubleClickOnAnyItem.Font = 'Microsoft Sans Serif, 9.75pt, style=Bold, Italic'
$labelDoubleClickOnAnyItem.Location = '13, 13'
$labelDoubleClickOnAnyItem.Name = 'labelDoubleClickOnAnyItem'
$labelDoubleClickOnAnyItem.Size = '353, 41'
$labelDoubleClickOnAnyItem.TabIndex = 3
$labelDoubleClickOnAnyItem.Text = 'Double click on any item to move it from one box to the other.'
$listbox2.FormattingEnabled = $True
$listbox2.Location = '200, 67'
$listbox2.Name = 'listbox2'
$listbox2.Size = '166, 277'
$listbox2.Sorted = $True
$listbox2.TabIndex = 2
$listbox2.add_MouseDoubleClick($listbox2_MouseDoubleClick)
$listbox1.FormattingEnabled = $True
$listbox1.Location = '12, 67'
$listbox1.Name = 'listbox1'
$listbox1.Size = '167, 277'
$listbox1.Sorted = $True
$listbox1.TabIndex = 1
$listbox1.add_MouseDoubleClick($listbox1_MouseDoubleClick)
$buttonOK.Anchor = 'Bottom, Right'
$buttonOK.DialogResult = 'OK'
$buttonOK.Location = '291, 353'
$buttonOK.Name = 'buttonOK'
$buttonOK.Size = '75, 23'
$buttonOK.TabIndex = 0
$buttonOK.Text = '&OK'
$form1.ShowDialog()
Or even this using just the ComboBox elements
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = New-Object System.Drawing.Point(498,459)
$Form.text = 'Form'
$Form.TopMost = $false
$Form.StartPosition = 'CenterScreen'
$ComboBox1 = New-Object system.Windows.Forms.ComboBox
$ComboBox1.text = ''
$ComboBox1.width = 216
$ComboBox1.height = 75
#('ComboxItem1','ComboxItem2','ComboxItem3') |
ForEach-Object {[void] $ComboBox1.Items.Add($_)}
$ComboBox1.location = New-Object System.Drawing.Point(31,41)
$ComboBox1.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
$ComboBox2 = New-Object system.Windows.Forms.ComboBox
$ComboBox2.text = ''
$ComboBox2.width = 213
$ComboBox2.height = 38
$ComboBox2.location = New-Object System.Drawing.Point(32,73)
$ComboBox2.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
Function Add-ItemToComboBox2
{
$ComboBox2.Items.Add($ComboBox1.SelectedItem)
$ComboBox2.Refresh()
}
$Form.controls.AddRange(
#(
$ComboBox1,
$ComboBox2
)
)
$ComboBox1.Add_SelectedIndexChanged({ Add-ItemToComboBox2 })
[void]$Form.ShowDialog()
You have not stated what UX/UI tool you are using to design and implement your
GUI. Yet, hard coding is also just a challenge. So consider using one of these for your UX/UI effort. Your UX/UI should just work, whether you have PowerShell code behind it (or any other language). Your PowerShell code should just work, regardless of the UX/UI that may be implemented. Your UX/UI code should be separate from your ops code. You call your ops code from the UX/UI.
Free:
https://poshgui.com
https://www.youtube.com/results?search_query=poshgui
https://visualstudio.microsoft.com/vs/community
Be sure to thoroughly read the licensing agreement for VS.
https://www.youtube.com/results?search_query=vs+2019+community+winforms
Purchase
https://ironmansoftware.com/psscriptpad
https://www.youtube.com/results?search_query=psscriptpad
https://ironmansoftware.com/powershell-pro-tools
https://www.youtube.com/results?search_query=powershell-pro-tools
https://www.sapien.com/software/powershell_studio
https://www.youtube.com/results?search_query=Sapien%27s+powershell+studio++winforms
https://visualstudio.microsoft.com/vs
https://www.youtube.com/results?search_query=vs+2019+community+winforms

Unable to get the selected item directly

I just learned about powershell gui programming. The following code can be executed successfully, but the result of the execution is not the text of the item in the list.
like this:
#{Name=a1; Status=True}
#{Name=a2; Status=False}
I need to output the text of the item directly.
a1
a2
I tried a lot of methods but no results
Thanks in advance
function Show-7_psf {
[void][reflection.assembly]::Load('System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
[void][reflection.assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$buttonOk = New-Object 'System.Windows.Forms.Button'
$checkedlistbox1 = New-Object 'System.Windows.Forms.CheckedListBox'
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
$csvtxt = #'
Name,Status
a1,True
a2,False
a3,False
'#
$form1_Load = {
$csv = ConvertFrom-Csv $csvtxt
$checkedlistbox1.DataSource = [System.Collections.ArrayList]$csv
$checkedlistbox1.DisplayMember = 'Name'
for ($i = 0; $i -lt $checkedlistbox1.Items.Count; $i++)
{
$state = if ($checkedlistbox1.Items[$i].Status -eq 'True') { $true }
else { $false }
$checkedlistbox1.SetItemChecked($i, $state)
}
}
$buttonOk_Click = {
$checkedlistbox1.CheckedItems | Write-Host
}
$Form_StateCorrection_Load=
{
#Correct the initial state of the form to prevent the .Net maximized form issue
$form1.WindowState = $InitialFormWindowState
}
$Form_Cleanup_FormClosed=
{
#Remove all event handlers from the controls
try
{
$buttonOk.remove_Click($buttonOk_Click)
$form1.remove_Load($form1_Load)
$form1.remove_Load($Form_StateCorrection_Load)
$form1.remove_FormClosed($Form_Cleanup_FormClosed)
}
catch { Out-Null <# Prevent PSScriptAnalyzer warning #> }
}
$form1.SuspendLayout()
$form1.Controls.Add($buttonOk)
$form1.Controls.Add($checkedlistbox1)
$form1.AutoScaleDimensions = '6, 13'
$form1.AutoScaleMode = 'Font'
$form1.ClientSize = '482, 262'
$form1.Name = 'form1'
$form1.StartPosition = 'CenterScreen'
$form1.Text = 'Form'
$form1.add_Load($form1_Load)
$buttonOk.Location = '357, 199'
$buttonOk.Name = 'buttonOk'
$buttonOk.Size = '67, 25'
$buttonOk.TabIndex = 1
$buttonOk.Text = 'OK'
$buttonOk.UseCompatibleTextRendering = $True
$buttonOk.UseVisualStyleBackColor = $True
$buttonOk.add_Click($buttonOk_Click)
$checkedlistbox1.CheckOnClick = $True
$checkedlistbox1.FormattingEnabled = $True
$checkedlistbox1.Location = '49, 30'
$checkedlistbox1.Name = 'checkedlistbox1'
$checkedlistbox1.Size = '177, 184'
$checkedlistbox1.TabIndex = 0
$checkedlistbox1.UseCompatibleTextRendering = $True
$form1.ResumeLayout()
#Save the initial state of the form
$InitialFormWindowState = $form1.WindowState
#Init the OnLoad event to correct the initial state of the form
$form1.add_Load($Form_StateCorrection_Load)
#Clean up the control events
$form1.add_FormClosed($Form_Cleanup_FormClosed)
#Show the Form
return $form1.ShowDialog()
}
Show-7_psf | Out-Null
Change this line
$checkedlistbox1.CheckedItems | Write-Host
To
$checkedlistbox1.CheckedItems.Name | Write-Host
This is because you are calling the object in the $csv array but not selecting the Property so you are returning the entire object in the array.
If you want any other property then replace with name
$buttonOk_Click = {
$checkedlistbox1.CheckedItems | Where-Object { $_."Name" | Write-Host }
}

Using variables for events on dynamically generated control

I have a function Add-TextBox, which creates a TextBox control, and assigns a block to it's event handler.
The problem I am facing is that the block is presumably being called with global namespace, and the variables declared inside the Add-TextBox function aren't accessible to it. How do I make them accessible?
Edit: added full code
Add-Type -AssemblyName System.Windows.Forms
Function Add-Control() {
Param (
[System.Windows.Forms.Form]$Form,
[string]$ControlType,
[System.Windows.Forms.Control]$AfterControl = $null,
[int]$Padding = 0
)
$control = New-Object System.Windows.Forms.$ControlType
$control.AutoSize = $true
$x = 5
$y = 5
if ($AfterControl) {
$y = $AfterControl.Location.Y + $AfterControl.Size.Height + $Padding
}
$control.Location = "5,$y"
$form.Controls.Add($control)
return $control
}
Function Add-TextBox() {
Param (
[System.Windows.Forms.Form]$Form,
[string]$Placeholder = "",
[System.Windows.Forms.Control]$AfterControl = $null,
[int]$Padding = 0
)
$control = Add-Control -Form $Form -ControlType "TextBox" -AfterControl $AfterControl -Padding $Padding
$control.Add_GotFocus({
Write-Host "Placeholder is null: $($Placeholder -eq $null)"
if ($this.Text -eq $Placeholder) {
$this.ForeColor = "Black"
$this.Text = ""
}
})
$control.Add_LostFocus({
if ($this.Text -eq "") {
$this.ForeColor = "Darkgray"
$this.Text = $Placeholder
}
})
return $control
}
$form = New-Object system.Windows.Forms.Form
$textbox = Add-TextBox -Form $form -Placeholder "(XXXXXX, npr. 012345)"
$form.ShowDialog()
Seems I can use .GetNewClosure() on script blocks to capture the local variables and retain them.
Add-Type -AssemblyName System.Windows.Forms
Function Add-Control() {
Param (
[System.Windows.Forms.Form]$Form,
[string]$ControlType,
[System.Windows.Forms.Control]$AfterControl = $null,
[int]$Padding = 0
)
$control = New-Object System.Windows.Forms.$ControlType
$control.AutoSize = $true
$x = 5
$y = 5
if ($AfterControl) {
$y = $AfterControl.Location.Y + $AfterControl.Size.Height + $Padding
}
$control.Location = "5,$y"
$form.Controls.Add($control)
return $control
}
Function Add-TextBox() {
Param (
[System.Windows.Forms.Form]$Form,
[string]$Placeholder = "",
[System.Windows.Forms.Control]$AfterControl = $null,
[int]$Padding = 0
)
$control = Add-Control -Form $Form -ControlType "TextBox" -AfterControl $AfterControl -Padding $Padding
$control.Add_GotFocus({
if ($this.Text -eq $Placeholder) {
$this.ForeColor = "Black"
$this.Text = ""
}
}.GetNewClosure()) # Here...
$control.Add_LostFocus({
if ($this.Text -eq "") {
$this.ForeColor = "Darkgray"
$this.Text = $Placeholder
}
}.GetNewClosure()) # And here.
return $control
}
$form = New-Object system.Windows.Forms.Form
$textbox = Add-TextBox -Form $form -Placeholder "(XXXXXX, npr. 012345)"
$form.ShowDialog()

Powershell - DataViewGrid - Column Autosize

I am a complete novice when it comes to .NET and powershell and was wondering if you guys could assist. I am generating a Data Grid from a .CSV on a form and would like the grid to auto size columns accordingly. Also if I could lock the columns/rows from user adjustment that would be amazing.
Clear-Host
Function Populate-CycleCountDataGrid {
$InventoryListArray = New-Object System.Collections.ArrayList
$Script:InventoryList = #(Import-CSV C:\File.csv | Write-Output)
$InventoryListArray.AddRange($Script:InventoryList)
$CycleCountDataGrid.DataSource = $InventoryListArray
}
Function GenerateForm {
$objForm = New-Object System.Windows.Forms.Form
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
$RefreshButton_Click = {
Populate-CycleCountDataGrid
}
# Form Setup
#*******************************************************************************************\
$OnLoadForm_StateCorrection= { $objForm.WindowState = $InitialFormWindowState }
$objForm.Text = "CycleCount"
$objForm.Name = "CycleCount"
$objForm.Size = New-Object System.Drawing.Size(600,480)
$objForm.StartPosition = 0
$objForm.AutoSize = $False
$objForm.MinimizeBox = $False
$objForm.MaximizeBox = $False
$objForm.WindowState = "Normal"
# DataGrid
#*******************************************************************************************\
$CycleCountDataGrid = New-Object System.Windows.Forms.DataGrid
$CycleCountDataGrid.Location = New-Object System.Drawing.Size(0,0)
$CycleCountDataGrid.Size = New-Object System.Drawing.Size(592,400)
$CycleCountDataGrid.AutoSize = $False
$CycleCountDataGrid.AllowSorting = $False
$CycleCountDataGrid.ReadOnly = $True
$CycleCountDataGrid.CaptionText = "Inventory List"
$CycleCountDataGrid.HeaderFont = New-Object System.Drawing.Font("Verdana",8.25,1,3,0)
$CycleCountDataGrid.HeaderForeColor = [System.Drawing.Color]::FromArgb(255,0,0,0)
$CycleCountDataGrid.Font = New-Object System.Drawing.Font("Verdana",8.25,[System.Drawing.FontStyle]::Bold)
$CycleCountDataGrid.BackColor = [System.Drawing.Color]::FromArgb(255,0,160,250)
$CycleCountDataGrid.AlternatingBackColor = [System.Drawing.Color]::FromArgb(255,133,194,255)
$CycleCountDataGrid.Name = "CycleCountDataGrid"
$CycleCountDataGrid.DataBindings.DefaultDataSourceUpdateMode = 0
$objForm.Controls.Add($CycleCountDataGrid)
#*******************************************************************************************/
# Refresh Button
#*******************************************************************************************\
$RefreshButton = New-Object System.Windows.Forms.Button
$RefreshButton.Location = New-Object System.Drawing.Size(0,400)
$RefreshButton.Size = New-Object System.Drawing.Size(590,45)
$RefreshButton.Name = "RefreshButton"
$RefreshButton.Text = "Refresh"
$RefreshButton.UseVisualStyleBackColor = $True
$RefreshButton.add_Click($RefreshButton_Click)
$RefreshButton.DataBindings.DefaultDataSourceUpdateMode = 0
$objForm.Controls.Add($RefreshButton)
#*******************************************************************************************/
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
$objForm.FormBorderStyle = 'Fixed3D'
$objForm.MaximizeBox = $False
$objForm.Add_FormClosing([System.Windows.Forms.FormClosingEventHandler]{
if ($objForm.DialogResult -eq "Cancel") {}
})
$InitialFormWindowState = $objForm.WindowState
$objForm.add_Load($OnLoadForm_StateCorrection)
$objForm.ShowDialog()
#*******************************************************************************************/
}
GenerateForm
Add the following code:
$CycleCountDataGrid.Columns | Foreach-Object{
$_.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::AllCells
}
Change your control to a system.windows.forms.datagridview rather than just a datagrid. Then you have access to $CycleCountDataGrid.columns
Each column has a width property. The answer above will try to autosize each column but you could specify each one if you like.
$CycleCountDatarid.columns[0].width = 200
100 is the default
The secret to Autosizing a Windows.Forms.Datagrid is that it has a private method 'ColAutoResize' you can invoke with Reflection:
Function AutoResizeColumns([System.Windows.Forms.DataGrid] $dg1){
[System.Reflection.BindingFlags] $F = 'static','nonpublic','instance'
$ColAutoResizeMethod = $dg1.GetType().GetMethod('ColAutoResize', $F)
If($ColAutoResizeMethod) {
For ([int]$i = $dg1.FirstVisibleColumn; $i -lt $dg1.VisibleColumnCount; $i++){
$ColAutoResizeMethod.Invoke($dg1, $i) | Out-Null
}
}
}
Once you have that function, you can add it to the DataGrid's VisibleChanged and DataSourceChanged events so drawing and refreshing the DataGrid will invoke AutoResizeColumns:
$objForm.Controls["CycleCountDataGrid"].add_DatasourceChanged({ AutoResizeColumns $objForm.Controls["CycleCountDataGrid"] } )
$objForm.Controls["CycleCountDataGrid"].add_VisibleChanged({ AutoResizeColumns $objForm.Controls["CycleCountDataGrid"] } )
$objForm.ShowDialog() | Out-Null
There's probably a cleaner way to do this, but it's working for me.