Is such a creation of objects in a loop correct? I do it this way and it works. But maybe I'm wrong and there is a more literate way. Thank you.
0..10 | ForEach-Object {
New-Variable "Button$_" -EA 0
(Get-Variable "Button$_").Value = New-Object System.Windows.Forms.Button
$Button = (Get-Variable "Button$_").Value
$Button.FlatStyle = 'Flat'
...
}
Editing. I mean the correctness of this part:
New-Variable "Button$_" -EA 0
(Get-Variable "Button$_").Value = New-Object System.Windows.Forms.Button
Assuming you really want to create distinct variables $Button1, $Button2, ... rather than storing your button objects in a single array variable:
New-Variable "Button$_" -EA 0
(Get-Variable "Button$_").Value = New-Object System.Windows.Forms.Button
works, but can more simply and more efficiently be written as:
Set-Variable "Button$_" (New-Object System.Windows.Forms.Button)
Note: The "..." around Button$_ isn't strictly necessary here, but it makes the intent clearer.
or, in PSv5+:
Set-Variable "Button$_" ([System.Windows.Forms.Button]::new())
If you want to obtain a reference to the newly created variable object at the same time, using -PassThru:
$buttonVar = Set-Variable -PassThru "Button$_" ([System.Windows.Forms.Button]::new())
$buttonVar.Value.FlatStyle = 'Flat'
Alternatively, you can store the button object directly in an aux. variable with a fixed name:
Set-Variable "Button$_" ($button = [System.Windows.Forms.Button]::new())
$button.FlatStyle = 'Flat'
Note how the variable assignment ($button = ...) is part of the constructor expression.
I would rather use a collection to store button attributes and loop through it to create one by one from that.
Then you have full control on the attributes of the button that you want to create.
$Buttons = #(
#{ Name='ABC'; Val=100; FlatStye='Flat'; Xpos=35; Ypos=30}
#{ Name='EFG'; Val=101; FlatStye='Flat'; Xpos=35; Ypos=60 }
#{ Name='XYZ'; Val=102; FlatStye='Popup'; Xpos=35; Ypos=90 }
#{ Name='MNL'; Val=102; FlatStye='Popup'; Xpos=35; Ypos=120 }
)
Function Generate-Form {
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# prepare Form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "My Form"
$Form.Size = New-Object System.Drawing.Size(400,400)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
# Add Buttons from collection
foreach ($btn in $Buttons) {
$Button = [System.Windows.Forms.Button] #{
FlatStyle = $btn.FlatStye
Name = $btn.Name
Text = $btn.Name
Location = New-Object System.Drawing.Size($btn.Xpos, $btn.Ypos )
Size = New-Object System.Drawing.Size(220,25)
}
#Add Button event
$Button.Add_Click({
[System.Windows.Forms.MessageBox]::Show($Button.Text , "My Dialog Box")
})
$Form.Controls.Add($Button)
}
#Show the Form
$form.ShowDialog()| Out-Null
} #End Function
Generate-Form;
Related
I am trying to load the contents of a .csv table into a DataGridView. In the following code it is loaded at two point with the same method: after a button press and in the main routine.
The one in the main routine works, but the button doesn't work.
(The DataGrid just goes blank after pressing the button, so it apprears it's not a scope issue).
#############################################
#Functions
#############################################
function SelectCsvFile () {
Add-Type -AssemblyName System.Windows.Forms
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.Title = "Datei auswählen"
$OpenFileDialog.InitialDirectory = Get-Location
$OpenFileDialog.filter = 'Tabelle (*.csv)|*.csv'
$SelectedFile=$OpenFileDialog.ShowDialog()
If ($SelectedFile -eq "Cancel") {
$result = $null
}
else {
$result= new-object System.Collections.ArrayList
$data=#(import-csv $OpenFileDialog.FileName -Delimiter $ListImportDelimiter)
$result.AddRange($data)
}
$OpenFileDialog.Dispose()
return $result
}
#############################################
#Main Routine
#############################################
# Init PowerShell Gui
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
# Create a new form
$ManageTeamsForm = New-Object system.Windows.Forms.Form
# Define the size, title and background color
$ManageTeamsForm.ClientSize = New-Object System.Drawing.Point(1000,700)
#DataGrid
$DataGrid = new-object System.windows.forms.DataGridView
$DataGrid.width = 800
$DataGrid.height = 500
$DataGrid.location = New-Object System.Drawing.Point(37,80)
#loades testing csv on startup
$result= new-object System.Collections.ArrayList
$data=#(import-csv "./testfile.csv" -Delimiter $ListImportDelimiter)
$result.AddRange($data)
$DataGrid.DataSource = $result
#Select File Button
$SelectCsvFileButton = New-Object System.Windows.Forms.Button
$SelectCsvFileButton.Location = New-Object System.Drawing.Size(100,35)
$SelectCsvFileButton.Size = New-Object System.Drawing.Size(120,23)
$SelectCsvFileButton.Text = "Open CSV"
$SelectCsvFileButton.Add_Click({
$DataGrid.DataSource =SelectCsvFile;
ForEach ($row in $DataGrid.DataSource ){ #Testing if DataSource successfully changes
write-host $row
}
})
$ManageTeamsForm.controls.AddRange(#($SelectCsvFileButton,$DataGrid))
[void]$ManageTeamsForm.ShowDialog()
Your code is correct the only issue is that the output from your function SelectCsvFile is being enumerated, your function is no longer outputting an ArrayList but an object[] or a single object PSCumstomObject.
This is more or less explained in about Script Blocks:
A script block returns the output of all the commands in the script block, either as a single object or as an array.
You can overcome this by using the comma operator , which will wrap your ArrayList in a single element array to avoid losing the output type.
To put it into perspective with a simple example:
(& { [System.Collections.ArrayList]#(0..10) }).GetType() # Object[]
(& { , [System.Collections.ArrayList]#(0..10) }).GetType() # ArrayList
So, changing your function as follows should solve your problem:
function SelectCsvFile {
$fileDialog = [System.Windows.Forms.OpenFileDialog]#{
Title = "Datei auswählen"
InitialDirectory = Get-Location
Filter = 'Tabelle (*.csv)|*.csv'
}
if($fileDialog.ShowDialog() -eq 'OK') {
, [System.Collections.ArrayList]#(Import-Csv $fileDialog.FileName -Delimiter $ListImportDelimiter)
}
}
I'm building GUI using foreach,
Now... could do 16 buttons but trying to automate build a little:
1,2,3,4 | foreach { $Button...}
all this works and I get 16 buttons with correct offsets but got stuck a little when decided to use jobs within buttons, background job pings, sleeps, pings - if fail to ping job ends and at this point I want it to change button's color
Since all buttons are "$Button",
can foreach generate $button with different number at the end? ie $Button1, $Button2... etc ?
tried $Button$_ but this didn't work
or any thoughts on how to reference buttons so depending on which job finished could change color of that button?
Cheers
#Wasif_Hasan this didn't work
$i=120
1..4 | Foreach {Set-Variable -Name "Button$($_)" -Value "Value"
$i=$i+70
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Point(150,$i)
$Button.Size = New-Object System.Drawing.Size(75,23)
$Button.Text = 'Cancel'+$_
$Button.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.CancelButton = $Button
$form.Controls.Add($Button)
}
$Button3.Text = 'ok'+$_
Error:
The property 'Text' cannot be found on this object. Verify that the property exists and can be set.
Code that can be saved as .PS1 to check...
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Select a Computer'
$form.Size = New-Object System.Drawing.Size(300,400)
$form.StartPosition = 'CenterScreen'
$i=10
1..4 | Foreach {Set-Variable -Name "Button$($_)" -Value "Value"
$i=$i+50
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Point(150,$i)
$Button.Size = New-Object System.Drawing.Size(75,23)
$Button.Text = "Number"+$_
$form.Controls.Add($Button)
}
$Button3.Text = "Test"
$result = $form.ShowDialog()
Error:
The property 'Text' cannot be found on this object. Verify that the property
exists and can be set.
At line:19 char:1
+ $Button3.Text = "Test"
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException
Solution, Thanks Wasif_Hasan!
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Select a Computer'
$form.Size = New-Object System.Drawing.Size(300,400)
$form.StartPosition = 'CenterScreen'
$Button=1,2,3,4
$i=10
$k=1
1,2,3,4 | Foreach {
$i=$i+50
New-Variable "Button$_" -Value $(new-object System.Windows.Forms.Button -Property #{
Name = 'Dvar'
Location = New-Object System.Drawing.Size(10,$i)
Size = New-Object System.Drawing.Size(75,23)
Text = $_
})
$form.Controls.Add($(Get-Variable "Button$_" -ValueOnly))
$k=$k++
}
$Button2.Text="Hello"
$result = $form.ShowDialog()
$k=5
Remove-Variable Button*
$form.ActiveControl.Text
Result:
1
Hello
2
3
Yes, to set them use this:
1..4 | Foreach {Set-Variable -Name "Button$($_)" -Value "Value"}
And you can also get the values like:
1..4 | Foreach {(Get-Variable -Name "Button$($_)").Value}
All will be created like $Button1,$Button2 etc.
I built a dynamic Powershell GUI but i have trouble getting my buttons to work correctly.
I created a function to add texboxes and buttons. However they do not correspond correctly at this point. because i'm not sure how to bind the add_click to a specific textbox.
Here is the example code:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$Form = New-Object System.Windows.Forms.Form
$Form.Size = New-Object System.Drawing.Size(300,300)
$ButtonAdd = New-Object System.Windows.Forms.Button
$ButtonAdd.Location = New-Object System.Drawing.Point(20,20)
$ButtonAdd.Size = New-Object System.Drawing.Size(50,30)
$ButtonAdd.Text = 'Add'
$Form.Controls.Add($ButtonAdd)
$global:Counter = 0
$global:ButtonPosY = 20
$global:DefaultTextValue = ""
$ButtonAdd.Add_Click{Add-Fields}
Function Create-Button {
param ( $ButtonPosY, $TextBoxPosY)
$TextBoxField = New-Object System.Windows.Forms.TextBox
$TextBoxField.Location = New-Object System.Drawing.Point(181,$TextBoxPosY)
$TextBoxField.Size = New-Object System.Drawing.Size(50,30)
$TextBoxField.Text = $global:DefaultTextValue
New-Variable -name TextBoxField$global:Counter -Value $TextBoxField -Scope Global -Force
$Form.controls.Add($((Get-Variable -name TextBoxField$global:Counter).value))
$ButtonClick = New-Object System.Windows.Forms.Button
$ButtonClick.Location = New-Object System.Drawing.Point(100,$global:ButtonPosY)
$ButtonClick.Size = New-Object System.Drawing.Size(50,30)
$ButtonClick.Text = 'Click'
New-Variable -name ButtonClick$global:Counter -Value $ButtonClick -Scope Global -Force
$Form.controls.Add($((Get-Variable -name ButtonClick$global:Counter).value))
$ButtonClick.Add_Click({
$((Get-Variable -name TextBoxField$global:Counter -Scope Global).value).Text = 'hello'
})
}
Function Add-Fields {
$global:Counter = $global:Counter + 1
$global:ButtonPosY = $global:ButtonPosY + 40
Create-Button -TextBoxPosY $global:ButtonPosY -ButtonPosY $global:ButtonPosY
}
Create-Button -TextBoxPosY 21 -ButtonPosY 20
$Form.ShowDialog()
If the click button is pressed each time after new input is added everything works fine. however if multiple input fields are added first and then the click button is pressed the code breaks.
The Problem is here:
$ButtonClick.Add_Click({...})
I don't know how to add the counter (in my case $global:Counter) to the $ButtonClick variable as in $ButtonClick0, $ButtonClick1, ... etc. So right now when i add more buttons by calling the function the input will always be applied to the last added textbox since the add_click is not linked to a individual $ButtonClick0 variable.
How would this be done right?
After some serious reading i figured it out.
A name needs to be added to $ButtonClick
$ButtonClick.Name = $global:Counter
And the Add_Click event should look the following:
$ButtonClick.Add_Click({
[System.Object]$Sender = $args[0]
$((Get-Variable -name ('TextBoxField' + [int]$Sender.Name)).value).Text = 'hello'
})
However i don't know if this is how it should be done. Please post your solution if it looks different.
I have been working on a scrip that generates a password with specific characters with a specific length, but with random numbers at a specific length.
The script has an GUI (it's a work in progress, I will finish it eventually).
The issue that I'm facing, is that, whenever I press "Generate Password", it creates a password, but it does not give me a new one after generating it. It just gives the same password that it generates it the first time.
I was looking on the web on how to retrieve a new password each time the button is pressed, but I did not found anything.
Can someone help with some tips?
Thank you.
The script is:
Function Button_Click()
{
[System.Windows.Forms.MessageBox]::Show($DefinedLetters)
}
Function Generate-Form {
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Build Form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "Password Generator"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
# Add Button
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(35,35)
$Button.Size = New-Object System.Drawing.Size(120,23)
$Button.Text = "Generate Password"
$Form.Controls.Add($Button)
#Add Button event
$Button.Add_Click({Button_Click})
#Show the Form
$form.ShowDialog()| Out-Null
} #End Function
# Password generator #
Function DefinedLetters
{
$DefinedLetters = 'Summer'
$numbers = 0..5
$array = #()
$array += $DefinedLetters.Split(',') | Get-Random -Count 4
$DefinedLetters += $numbers | Get-Random -Count 4
($DefinedLetters | Get-Random -Count $DefinedLetters.Count) -join ""
}
#Call the Function
Generate-Form
This isn't a great method of generating passwords, but here is a version of your code that produces a 'random' password each time using 4 letters from 'summer' and 4 numbers from (0,1,2,3,4,5):
# Password generator #
Function DefinedLetters {
$DefinedLetters = 'Summer'
$numbers = 0..5
$array = #()
$array += $DefinedLetters.ToCharArray() | Get-Random -Count 4
$array += $numbers | Get-Random -Count 4
($array | Get-Random -Count $array.Count) -join ""
}
Function Button_Click() {
$DefinedLetters = DefinedLetters
[System.Windows.Forms.MessageBox]::Show($DefinedLetters)
}
Function Generate-Form {
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Build Form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "Password Generator"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
# Add Button
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(35,35)
$Button.Size = New-Object System.Drawing.Size(120,23)
$Button.Text = "Generate Password"
$Form.Controls.Add($Button)
#Add Button event
$Button.Add_Click({Button_Click})
#Show the Form
$form.ShowDialog()| Out-Null
} #End Function
#Call the Function
Generate-Form
In Button_Click() you just refer to variable, which doesn't call function. You need to assign the value form function to variable like:
Function Button_Click()
{
$PW = DefinedLetters
[System.Windows.Forms.MessageBox]::Show($PW)
}
Your function DefinedLetters doesn't/can't work as you intended
You don't use the function but a variable to show.
Function Button_Click(){
[System.Windows.Forms.MessageBox]::Show((DefinedLetters))
}
Function Generate-Form {
#... snipped for brevity ...
}
Function DefinedLetters{
(([char[]]'Summer' | Get-Random -Count 4) -join '')+
((0..5| Get-Random -Count 4) -join '')
}
#Call the Function
Generate-Form
I have a PowerShell (version 5.1) form with various sets of System.Windows.Forms Objects in it all named something generic, like $label1, $label2, etc. as well as $textBox1, $textBox2, etc.
Currently, I'm creating them each as they appear in the Form, like so:
Add-Type -AssemblyName System.Windows.Forms
$Form = New-Object system.Windows.Forms.Form
$Form | ForEach-Object {
$_.Size = New-Object System.Drawing.Size(800,550)
$_.StartPosition = 'CenterScreen'
$_.Text = "Example Form Name"
$_.Topmost = $true
}
$label1 = New-Object System.Windows.Forms.Label
$label1 | ForEach-Object {
$_.Location = '10,10'
$_.Size = '156,20'
$_.Text = 'Dummy text part 1'
}
$textBox1 = New-Object System.Windows.Forms.TextBox
$textBox1 | ForEach-Object {
$_.Location = '166,8'
$_.Size = '100,20'
}
$label2 = New-Object System.Windows.Forms.Label
$label2 | ForEach-Object {
$_.Location = '267, 10'
$_.Size = '85,20'
$_.Text = 'Dummy text part 2'
}
$textBox2 = New-Object System.Windows.Forms.TextBox
$textBox2 | ForEach-Object {
$_.Location = '352,8'
$_.Size = '100,20'
}
#A lot more lines of similar code go here
$Form.Controls.Add($label1)
$Form.Controls.Add($textBox1)
$Form.Controls.Add($label2)
$Form.Controls.Add($textBox2)
$result = $Form.ShowDialog()
I've got a few dozen of these Objects that I am creating and naming according to this scheme, and I'd really like to save some lines by creating all of these on one line each (e.g. all the labels on one line, then all the textBoxes on one line), or at least in one method/function each.
I tried this:
$label1, $label2, $label3 = New-Object System.Windows.Forms.Label
instead of declaring each $label on its own line in the full demo form above, but PowerShell doesn't seem to work like that; it only creates $label1 (which itself is odd; I would've expected it to only create $label3 were it going to work on only one of them) and PowerShell ISE throws an error for the property declarations later on for $label2 and $label3:
The property 'Location' cannot be found on this object. Verify that the property exists and can be set.
At <filepath>.ps1:43 char:5
+ $_.Location = '267, 10'
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound
...and so on.
Is there a way I can create multiple of the same type of Object with an incrementing suffix all in one go, instead of declaring them each on their own line, or am I stuck writing these how I've currently got them?
Multiple assignment assigns the elements of an array on the right hand of the assignment to the variables on the left hand. New-Object in and of itself does not allow for creating multiple objects at once. You could write a loop or a function that produces an array of labels and then use multiple assignment for assigning the elements of that array to the variables.
$label1, $label2, $label3 = 1..3 | ForEach-Object {
New-Object Windows.Forms.Label
}
or
function New-Label {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false)]
[int]$Count = 1
)
for ($i=0; $i -lt $count; $i++) {
New-Object Windows.Forms.Label
}
}
$label1, $label2, $label3 = New-Label 3
However, I think a better approach would be to write a function for creating one label object with the given parameters, and then assign the output of that function to one variable.
function New-Label {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]$Location,
[Parameter(Mandatory=$true)]
[string]$Size,
[Parameter(Mandatory=$false)]
[string]$Text = ''
)
$o = New-Object Windows.Forms.Label
$o.Location = $Location
$o.Size = $Size
$o.Text = $Text
return $o
}
$label1 = New-Label -Location '10,10' -Size '156,20' -Text 'Dummy text part 1'
$label2 = New-Label -Location '267,10' -Size '85,20' -Text 'Dummy text part 2'
$label3 = ...
You could also directly add the labels to the form instead of assigning them to variables first:
$Form.Controls.Add((New-Label '10,10' '156,20' 'Dummy text part 1'))
$Form.Controls.Add((New-Label '267,10' '85,20' 'Dummy text part 2'))
$Form.Controls.Add((New-Label ...))
Note that you need grouping expressions around the function calls when matrjoshking (nesting) them into the method calls. Hence the two sets of parentheses.
Of course, New-Label could be improved further, e.g. by having integer arrays instead of strings for $Location and $Size. Or by having another parameter set with individual values for $Top, $Left, $Width, and $Height, and assigning the values depending on which parameter set is used. Parameters should be validated too. And so on, and so forth.
This...
$label1, $label2, $label3 = New-Object System.Windows.Forms.Label
...creates a new Label and assigns it to $label1. Nothing is assigned to $label2 and $label3 because there are no more values on the right side to assign to them. If that syntax did assign all three variables they would (likely) all contain the same Label instance, which wouldn't work for your use case, anyways.
Instead, to instantiate three Label instances and assign them to the three variables you'd need to do this...
$label1, $label2, $label3 = (New-Object System.Windows.Forms.Label), (New-Object System.Windows.Forms.Label), (New-Object System.Windows.Forms.Label)
...which can be simplified to...
$label1, $label2, $label3 = 1..3 | ForEach-Object -Process { New-Object System.Windows.Forms.Label }
...which can be simplified to...
$label1, $label2, $label3 = 1..3 | % { New-Object System.Windows.Forms.Label }