Powershell: Event handlers registered via Register-ObjectEvent don't fire immediately - only after my dialog closes - powershell

I have a question regarding event handlers (Register-ObjectEvent). Here's my code:
Import-Module "$PSScriptRoot\Lib\Ookii.Dialogs.Winforms.dll"
$OKButton = [Ookii.Dialogs.WinForms.TaskDialogButton]("Ok")
$NOButton = [Ookii.Dialogs.WinForms.TaskDialogButton]("No")
$CancelButton = [Ookii.Dialogs.WinForms.TaskDialogButton]("Cancel")
$HelpButton = [Ookii.Dialogs.WinForms.TaskDialogButton]("Help")
$OokieTaskDialog = New-Object Ookii.Dialogs.WinForms.TaskDialog
$OokieTaskDialog.Buttons.Add($OKButton)
$OokieTaskDialog.Buttons.Add($NOButton)
$OokieTaskDialog.Buttons.Add($CancelButton)
$OokieTaskDialog.Buttons.Add($HelpButton)
$OokieTaskDialog.MainInstruction = "Main Instruction"
$OokieTaskDialog.Content = "Eiusmod cupidatat amet officia ut cillum anim proident. Aliqua aliqua ullamco reprehenderit velit est eiusmod et aute."
$OokieTaskDialog.ExpandedInformation = "Wozarlov, Marvin Houston, (870) 487-7980"
$OokieTaskDialog.Footer = "This is the footer text More Info"
$OokieTaskDialog.FooterIcon = [Ookii.Dialogs.WinForms.TaskDialogIcon]::Information
$OokieTaskDialog.EnableHyperlinks = $true
Register-ObjectEvent -InputObject $OokieTaskDialog -EventName HyperlinkClicked -Action { Write-Host "Link Clicked" }
$OokieTaskDialog.WindowTitle = "Window Title"
$OokieTaskDialog.Width = 300
$OokieTaskDialog.AllowDialogCancellation = $true
$OokieTaskDialog.MainIcon = [Ookii.Dialogs.WinForms.TaskDialogIcon]::Warning
$Result = $OokieTaskDialog.ShowDialog((New-Object System.Windows.Forms.Form -Property #{TopMost = $true}))
$Result
Specifically: Register-ObjectEvent -InputObject $OokieTaskDialog -EventName HyperlinkClicked -Action { Write-Host "Link Clicked" }
The event handler works, but it only displays the results ("Link Clicked") AFTER the dialog has closed. How do I get an event handler to respond immediately when the trigger conditions have been met?
For instance, if I click the hyperlink in the footer 10 times, and then click the OK button, the console prints "Link Clicked" 10 times.
I want to print "Link Clicked" immediately when the hyperlink is clicked.
How can I register an event that fires immediately? What am I doing wrong?

ShowDialog run on the same thread than the listener created through Register-ObjectEvent. It won't process anything until the dialog is closed.
To have a script block directly handle a .NET event, call the object's Add-Event() method.
$OokieTaskDialog.Add_HyperlinkClicked({ Write-Host "Link Clicked" })
Additional references:
This mklement0 excellent answer: How to add an Event Action handler in Powershell
Excerpt
C# offers syntactic sugar in the form of operators += and -= for
attaching and detaching event-handler delegates, which look like
assignments, but are in reality translated to add_() and
remove_() method calls.
PowerShell offers no such syntactic sugar for attaching/removing event
handlers, so the methods must be called directly.
This very complete answer by iRon: Powershell: Job Event Action with Form not executed
Excerpt:
Even if there are
multiple threads setup, there are two different 'listeners' in one
thread. When your script is ready to receive form events (using
ShowDialog or DoEvents) it can’t listen to .NET events at the same
time. And visa versa
Both of these answers provide a lot more insights and references links themselves.

Related

Hide and disable maximize button and disable double-clicking bar to maximize window

I have the following Frame Settings in Powershell for my GUI.
$frmMainframe = New-Object system.Windows.Forms.Form
$frmMainframe.ClientSize = New-Object System.Drawing.Point(449,385)
$frmMainframe.text = "Yannicks Administration Tools"
$frmMainframe.TopMost = $false
$frmMainframe.FormBorderStyle = "FixedSingle"
$frmMainframe.startposition = "CenterScreen"
$frmMainframe.BackColor = [System.Drawing.ColorTranslator]::FromHtml("#ffffff")
Can someone help me disabling aswell as hiding the maximize button?
And is it possible to disable the double-clicking function on the Window bar to maximize the window?
Thank you for your time!
You can disable the Maximize button like this:
$frmMainframe.MaximizeBox = $false
You can do the same for the MinimizeBox. To not show any of the Minimize, Maximize and Close boxes, you can do $frmMainframe.ControlBox = $false. In that case, you need to have a button so the form can be closed.
To prevent changing the size when the user double-clicks the window bar, setting the MinimumSize and MaximumSize properties of the form might help:
$frmMainframe.MaximumSize = $frmMainframe.Size
$frmMainframe.MinimumSize = $frmMainframe.Size

Powershell GUI based on Forms + Panels - Flickers on Update

Before we begin, let me aside: I am not THAT familiar with Powershell. I know more than a lot of people around me by virtue of experimentation in a VM environment and Googlefu, which is why this has landed on me.
Here's my problem. I've created a small Powershell GUI. I've built a form, and in that form I've embedded four panels. IndexPanel, ScreenSelectionPanel, OptionsPanel, and ThemePanel.
The form itself has an 'OK' and 'CANCEL' button.
Each Panel has either a NEXT, PREVIOUS or both buttons depending on placement.
ScreenSelect only has NEXT
Options has NEXT and PREVIOUS
Theme has PREVIOUS
Index floats off to the side in the same form, letting you jump around as there'll be more panels later.
I've gotten to the point where I can have the panels - all the same size - slide from one to the next via add_click call on the individually named buttons, with my panel function sending the argument called to all panels at once. If the argument is '1', for instance, SelectScreen calls method .show(), while the others call method .hide().
And for the first few clicks, it works great. My problem is that after three or four clicks back and forth, the app starts flickering. And then starts lagging. And then begins doing both. I suspect the problem is in the way paint is being handled, as all I'm calling is 'Refresh' or 'Update'.
Has anyone found a smooth way to swap from panel to panel in a single form in Powershell? Yes, I realize VB or even C would have been a better solution as I'm rather pushing what Powershell is supposed to handle, but neither of those are available to me via the nature of the project. It MUST be done in Powershell, with .NET support.
Example code, because my current is a mess of reminders and hashes for mistakes I've made in writing. I'll clean it all up once I jot down the roadblocks I've hit. I load all my variable at the top of the script so they're considered in scope for every function that calls them. It also helps me keep track of all the variables I've got, so don't be concerned by my lack of loading here. Also, I'm leaving out the definitions for the boxes, buttons, and so forth - I'll add it if it's important to the scope of the problem. Just to cut down on so much spam.
function GUIPanels {
function ScreenSelectPanel{
$objScreenSelectPanel.Size = New-Object System.Drawing.Size(500,600)
$objScreenSelectPanel.BorderStyle = "FixedSingle"
$objScreenSelectPanel.AutoScroll = "True"
$objScreenSelectPanel.AutoSize = "True"
$objScreenSelectPanel.Width = 400
if ($Args[0] -eq 1) {$objScreenSelectPanel.Show()}
else {$objScreenSelectPanel.Hide()}
$SelectionPanelNEXTButton.Add_Click({
GUIPanels 2
$objMainForm.Update()
})
}
function OptionsPanel {
$objOptionsPanel.Size = New-Object System.Drawing.Size(500,600)
$objOptionsPanel.BorderStyle = "FixedSingle"
$objOptionsPanel.AutoScroll = "True"
$objOptionsPanel.AutoSize = "True"
$objOptionsPanel.Width = 400
if ($Args[0] -eq 2) {$objOptionsPanel.Show()}
else {$objOptionsPanel.Hide()}
$OptionsPanelNEXTButton.Add_Click({
GUIPanels 3
$objMainForm.Update()
})
$OptionsPanelPREVIOUSButton.Add_Click({
GUIPanels 1
$objMainform.Update()
})
}
function ThemePanel {
$objThemePanel.Size = New-Object System.Drawing.Size(500,600)
$objThemePanel.BorderStyle = "FixedSingle"
$objThemePanel.AutoScroll = "True"
$objThemePanel.AutoSize = "True"
$objThemePanel.Width = 400
if ($Args[0] -eq 3) {$objThemePanel.Show()}
else {$objThemePanel.Hide()}
$ThemePanelPREVIOUSButton.Add_Click({
GUIPanels 1
$objMainform.Update()
})
}
}
function GUIMainForm {
$objMainForm.Size = New-Object System.Drawing.Size(680,700)
$objMainForm.AutoScroll = "True"
$objMainForm.KeyPreview = $True
#Location setups.
$objMainForm.StartPosition = "CenterScreen"
#Key captures. Adding in activity for button presses.
#Letting panels handle the ENTER Key for now.
#$objMainForm.Add_KeyDown({
# if ($_.KeyCode -eq "Enter") {
# $x=$objMainScreenBox.SelectedItem
# $objMainForm.Close()
# }
# })
$objMainForm.Add_KeyDown({
if ($_.KeyCode -eq "Escape") {
$objMainForm.Close()
}
})
#Button Locations.
$OKButton.Location = New-Object System.Drawing.Size(175,620)
$CloseButton.Location = New-Object System.Drawing.Size(320,620)
#Panel Locations.
$objScreenSelectPanel.Location = New-Object System.Drawing.Size(220,10)
$objOptionsPanel.Location = New-Object System.Drawing.Size(220,10)
$objThemePanel.Location = New-Object System.Drawing.Size(220,10)
$objIndexPanel.Location = New-Object System.Drawing.Size(10,10)
$objMainForm.Controls.add($objIndexPanel)
$objMainForm.Controls.add($objScreenSelectPanel)
$objMainForm.Controls.add($objThemePanel)
$objMainForm.Controls.add($objOptionsPanel)
$objMainForm.Controls.add($OKButton)
$objMainForm.Controls.add($CloseButton)
$objMainForm.Topmost = $True
#$objMainForm.DoubleBuffered = "True"
}
function GUISetup {
#1 - main screen, 2 - optionals, 3 - theme, 4 - Slipdata
#for ($i=1; $i -le 4; $i++) {Write-Host (4%$i)} -- Doesn't feed $i properly.
GUIBoxes
GUIButtons
GUILabels
GUIMainForm
GUIPanels $Args[0]
#$objMainForm.Add_Shown({$objMainForm.Activate()})
#[void] $objMainForm.ShowDialog()
#$objMainForm.hide()
#timeout 5 > $null
#$objMainForm.show()
#timeout 4 > $null
#$objMainForm.Update()
#$objMainForm.hide()
#$objMainForm.Refresh()
#$InitialFormWindowState = $objMainForm.WindowState
#Init the OnLoad event to correct the initial state of the form
#$objMainForm.add_Load($Form_StateCorrection_Load)
#Handling Refresh in the actual button press.
return $objMainForm.ShowDialog()
#$objMainForm.Dispose()
#$objApplicationClass.run($objMainForm)
}
#DEBUG
GUIsetup 1 | Out-Null
$x
I actually figured out what I did wrong. Namely, I'm calling every panel to create with every next or previous. So four panels, eight panels, twelve panels, sixteen panels (flicker), twenty panels (Flicker flicker slow down) and so forth. I was so focused on this being a problem with Powershell I didn't stop to think about the logic I was using.
So I'm going to call the .hide() and .show() in a separate function.

How to close the form on mouse click?

I'm creating a Graphical Date Picker in PowerShell, based on this article. The following part of the code in the article helps to close the form after the date selection and pressing Enter key:
$objForm.Add_KeyDown({
if ($_.KeyCode -eq "Enter")
{
$dtmDate=$objCalendar.SelectionStart
$objForm.Close()
}
})
I also want to add mouse event for the date selection and to close the form. So the question is, how do we close the form once the date has been selected and on MouseUp event?
Thanks.
Instead of registering an event handler for all Click/MouseDown/MouseUp events, you could use the DateSelected event instead. From the description:
Occurs when the user makes an explicit date selection using the mouse.
$objForm.Add_DateSelected({
$dtmDate=$objCalendar.SelectionStart
$objForm.Close()
})
In PowerShell 3.0 or newer, you may have to change the scope of $dtmDate variable in order for it to work:
$script:dtmDate = $objCalendar.SelectionStart
or (-Scope 1 means "direct parent scope" or "1 step up the call stack")
Set-Variable -Scope 1 -Name dtmDate -Value $objCalendar.SelectionStart

Perl TK with Proc::Background proper usage (keep GUI active?)

I'm trying to build a Toplevel window that will show the progress of a system cmd. I want the GUI to be active (without freezing and "not responding"), so pressing on the "Cancel" button will kill the process, otherwise, when finished, make active the "close" button and disable the "cancel". Following a suggestion to one of my previous questions, I tried to use Proc::Background. The sole way I've found to do it is:
my $proc1;
my $cancel = $toplevel->Button(-text => "Cancel", -command =>sub{$proc1->die;})->pack;
my $close = $toplevel->Button(-text => "Close", -command =>sub{destroy $toplevel;}, -state=>"disabled")->pack;
$proc1 = Proc::Background->new("x264.exe $args");
while ($proc1->alive == 1){
$mw->update();
sleep(1);
}
$cancel->configure(-state=>'disabled');
$close->configure(-state=>'normal');
Is there another, more efficient way to do it (without waiting 1 sec for response)?
Thanks,
Mark.
I use Time::HiRes::usleep.
use Time::HiRes qw(usleep);
while ($proc1->alive == 1){
$mw->update();
usleep(100_000); //0.1 seconds
}
It may be an overkill for this problem, but at some point our UI applications grow and we desperately need to use high resolution timers and asynchronously dispatch and listen events thorough out the application. For this purpose I find the POE framework a great asset.
I particularly use POE with wxWidgets but it is also compatible with Tk:
POE::Loop::Tk
The after method (of any Tk widget) lets you schedule a callback to occur a certain number of milliseconds in the future, and the waitVariable method (you'll need to search in the page) will run the event loop until a variable is set.
my $proc1;
my $cancel = $toplevel->Button(-text => "Cancel", -command =>sub{$proc1->die;})->pack;
$proc1 = Proc::Background->new("x264.exe $args");
my $procDone = 0;
my $timercb = sub {
if ($proc1->alive) {
$toplevel->after(100, $timercb);
} else {
$procDone = 1; # Anything really
}
};
$timercb();
$toplevel->waitVariable(\$procDone) unless ($procDone);
(I'm not sure if this code will work; I don't code very much in Perl these days, so I'm translating what I'd do in another language…)

How to check if a button is clicked?

I have a following LOC in my ps1 script which displays a message with an OK button.
[Windows.Forms.MessageBox]::Show($Message, $Title, [Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information, [System.Windows.Forms.MessageBoxDefaultButton]::Button1, [System.Windows.Forms.MessageBoxOptions]::DefaultDesktopOnly) | Out-Null
I want to perform some operation only if the user clicks the OK button something like:
if (Button1.pressed())
{
#perform some operations
}
How do I check if the button is clicked?
Thanks
I found a simple explanation here.
Basically, you compare the Show function's return value with [Windows.Forms.DialogResult]::OK