I am trying to achieve the following
User clicks a button
Label1 appears at the top left (text = "Hi there")
Label1 disappears after 5 seconds
I have tried to work with the timer function but the Interval setting just appears to work as it states, in intervals.
I don't want to use the Start-Sleep option because I need the form to still be active while this prompt appears.
$timerPrompt = New-Object System.Windows.Forms.Timer
$timerPrompt.Interval = 3000
$timerPrompt.Add_Tick({$form.Controls.Remove($label1)})
As Drew already pointed out, your code does not show where you start, stop or dispose of the timer object.
This very simple form below creates a timer that gets started when the user clicks the button. In the Tick event of the timer it shuts itself down again.
Instead of removing the label, it hides and unhides it from view .
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Test Timer'
$form.Size = New-Object System.Drawing.Size(300,200)
$form.MinimumSize = New-Object System.Drawing.Size(300,150)
$form.StartPosition = "CenterScreen"
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(($form.Width - 20),50)
$label.Text = "Hi There"
$label.Anchor = "Top","Left","Right"
$label.Visible = $false
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 5000
$timer.Add_Tick({
# hide the label and stop the timer
$label.Visible = $false
$timer.Stop()
})
$button = New-Object System.Windows.Forms.Button
$button.Location = New-Object System.Drawing.Point(($form.Width - 185),($form.Height - 80))
$button.Size = New-Object System.Drawing.Size(75,23)
$button.Anchor = "Bottom","Left","Right"
$button.Text = "&Click Me"
$button.Add_Click({
# show the label and start the timer
$label.Visible = $true
$timer.Start()
})
# add the controls to the form
$form.Controls.Add($label)
$form.Controls.Add($button)
[void]$form.ShowDialog()
# when done, clean up the objects
$timer.Stop()
$timer.Dispose()
$form.Dispose()
Hope that explains
I am not very good with GUI programming but I have a trick that will solve that problem for you as I have used it many of my GUI programs.
for ($i = 0; $i -lt 25; $i++)
{
Start-Sleep -Milliseconds 200
[System.Windows.Forms.Application]::DoEvents()
}
Be advised that this is not ideal but I can't argue with the results. The little loop will give you a delay of 5 seconds while the DoEvents() part will keep ur form from going non responsive. Basically it checks for interrupts every 200ms giving the impression that the form is active.
Make the loop count 50 and reduce time to 100ms if you like. same results but extra responsiveness.
Related
I have been trying to create a Powershell script that performs the following
Shows a dialog box that an update is about to occur
Provide a countdown of say 30 seconds
During the countdown, a user can press "Cancel Update"
If the countdown expires and "Cancel Update" was not pressed, then update will occur
Right before the loop, the window shows if I call $Counter_Form.ShowDialog() and I can click the Cancel button. When clicking, the following should occur.
Window should close after pressing the button. This is correct.
$cancel should be set to $true to indicate that Cancel was pressed. However, it remains $false and this is incorrect. Why is this?
Now, for the problems in the while loop
The window refreshes to show the new delay, but I cannot click "Cancel Update" since it just shows an hourglass icon and seems to be frozen
Script
#Adjust delay here
$delay = 5
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Counter_Form = New-Object System.Windows.Forms.Form
$Counter_Form.Text = "Warning"
#Form size options
$Counter_Form.Width = 350
$Counter_Form.Height = 150
#Centers form on screen
$Counter_Form.StartPosition = "CenterScreen"
#Places form on top of everything else
$Counter_Form.TopMost = $true
$Counter_Label = New-Object System.Windows.Forms.Label
$Counter_Label2 = New-Object System.Windows.Forms.Label
#Label2's text
$Counter_Label2.Text = "Please save all your work"
#Labels size and position
$Counter_Label.AutoSize = $true
$Counter_Label.Location = New-Object System.Drawing.Point(50,60)
$Counter_Label2.AutoSize = $true
$Counter_Label2.Location = New-Object System.Drawing.Point(90,30)
$cancel = $false
$button1 = New-Object System.Windows.Forms.Button
$button1.Text = "Cancel Update";
$button1.Location = New-Object System.Drawing.Point(130,80)
$button1.Add_Click({ $Counter_Form.Close(); $cancel = $true})
$Counter_Form.Controls.Add($Counter_Label)
$Counter_Form.Controls.Add($Counter_Label2)
$Counter_Form.Controls.Add($button1)
#LOOP!
while ($delay -ge 0 -And $cancel -eq $false)
{
$Counter_Form.Show()
#Timer label's text
$Counter_Label.Text = "Update will occur in $($delay) seconds."
start-sleep 1
$delay -= 1
}
$Counter_Form.Close()
For the first question about $cancel not being updated to $true, it is a result of scope.
Add the script scope, with $script:cancel = $true. So that line of code should be:
$button1.Add_Click({ $Counter_Form.Close(); $script:cancel = $true})
I didn't find an exact solution to what I started. A pieced together some code from various sources and found the following worked. I'm posting it here in case it ends up helping someone:
function ShowMsg ($timeout, $message)
{
Add-Type -AssemblyName system.windows.forms
Add-Type -AssemblyName system.drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = "Window Title"
$form.Size = New-Object System.Drawing.Size(300,300)
$form.StartPosition = 'CenterScreen'
$label = New-Object System.Windows.Forms.label
$label.Text = $message
$label.Size = New-Object System.Drawing.Size(280,205)
$form.Controls.Add($label)
#add button to form
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Point(160,215)
$okButton.Size = New-Object System.Drawing.Size(100,23)
$okButton.Text = 'Cancel Update'
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $okButton
$form.Controls.Add($okButton)
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Location = New-Object System.Drawing.Point(40,215)
$cancelButton.Size = New-Object System.Drawing.Size(100,23)
$cancelButton.Text = 'Allow Update'
$cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::CANCEL
$form.CancelButton = $cancelButton
$form.Controls.Add($cancelButton)
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = $timeout * 1000
$timer.add_tick({$form.Close()})
$timer.Start()
$form.Topmost = $true
$form.ShowDialog()
$form.Dispose()
}
Calling the function:
$timeout = 60 #seconds
$message_response = ShowMsg -timeout $timeout -message "Hello, update is happening"
I want to be able to display the currently highlighted item from a scrolling list box, without having to click it.
I have set up the list box so that only one item is displayed. The selection changes via the scroll buttons. I want to be able to read the item displayed without having to click it - so .Add_Click is not suitable.
Example code below:
Many thanks
# Listbox test
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# set up form
$form = New-Object System.Windows.Forms.Form
$form.Text = "List Box Test"
$form.Size = New-Object System.Drawing.Size(500,200)
$form.StartPosition = "Manual"
$form.Location = New-Object System.Drawing.Point(10, 10)
# Set up list
$List1 = New-Object System.Windows.Forms.ListBox
$List1.Location = New-Object System.Drawing.Point(10, 10)
$List1.Height = 30
$List1.Width = 150
$List1.font = 'arial, 16pt'
[void] $List1.Items.Add('Clubs')
[void] $List1.Items.Add('Diamonds')
[void] $List1.Items.Add('Hearts')
[void] $List1.Items.Add('Spades')
$form.Controls.Add($List1)
# Question: how do I get the highlighted item from the list box to show here, before it is clicked?
# ----------------------------------------------------------------------------------------
# display selection
$TextBox1 = New-Object System.Windows.Forms.TextBox
$TextBox1.Location = New-Object System.Drawing.Point(10,50)
$TextBox1.Size = New-Object System.Drawing.Size(300,30)
$TextBox1.Font = 'arial, 12pt'
$TextBox1.text = "Highlighted item to show here ..."
$form.Controls.Add($TextBox1)
# ----------------------------------------------------------------------------------------
# set up button
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Location = New-Object System.Drawing.Point(10,100)
$Button1.Size = New-Object System.Drawing.Size(50,20)
$Button1.Text = "OK"
$Button1.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.Controls.Add($Button1)
$form.Topmost = $true
$null = $form.ShowDialog()
Just add a SelectedIndexChanged event handler to the listbox:
$List1.Add_SelectedIndexChanged({
$TextBox1.Text = $this.SelectedItem.ToString() # or do: $this.items[$this.SelectedIndex].ToString()
})
Since a Listbox only contains text, you could leave out the .ToString().
P.S. Don't forget to remove the form from memory when all done with
$form.Dispose()
I have created a function that creates a specific UI element with text input forms. This form also has three buttons:
One is supposed to display the text inputs again to do the same function again.
Another is supposed to finish and close the UI, passing the text input into the variables.
The last is supposed to cancel the UI element, doing nothing with anything in the text input forms.
Now I know the loop in the code isn't really complete, but I am having issues with it even performing the loop as well as passing the text forms into the variables. I know its something I am doing but it seems correct to me when I look at it.
Changed from an if loop, a while loop, and now a do/while loop.
Changed the position of the variables between the do section into the while section. Same for the if and while loops.
do {
ChangeDesc
}
while ($result -eq [System.Windows.Forms.DialogResult]::Retry)
{
$PC = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $HNInputBox.Text
$PC.Description = $DescInputBox.Text
$PC.Put()
}
ChangeDesc is the name of the function and works just as intended.
Expected to work is to loop the function 'ChangeDesc', and then when the 'Retry' or 'Ok' button is pressed, pass those forms to the variables as shown.
Currently, it will display the form, and when the 'Retry' button is pressed, the forms are passed properly and then the UI is closed out, the 'Ok' button does not pass any input and the 'Cancel' does the same thing.
Below is the rest of my lines of code for clarification.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function ChangeDesc {
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Data Entry Form'
$form.Size = New-Object System.Drawing.Size(300,210)
$form.StartPosition = 'CenterScreen'
$AnotherButton = New-Object System.Windows.Forms.Button
$AnotherButton.Location = New-Object System.Drawing.Point(15,130)
$AnotherButton.Size = New-Object System.Drawing.Size(75,23)
$AnotherButton.Text = 'Another?'
$AnotherButton.DialogResult = [System.Windows.Forms.DialogResult]::Retry
$form.AcceptButton = $AnotherButton
$form.Controls.Add($AnotherButton)
$FinishedButton = New-Object System.Windows.Forms.Button
$FinishedButton.Location = New-Object System.Drawing.Point(100,130)
$FinishedButton.Size = New-Object System.Drawing.Size(75,23)
$FinishedButton.Text = 'Finished'
$FinishedButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.CancelButton = $FinishedButton
$form.Controls.Add($FinishedButton)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Point(185,130)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = 'Cancel'
$CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.CancelButton = $CancelButton
$form.Controls.Add($CancelButton)
$HNLabel = New-Object System.Windows.Forms.Label
$HNLabel.Location = New-Object System.Drawing.Point(10,20)
$HNLabel.Size = New-Object System.Drawing.Size(280,20)
$HNLabel.Text = 'Enter Host Name:'
$form.Controls.Add($HNLabel)
$HNInputBox = New-Object System.Windows.Forms.TextBox
$HNInputBox.Location = New-Object System.Drawing.Point(10,40)
$HNInputBox.Size = New-Object System.Drawing.Size(260,20)
$form.Controls.Add($HNInputBox)
$DescLabel = New-Object System.Windows.Forms.Label
$DescLabel.Location = New-Object System.Drawing.Point(10,70)
$DescLabel.Size = New-Object System.Drawing.Size(280,20)
$DescLabel.Text = 'Enter Description:'
$form.Controls.Add($DescLabel)
$DescInputBox = New-Object System.Windows.Forms.TextBox
$DescInputBox.Location = New-Object System.Drawing.Point(10,90)
$DescInputBox.Size = New-Object System.Drawing.Size(260,20)
$form.Controls.Add($DescInputBox)
$form.Topmost = $true
$form.Add_Shown({$HNInputBox.Select()})
$result = $form.ShowDialog()
}
Your form is already closed when the loop terminates, and the variables you're trying to use are local to your function. Assign the values you're trying to use to script- or global-scope variables at the end of the function, and the code should do what you expect:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function ChangeDesc {
$form = New-Object Windows.Forms.Form
...
$script:result = $form.ShowDialog()
$script:hostname = $HNInputBox.Text
$script:description = $DescInputBox.Text
}
do {
ChangeDesc
} while ($script:result -eq [Windows.Forms.DialogResult]::Retry)
$PC = Get-WmiObject Win32_OperatingSystem -Computer $script:hostname
$PC.Description = $script:description
$PC.Put()
I'm trying to create a message dialogue in Powershell where the user has no option to action on the message as that is the intention. So the message will have the X button grayed along with the buttons (not showing buttons are even better).
The closest I could reach was disabling the X via below code:
$wshell = New-Object -ComObject Wscript.Shell -ErrorAction Stop
$wshell.Popup("Aborted",0,"ERROR!",48+4)
But cannot figure out disabling button part. Below MS articles were of little help as well:
http://blogs.technet.com/b/heyscriptingguy/archive/2006/07/27/how-can-i-display-a-message-box-that-has-no-buttons-and-that-disappears-after-a-specified-period-of-time.aspx
https://msdn.microsoft.com/en-us/library/x83z1d9f(v=vs.84).aspx
Referred to few other articles over net some even suggesting custom made buttons using HTML, or VB library. But not what I was looking for.
Any help/hint/suggestion would be deeply appreciated.
Regards,
Shakti
Dig into the .NET Windows.Forms namespace, you can make pretty much any kind of window you want with that:
https://msdn.microsoft.com/en-us/library/system.windows.forms.aspx
Here's a quick sample window w/ no buttons that can't be moved/closed by the user, but closes itself after 5 seconds:
Function Generate-Form {
Add-Type -AssemblyName System.Windows.Forms
# Build Form
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Test"
$objForm.Size = New-Object System.Drawing.Size(220,100)
# Add Label
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(80,20)
$objLabel.Size = New-Object System.Drawing.Size(100,20)
$objLabel.Text = "Hi there!"
$objForm.Controls.Add($objLabel)
# Show the form
$objForm.Show()| Out-Null
# wait 5 seconds
Start-Sleep -Seconds 5
# destroy form
$objForm.Close() | Out-Null
}
generate-form
Using the script above as a launching point I'm attempting to make a function that will allow me to popup a please wait message run some more script then close the popup
Function Popup-Message {
param ([switch]$show,[switch]$close)
Add-Type -AssemblyName System.Windows.Forms
# Build Form
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Test"
$objForm.Size = New-Object System.Drawing.Size(220,100)
# Add Label
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(80,20)
$objLabel.Size = New-Object System.Drawing.Size(100,20)
$objLabel.Text = "Hi there!"
$objForm.Controls.Add($objLabel)
If ($show)
{
$objForm.Show() | Out-Null
$global:test = "Show"
}
If ($close)
{
# destroy form
$objForm.Close() | Out-Null
$global:test = "Close"
}
}
I can then get the popup to display by:
Popup-Message -show
At this point I can see the $test variable as Show
But when I try to close the window with:
Popup-Message -close
But the popup window will not close
If I look at $test again it will show as Close
I'm assuming this has something to do with keeping the function in the Global Scope but I can't figure out how to do this with the form
I have created a powershell timer, and after every 1 second, I would like to run a function to execute or post text to a text box log on the interface.
Run the below. When you click Start, every 1 second, the log text area should show "Post to log every 1 second, not as a batch". However, this messages only appear as a batch, all at once, when you click Stop.
This question does not appear to be answered on the internet!
Code:
$global:timer = New-Object System.Timers.Timer
$global:timer.Interval = 1000
function AddToLog($logtext)
{
$txtLog.Text = $txtLog.Text + "`r`n" + $logtext
$txtLog.ScrolltoCaret
}
function startTimer() {
Register-ObjectEvent -InputObject $global:timer -EventName Elapsed -SourceIdentifier theTimer -Action {AddToLog('Post to log every 1 second, not as a batch') }
$global:timer.start()
Write-Host "Start Timer"
}
function stopTimer() {
$global:timer.stop()
Write-Host "Close Function"
Unregister-Event theTimer
}
########################
# Setup User Interface
########################
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Timer Example"
$objForm.Size = New-Object System.Drawing.Size(330,380)
$objForm.StartPosition = "CenterScreen"
#Start Button
$btnStart = New-Object System.Windows.Forms.Button
$btnStart.Location = New-Object System.Drawing.Size(10,190)
$btnStart.Size = New-Object System.Drawing.Size(140,35)
$btnStart.Text = "Start"
$btnStart.Add_Click({StartTimer; })
$objForm.Controls.Add($btnStart)
#Stop Button
$btnStop = New-Object System.Windows.Forms.Button
$btnStop.Location = New-Object System.Drawing.Size(150,190)
$btnStop.Size = New-Object System.Drawing.Size(140,35)
$btnStop.Text = "Stop"
$btnStop.Add_Click({StopTimer; })
$objForm.Controls.Add($btnStop)
$btnStop.Enabled = $true
#Log Area
$lblLog = New-Object System.Windows.Forms.Label
$lblLog.Location = New-Object System.Drawing.Size(10,230)
$lblLog.Size = New-Object System.Drawing.Size(80,20)
$lblLog.Text = "Event Log:"
$objForm.Controls.Add($lblLog)
$txtLog = New-Object System.Windows.Forms.Textbox
$txtLog.Location = New-Object System.Drawing.Size(10,250)
$txtLog.Size = New-Object System.Drawing.Size(290,90)
$txtLog.Multiline = $True
$txtLog.Scrollbars = "vertical"
$txtLog.Add_Click({$txtLog.SelectAll(); $txtLog.Copy()})
$objForm.Controls.Add($txtLog)
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
Thanks for your help in advanced.
-R
I took what you had, and looked at this egg_timer project and came up with the following:
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.add_tick({AddToLog 'Post to log every 1 second, not as a batch'})
function AddToLog($logtext)
{
$txtLog.Text = $txtLog.Text + "`r`n" + $logtext
$txtLog.ScrolltoCaret
}
function startTimer() {
$timer.start()
}
function stopTimer() {
$timer.Enabled = $false
Write-Host "Close Function"
}
########################
# Setup User Interface
########################
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Timer Example"
$objForm.Size = New-Object System.Drawing.Size(330,380)
$objForm.StartPosition = "CenterScreen"
#Start Button
$btnStart = New-Object System.Windows.Forms.Button
$btnStart.Location = New-Object System.Drawing.Size(10,190)
$btnStart.Size = New-Object System.Drawing.Size(140,35)
$btnStart.Text = "Start"
$btnStart.Add_Click({StartTimer; })
$objForm.Controls.Add($btnStart)
#Stop Button
$btnStop = New-Object System.Windows.Forms.Button
$btnStop.Location = New-Object System.Drawing.Size(150,190)
$btnStop.Size = New-Object System.Drawing.Size(140,35)
$btnStop.Text = "Stop"
$btnStop.Add_Click({StopTimer; })
$objForm.Controls.Add($btnStop)
$btnStop.Enabled = $true
#Log Area
$lblLog = New-Object System.Windows.Forms.Label
$lblLog.Location = New-Object System.Drawing.Size(10,230)
$lblLog.Size = New-Object System.Drawing.Size(80,20)
$lblLog.Text = "Event Log:"
$objForm.Controls.Add($lblLog)
$txtLog = New-Object System.Windows.Forms.Textbox
$txtLog.Location = New-Object System.Drawing.Size(10,250)
$txtLog.Size = New-Object System.Drawing.Size(290,90)
$txtLog.Multiline = $True
$txtLog.Scrollbars = "vertical"
$txtLog.Add_Click({$txtLog.SelectAll(); $txtLog.Copy()})
$objForm.Controls.Add($txtLog)
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
Try using a System.Windows.Forms.Timer. See this article for info on the differences between .NET timers.
In the case of a WinForms timer, ditch the Register-ObjectEvent and Unregister-Event and add this line before you call ShowDialog():
$timer.add_Tick({AddToLog('Post to log every 1 second, not as a batch')})
Don't forget to change to System.Windows.Forms.Timer. When I make those change, the UI works.
Use of Register-ObjectEvent requires that PowerShell services the event queue at various safe points in its execution. However, since PowerShell is blocked inside some .NET code (Form.ShowDialog()), it doesn't get a chance to service its event queue.