PowerShell and Windows Forms Tooltip custom colour - winforms

I'm trying to customise a simple tooltip with a black background (and black border if possible) and white text. I have the following code, but at the moment it's flakey, sometimes works, other times doesn't.
Can somebody please advise how to make this more reliable? Thanks.
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Windows.Forms.Application]::EnableVisualStyles()
Add-Type -AssemblyName System.Drawing
#create form
$form = New-Object System.Windows.Forms.Form
$shutdownBtn = New-Object System.Windows.Forms.Button
$shutdownBtn.Size = New-Object System.Drawing.Size(200, 40)
$shutdownBtn.Text = "Shut down"
$form.Controls.Add($shutdownBtn)
$tooltip2 = New-Object System.Windows.Forms.ToolTip
$tooltip2.SetToolTip($shutdownBtn, "Shut down.")
$tooltip2.OwnerDraw = $true
$tooltip2.Add_Draw($tooltip2_Draw)
$tooltip2_Draw=[System.Windows.Forms.DrawToolTipEventHandler]{
$fontstyle = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Normal)
$format = [System.Drawing.StringFormat]::GenericTypographic
$myBrush1 = new-object Drawing.SolidBrush White
$_.Graphics.DrawString($_.ToolTipText, $fontstyle, $myBrush1, $_.Bounds.X, $_.Bounds.Y, $format)
$myBrush2 = new-object Drawing.SolidBrush Black
$_.Graphics.FillRectangle($myBrush2, $_.Bounds)
$_.DrawBackground()
$_.DrawBorder()
$_.DrawText()
}
$form_cleanup =
{
$tooltip2.Remove_Draw($tooltip2_Draw)
$form.remove_FormClosed($form_cleanup)
}
$form.add_FormClosed($form_cleanup)
[void]$form.ShowDialog()
$form.Dispose()

I'll answer my own question:
There were a couple of glaring errors from a tired mind. Firstly:
$tooltip2.Add_Draw($tooltip2_Draw)
should have been called after I declared
$tooltip2_Draw
Secondly, I needed to call
FillRectangle
before i called
DrawString
And finally, since I set OwnerDraw = $true I didn't need to call:
$_.DrawBackground()
$_.DrawBorder()
$_.DrawText()
So here is the solution:
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Windows.Forms.Application]::EnableVisualStyles()
Add-Type -AssemblyName System.Drawing
#create form
$form = New-Object System.Windows.Forms.Form
$shutdownBtn = New-Object System.Windows.Forms.Button
$shutdownBtn.Size = New-Object System.Drawing.Size(200, 40)
$shutdownBtn.Text = "Shut down"
$form.Controls.Add($shutdownBtn)
$tooltip2 = New-Object System.Windows.Forms.ToolTip
$tooltip2.SetToolTip($shutdownBtn, "Shut down now.")
$tooltip2.OwnerDraw = $true
$tooltip2_Draw=[System.Windows.Forms.DrawToolTipEventHandler]{
$fontstyle = new-object System.Drawing.Font('Microsoft Sans Serif', 16, [System.Drawing.FontStyle]::Regular)
$format = [System.Drawing.StringFormat]::GenericTypographic
$format.LineAlignment = [System.Drawing.StringAlignment]::Center;
$format.Alignment = [System.Drawing.StringAlignment]::Center;
$whiteBrush = new-object Drawing.SolidBrush White
$blackBrush = new-object Drawing.SolidBrush Black
$_.Graphics.FillRectangle($blackBrush, $_.Bounds)
$_.Graphics.DrawString($_.ToolTipText, $fontstyle, $whiteBrush, ($_.Bounds.X + ($_.Bounds.Width/2)), ($_.Bounds.Y + ($_.Bounds.Height/2)), $format)
}
$tooltip2.Add_Draw($tooltip2_Draw)
$form_cleanup =
{
$tooltip2.Remove_Draw($tooltip2_Draw)
$form.remove_FormClosed($form_cleanup)
}
$form.add_FormClosed($form_cleanup)
[void]$form.ShowDialog()
$form.Dispose()

Related

Load WinForm Object from CliXml PS

How can one save and load the properties of GUI objects from a CliXml file? Saving mwe is below, with failed attempt to load commented out. directly importing changes object type from System.Windows.Forms.Button to System.Management.Automation.PSObject. Attempts to loop over the saved properties failed.
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
Remove-Variable * -ErrorAction SilentlyContinue
$form = New-Object system.Windows.Forms.Form
$form.ClientSize = New-Object System.Drawing.Point(100,100)
$form.text = "Form"
$form.TopMost = $false
$form.FormBorderStyle = "FixedSingle"
$form.MaximizeBox = $false
$TestButton = New-Object system.Windows.Forms.Button
$TestButton.text = "Test"
$TestButton.width = 85
$TestButton.height = 30
$TestButton.location = New-Object System.Drawing.Point(0,0)
$TestButton.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12)
$TestButton.Enabled = $true
$path = "./vars/test.xml"
$TestButton | Export-CliXml $path
$TestButton.Enabled = $false
#$TestButton.GetType().FullName #For comp below
#$TestButton = Import-CliXml $path #Import the saved properties - intention is that this enables button again
#$TestButton.GetType() #The import changes the type. How to load saved properties and avoid this?
$form.Controls.Add($TestButton)
[void]$form.ShowDialog()
Why not "serialize" and save it as a PowerShell expression:
{
New-Object system.Windows.Forms.Button -Property #{
text = "Test"
width = 85
height = 30
location = New-Object System.Drawing.Point(0,0)
Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12)
Enabled = $true
}
} | Out-File .\Vars\Test.ps1
Note: using the outer curly brackets will validate the expression, but you might also just use (here) quotes
And then load it using dot-sourcing:
$TestButton = . .\Vars\Test.ps1

Populate listbox from a selected.item in another listbox

Good day to you all!
I have a Powershell program that I am making for my company. I have 2 listbox, one with the make of printers and the other will be for the models of printers. I was hoping for help to populate the 'model' list with what the user selects in the 'make' list. I understand doing a selecteditem could work, but whatever I do, it doesn't populate the 'model' list.
This is my first time programming and am self-taught. Right now, I am using Windows.Forms to make the GUI.
Any help is appreciated. Thank you so much for this wonderful community of like-minded awesomeness!
*Code:
Add-Type -AssemblyName System.Windows.Forms
$Form_Service = New-Object system.Windows.Forms.Form
$Form_Service.ClientSize = '452,400'
$Form_Service.text = "Service Call"
$Form_Service.TopMost = $true
$Form_Service.StartPosition = 'CenterScreen'
$Label_ValleyID = New-Object system.Windows.Forms.Label
$Label_ValleyID.text = "Enter Valley ID"
$Label_ValleyID.AutoSize = $true
$Label_ValleyID.width= 25
$Label_ValleyID.height = 10
$Label_ValleyID.location = New-Object System.Drawing.Point(45,41)
$Label_ValleyID.Font = 'Microsoft Sans Serif,10'
$TextBox_ValleyID= New-Object system.Windows.Forms.TextBox
$TextBox_ValleyID.multiline= $false
$TextBox_ValleyID.width= 180
$TextBox_ValleyID.height = 20
$TextBox_ValleyID.location = New-Object System.Drawing.Point(45,62)
$TextBox_ValleyID.Font = 'Microsoft Sans Serif,10'
$Label_Make= New-Object system.Windows.Forms.Label
$Label_Make.text = "Make"
$Label_Make.AutoSize = $true
$Label_Make.width= 25
$Label_Make.height = 10
$Label_Make.location = New-Object System.Drawing.Point(45,108)
$Label_Make.Font = 'Microsoft Sans Serif,10'
$ListBox_Make= New-Object system.Windows.Forms.ListBox
$ListBox_Make.text = "Make"
$ListBox_Make.width= 144
$ListBox_Make.height = 50
$ListBox_Make.location = New-Object System.Drawing.Point(45,129)
[void] $ListBox_Make.Items.Add('Brother')
[void] $ListBox_Make.Items.Add('Canon')
[void] $ListBox_Make.Items.Add('HP')
[void] $ListBox_Make.Items.Add('Kyocera')
[void] $ListBox_Make.Items.Add('Ricoh')
[void] $ListBox_Make.Items.Add('Sharp')
$Label_Model = New-Object system.Windows.Forms.Label
$Label_Model.text= "Model"
$Label_Model.AutoSize= $true
$Label_Model.width = 25
$Label_Model.height= 10
$Label_Model.location= New-Object System.Drawing.Point(259,108)
$Label_Model.Font= 'Microsoft Sans Serif,10'
$ListBox_Model = New-Object system.Windows.Forms.ListBox
$ListBox_Model.text= "Model"
$ListBox_Model.width = 146
$ListBox_Model.height= 50
$ListBox_Model.location= New-Object System.Drawing.Point(259,129)
$Label_Location= New-Object system.Windows.Forms.Label
$Label_Location.text = "Location"
$Label_Location.AutoSize = $true
$Label_Location.width= 25
$Label_Location.height = 10
$Label_Location.location = New-Object System.Drawing.Point(45,195)
$Label_Location.Font = 'Microsoft Sans Serif,10'
$TextBox_Location= New-Object system.Windows.Forms.TextBox
$TextBox_Location.multiline= $false
$TextBox_Location.width= 363
$TextBox_Location.height = 20
$TextBox_Location.location = New-Object System.Drawing.Point(45,215)
$TextBox_Location.Font = 'Microsoft Sans Serif,10'
$Label_Problem = New-Object system.Windows.Forms.Label
$Label_Problem.text= "State what is wrong:"
$Label_Problem.AutoSize= $true
$Label_Problem.width = 25
$Label_Problem.height= 10
$Label_Problem.location= New-Object System.Drawing.Point(45,250)
$Label_Problem.Font= 'Microsoft Sans Serif,10'
$TextBox_Problem = New-Object system.Windows.Forms.TextBox
$TextBox_Problem.multiline = $false
$TextBox_Problem.width = 364
$TextBox_Problem.height= 100
$TextBox_Problem.location= New-Object System.Drawing.Point(45,270)
$TextBox_Problem.Font= 'Microsoft Sans Serif,10'
$CheckBox_Nope = New-Object system.Windows.Forms.CheckBox
$CheckBox_Nope.text= "Is your printer inoperable?"
$CheckBox_Nope.width = 250
$CheckBox_Nope.height= 50
$CheckBox_Nope.location= New-Object System.Drawing.Point(145,295)
$CheckBox_Nope.Font = 'Microsoft Sans Serif,10'
$Button_Submit = New-Object system.Windows.Forms.Button
$Button_Submit.text= "Submit"
$Button_Submit.width = 70
$Button_Submit.height= 30
$Button_Submit.location= New-Object System.Drawing.Point(189,345)
$Button_Submit.Font= 'Microsoft Sans Serif,10'
$Form_Service.controls.AddRange(#($Label_ValleyID,$Label_Make,$Label_Model,$Label_Location,$Label_Problem,$TextBox_ValleyID,$ListBox_Make,$ListBox_Model,$TextBox_Location,$TextBox_Problem,$Button_Submit,$CheckBox_Nope))
if ($ListBox_Make.SelectedItem -eq "Brother"){
[void] $ListBox_Model.Items.Add('MP301')
}
[void]$Form_Service.ShowDialog()
OK so what you are looking for is called events. Events are actions that take place that then allow you to run code after the action. Like when Mouse Clicks on a object or Keyboard button is pressed.
In Powershell when dealing with Winforms you can use
$Control.Add_EventName{
Code Here
}
Put the events after the controls are already called. I usally put them right before i show a form.
$ListBox_Make.Add_Click{
$ListBox_Model.Items.Add($ListBox_Make.SelectedItem)
}
[void]$Form_Service.ShowDialog()
in your exact case you could use :
$ListBox_Make.Add_Click{
if ($ListBox_Make.SelectedItem -eq "Brother"){
[void] $ListBox_Model.Items.Add('MP301')
}
}
Hopefully this puts you on the right track.
Also instead of a bunch of if Statements try a Switch instead
$ListBox_Make.Add_Click{
switch ($ListBox_Make.SelectedItem){
"Brother"{
$ListBox_Model.Items.Add('MP301')
}
"Canon"{
$ListBox_Model.Items.Add('LT45')
}
"HP"{
$ListBox_Model.Items.Add('ABC2133')
}
}
}
You can find the events for listbox
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.listbox?view=netframework-4.7.2#events

How to prevent null/whitespace value in Windows Form textbox (using Powershell)?

I made a Windows Form (this is the last portion of it) that allows the user to enter a search term in a textbox. The whole script checks a server for log files, and downloads them to the user's machine. The textbox data contains a string (date, account #, etc.)...and if left blank it is treated as a wildcard, downloading every log file in the folder chosen. I'm not sure if I can disable the "ok" button until data is entered or give a pop-up/message box prompting the user to enter a search term? I left the prior code/variables out of this example, as it has no bearing on the issue. I appreciate any help in advance!
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = "Enter search criteria"
$form.Size = New-Object System.Drawing.Size(300,200)
$form.StartPosition = "CenterScreen"
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $OKButton
$form.Controls.Add($OKButton)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.Text = "Enter search term"
$form.Controls.Add($label)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10,40)
$textBox.Size = New-Object System.Drawing.Size(260,20)
$form.Controls.Add($textBox)
$form.Topmost = $True
$form.Add_Shown({$textBox.Select()})
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
$Search = $textBox.Text
$Search
}
$UserPath = "C:\GetFiles\getfiles"
& cmd /c $UserPath" "$Search
Here would be one way you could handle it.
$okbutton.enabled = $false # make this a default
if(![string]::IsNullOrEmpty($textbox.text)) #only enable when you have text in the text box
{
$okbutton.enabled = $true
}
You'll have to use an event from the textbox to change the value of the $okbutton to enabled once text has been entered. I believe this is the event you'll need:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.textchanged?view=netframework-4.7.2
Depending on what you are doing you may need some of the other events as well.
I figured it out. Thank you to Thom Schumacher for helping me with the syntax necessary to complete this:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = "Enter search criteria"
$form.Size = New-Object System.Drawing.Size(300,200)
$form.StartPosition = "CenterScreen"
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $OKButton
$form.Controls.Add($OKButton)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.Text = "Enter loan number or date to search"
$form.Controls.Add($label)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10,40)
$textBox.Size = New-Object System.Drawing.Size(260,20)
$form.Controls.Add($textBox)
$form.Topmost = $True
$form.Add_Shown({$textBox.Select()})
do
{
$result = $form.ShowDialog()
if ([string]::IsNullOrEmpty($textbox.text))
{
Write-Warning 'Please enter search term.'
}
}
until(![string]::IsNullOrEmpty($textbox.text))
{
}
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
$Search = $textBox.Text
$Search
}

.NET DateTimePicker calendar not appearing in PowerShell form

I am trying to show a DateTimePicker in a WinForms form in PowerShell. When the form appears, the date can be changed, but hitting the dropdown button doesn't show the calendar. It seems like something is happening, it's just not visible.
What stupid thing am I missing here?
$form = New-Object Windows.Forms.Form
$form.Size = New-Object Drawing.Size #(400, 400)
$form.StartPosition = "CenterScreen"
$form.Text = "When do you want the snapshot to be taken?"
$form.Font = New-Object Drawing.Font("Microsoft Sans Serif",12)
$form.FormBorderStyle = [Windows.Forms.FormBorderStyle]::Fixed3D
$panel = New-Object Windows.Forms.FlowLayoutPanel
$panel.Height = 400
$panel.Width = 400
$panel.AutoScroll = $true
$panel.FlowDirection = [Windows.Forms.FlowDirection]::TopDown
$panel.WrapContents = $false
$titleText = New-Object Windows.Forms.Label
$titleText.AutoSize = $true
$titleText.Text = "When do you want the snapshot to be taken?"
$panel.Controls.Add($titleText)
$datePicker = New-Object Windows.Forms.DateTimePicker
$datePicker.ShowUpDown = $false
$datePicker.MinDate = $now
$datePicker.MaxDate = $now.AddMonths(3); #arbitrary
$datePicker.MaxSelectionCount = 1
$datePicker.Width = 350
$datePicker.Enabled = $false
$panel.Controls.Add($datePicker)
$form.Controls.Add($panel)
$drc = $form.ShowDialog()
Your code seems to work fine on my computer. I copy-pasted your code and added Add-Type to top:
Add-Type -AssemblyName System.Windows.Forms
And changed the $datePicker.Enabled:
$datePicker.Enabled = $true
When run, I can change the date/time and also the dropdown is working as expected:

Power Shell marquee progress bar not working

What I'm attempting to do is create a window using forms and add a marquee style progress bar that continuously loops while my script runs. I'm not concerned about tracking progress, it is just so the user knows that something is occurring.
Here's what I have so far:
Add-Type -AssemblyName System.Windows.Forms
$window = New-Object Windows.Forms.Form
$window.Size = New-Object Drawing.Size #(400,75)
$window.StartPosition = "CenterScreen"
$window.Font = New-Object System.Drawing.Font("Calibri",11,[System.Drawing.FontStyle]::Bold)
$window.Text = "STARTING UP"
$ProgressBar1 = New-Object System.Windows.Forms.ProgressBar
$ProgressBar1.Location = New-Object System.Drawing.Point(10, 10)
$ProgressBar1.Size = New-Object System.Drawing.Size(365, 20)
$ProgressBar1.Style = "Marquee"
$ProgressBar1.MarqueeAnimationSpeed = 20
$window.Controls.Add($ProgressBar1)
$window.ShowDialog()
This draws the progress bar and the window, but I don't get the marquee animation inside the progress bar.
What am I missing?
VisualStyles must be enabled. That's why on ISE works, but doesn't on Console.
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$window = New-Object Windows.Forms.Form
$window.Size = New-Object Drawing.Size #(400,75)
$window.StartPosition = "CenterScreen"
$window.Font = New-Object System.Drawing.Font("Calibri",11,[System.Drawing.FontStyle]::Bold)
$window.Text = "STARTING UP"
$ProgressBar1 = New-Object System.Windows.Forms.ProgressBar
$ProgressBar1.Location = New-Object System.Drawing.Point(10, 10)
$ProgressBar1.Size = New-Object System.Drawing.Size(365, 20)
$ProgressBar1.Style = "Marquee"
$ProgressBar1.MarqueeAnimationSpeed = 20
$window.Controls.Add($ProgressBar1)
$window.ShowDialog()

Resources