Basically I'm trying to create a ContextMenu that shows a list of mounted network drive, so the user can click on one and access it.
The problem is : the list is not fix, but depend on a string list extracted from a DataGrid.
In my mind, I could just create the menu items by looping on that string list.
Here is the code sample :
$Main_Tool_Icon = New-Object System.Windows.Forms
[...]
# Add menu entries
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
foreach ($nameTag in $dataGrid[$DATAGRID_COLUMN_NAME]){
$menuEntry = New-Object System.Windows.Forms.MenuItem
$menuEntry.Text = $nameTag
$Main_Tool_Icon.ContextMenu.MenuItems.AddRange($menuEntry)
}
# Add separator
$Main_Tool_Icon.ContextMenu.MenuItems.Add('-')
# Last one to show main form
$displayMainform = New-Object System.Windows.Forms.MenuItem
$displayMainform.Text = "Edit"
$Main_Tool_Icon.ContextMenu.MenuItems.Add($displayMainform)
It appears that's wrong and only shows the separator and last "Edit" line.
Is there a way to populate a contextual menu with a list ?
Or I should look for a better way to do the job ?
EDIT #Mathias R. Jessen
You're right about AddRange(), it's just a mistake.
I can't show everything since it's quite big. I'll try to synthesize an answer properly.
The data grid is created/populated by my script and is just a table of strings from a csv file.
$DATAGRID_COLUMN_NAME = 'NAME' it's one of constants defined for my datagrid headers.
Here is the setup
# set datagrid
$dataGrid = New-Object System.Windows.Forms.DataGridView
[...]
$dataGrid.Columns.Add($DATAGRID_COLUMN_NAME,'Name')
$dataGrid.Columns[0].DataPropertyName = $dataGrid.Columns[0].Name
Then some databinding
# initiate the data table
$table = New-Object System.Data.DataTable
[...]loop on csv file[...]
# bind data table to data grid
$dataGrid.DataSource = $table
EDIT #SantiagoSquarzon
You were right for pointing out $dataGrid[$DATAGRID_COLUMN_NAME]. The syntax was bad and returned something null. Instead, I looped over the $dataGrid.Rows like so:
foreach ($row in $dataGrid.Rows){
$nameTag = $row.Cells[$DATAGRID_COLUMN_NAME].Value
if ($nameTag -ne $null){
$menuEntry = New-Object System.Windows.Forms.MenuItem
$menuEntry.Text = $nameTag
$Main_Tool_Icon.ContextMenu.MenuItems.AddRange($menuEntry)
}
}
Now I just have to figure out how to invoke there click event.
I created a powershell form with a few entry fields that are checked for input before doing any further actions. One of them is a listbox with a set of numbers representing printer numbers in our organization.
When the end-user selects the item with the printer via the dropdown menu, my script can perfectly read via .SelectedItem. However, they can also type the number in the field, and I am struggling to find how to get the value from the field when it is typed instead of selected.
Listbox :
$dropdown_Machine = New-Object System.Windows.Forms.Combobox
$dropdown_Machine.Location = New-Object System.Drawing.Size(250,215)
$dropdown_Machine.Size = New-Object System.Drawing.Size(300,50)
$dropdown_Machine.Font = $DROPDOWNFONT
[void] $dropdown_Machine.Items.Add(" ")
[void] $dropdown_Machine.Items.Add("729")
[void] $dropdown_Machine.Items.Add("730")
[void] $dropdown_Machine.Items.Add("744")
... some more items ...
$Form_PrintLabel.Controls.Add($dropdown_Machine)
My check that is working when selected :
$searchmach = $dropdown_Machine.SelectedItem
When it is typed, it gives me an empty string. When it is selected it gives me the input I expected.
Can anyone push me in the right direction ?
Thanks.
Regards,
Mike
if ($dropdown_Machine.SelectedIndex -eq -1) { # nothing selected
$dropdown_Machine.Text
} else {
$dropdown_Machine.SelectedItem
}
I have got an understanding problem regarding to Powershell Code in order to remove a Click Event from a WinForm button. After several hours... several days of trying, trying to understand and despairing I thought I give it a break and probably you guys can help me. I really have read several Posts regarding this theme. But that did not help me finally. So please let me ask that question again.
I have seen that there is a possibility to use Eventhandlers and this method seems to work quite fine. As my code seems to be correct, because Powershell do not throw out an error, I would like to know why the code line seems not to be affective. I really do not understand why. Because I have found several codes with remove_Click examples, but in my case it seems not to do what I expect. As I really do not understand why I would like you to help me. Please be so kind and try to explain to me why line 30 of my script has no effect or not the desired effect.
Short: What do I want to do? I just want to remove a Click Event from a button. I could add the Event to the button using Add_Click. So I thought Remove_Click would remove the "Click Code" from this Special button. But it does not seem to work. I just want to remove the Click Property from the button if the savefiledialog is closed by using the cancel button.
This is the code:
Add-Type -AssemblyName System.Windows.Forms
function form_status(){
$form_status = New-Object System.Windows.Forms.Form
$form_status.Size = New-Object System.Drawing.Size(800,530)
$form_status.StartPosition = 'CenterScreen'
$form_status.FormBorderStyle = 'FixedToolWindow'
$form_status_button_csv_logfile = New-Object System.Windows.Forms.Button
$form_status_button_csv_logfile.Location = New-Object System.Drawing.Point(1,1)
$form_status_button_csv_logfile.Size = New-Object System.Drawing.Size(50,50)
$form_status.Controls.Add($form_status_button_csv_logfile)
$form_status_button_csv_logfile.Add_Click({Choose-Folder-For-Checksumlog})
$form_status_button_csv_logfile.add_MouseHover({button_mousehover})
$form_status_button_csv_logfile.add_MouseLeave({button_mouseleave})
[System.Windows.Forms.Application]::EnableVisualStyles();
$form_status_result = $form_status.ShowDialog()
}
Function Choose-Folder-For-Checksumlog(){
$SaveChooser = New-Object -Typename System.Windows.Forms.SaveFileDialog
$SaveChooser.InitialDirectory = [Environment]::GetFolderPath("Desktop")
$SaveChooser.Filter = "CSV Logfile (*.csv)|*.csv"
$savechooser.FileName = "testfile.csv"
if($SaveChooser.ShowDialog() -eq [System.Windows.Forms.DialogResult]::CANCEL){
$savechooser.FileName = ""
$form_status_button_csv_logfile.Remove_Click({Choose-Folder-For-Checksumlog})
}
$checksumlog_folder = $SaveChooser.FileName
}
function button_mouseleave(){
$form_status.Cursor=[System.Windows.Forms.Cursors]::Default
}
function button_mousehover(){
$form_status.Cursor=[System.Windows.Forms.Cursors]::Hand
}
form_status
I appreciate any help from you guys. Please be so kind and explain to me what I do wrong. Probably my expectaions are wrong... But I do not understand it at the moment.
With kindest Regards
FernandeZ
$clickexample = {Choose-Folder-For-Checksumlog};
$form_status_button_csv_logfile.add_Click($clickexample);
$form_status_button_csv_logfile.remove_Click($clickexample);
I know that I'm a bit late to the party, but recently I came into the situtation where it was required to remove multiple event handlers from multiple elements (several different-purpose Click handlers on each of six buttons in my case).
While suggestion from #D'Artagnan works, it is still not helpful in case if your scriptblock handler is not stored in variable (or variable is unknown).
For example, this code removes handler from the button:
$ScriptBlock = {Write-Host 'Clicked'}
$MyButtonObject.Add_Click($ScriptBlock)
$MyButtonObject.Remove_Click($ScriptBlock)
But this one does not (I believe, it is because scriptblock is not referenced by variable, therefore it is a different object, which is not registered in event handler, and, as result, cannot be removed):
$MyButtonObject.Add_Click({Write-Host 'Clicked'})
$MyButtonObject.Remove_Click({Write-Host 'Clicked'})
In addition, I wasn't able to find something like .Remove_AllHandlers(), unfortunately.
Thankfully, I've discovered this question and was able to adapt the code given by #Douglas to PowerShell, so with credits to the original author, I'm happy to share the solution with anyone who wonders:
Function Remove-RoutedEventHandlers {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True)]
[ValidateNotNullorEmpty()]
[System.Windows.UIElement]$Element,
[Parameter(Mandatory = $True)]
[ValidateNotNullorEmpty()]
[System.Windows.RoutedEvent]$RoutedEvent
)
$eventHandlersStoreProperty = $Element.GetType().GetProperty("EventHandlersStore", [System.Reflection.BindingFlags]'Instance, NonPublic')
$eventHandlersStore = $eventHandlersStoreProperty.GetValue($Element, $Null)
If ($eventHandlersStore) {
$getRoutedEventHandlers = $eventHandlersStore.GetType().GetMethod("GetRoutedEventHandlers", [System.Reflection.BindingFlags]'Instance, Public, NonPublic')
$RoutedEventHandlers = [System.Windows.RoutedEventHandlerInfo[]]$getRoutedEventHandlers.Invoke($eventHandlersStore, $RoutedEvent)
ForEach ($RoutedEventHandler in $RoutedEventHandlers) {
$Element.RemoveHandler($RoutedEvent, $RoutedEventHandler.Handler)
}
}
}
To call the function, you must provide the control and required kind of events to be cleared. For example:
Remove-RoutedEventHandlers -Element $MyButtonObject -RoutedEvent $([System.Windows.Controls.Button]::ClickEvent)
Please note that you have to put event in $() to avoid it from being treated as string. Alternatively, you could alter the function to accept string (for example, with value "Click") and re-construct such event on the provided control inside the function like this $RoutedEvent = $Element.GetType()::"${RoutedEvent}Event"
This function enumerates all handlers of the specified type on provided object and removes them.
If I'm not mistaken, if you call $Element.Remove_SomeEvent($ScriptBlockToRemove) in PowerShell, $Element.RemoveHandler($Element.GetType()::SomeEvent, $ScriptBlockToRemove) is being executed under the hood, so in given example this function is equivalent of enumerating all of the scriptblocks (referenced in Handler property of RoutedEventHandlerInfo object) and calling $MyButtonObject.Remove_Click($ScriptBlockToRemove) on each of them
In PowerShell it is quiet common to use Windows Forms to build a User Interface for small cmdlets but the syntaxis required for this are often partly redundant and quiet verbose. This leads to the question:
Is there a way to minimize the code required or does there exist a Windows Forms wrapper for PowerShell to reduce the verbose and redundant syntaxis?
I am not looking for the ShowUI as this solution is too heavy considering it based on Windows Presentation Foundation (see also: WPF vs WinForms) and the fact that it concerns a PowerShell module which makes it more difficult to deploy it than a wrapper function.
In a lot of cases a wrapper in not required to make your code less verbose, take e.g. the lengthy WinForms PowerShell script here. Code pieces like this:
$System_Windows_Forms_Padding = New-Object System.Windows.Forms.Padding
$System_Windows_Forms_Padding.All = 3
$System_Windows_Forms_Padding.Bottom = 3
$System_Windows_Forms_Padding.Left = 3
$System_Windows_Forms_Padding.Right = 3
$System_Windows_Forms_Padding.Top = 3
$Tab1.Padding = $System_Windows_Forms_Padding
Can easily be simplified in WinForms to a single line:
$Tab1.Padding = 3
And if the padding would be different for each side, PowerShell will automatically convert:
$Tab1.Padding = "4, 6, 4, 6"
Note: PowerShell does not convert $Tab1.Padding = "3" or $Tab1.Padding = "4, 6"
Nevertheless, the native way to create a windows form control is far from DRY (don't repeat yourself) programming. Although (multiple) properties can be added at creation (using:New-Object System.Windows.Forms.Button -Property #{Location = "75, 120"; Size = "75, 23"}) , multiple properties can't be set right away at a later state. Above that, it isn't quick and easy to add events1, child controls and container properties (as e.g. RowSpan), or any combination, intermediately at creation of a windows form control. Bottom line, you have to reference the windows form control over and over again to set its properties and more (with e.g. $OKButton.<property> = ... as in this example) :
$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"
That's why I have created a reusable PowerShell Form Control wrapper that let's you minimize Windows Forms (WinForms) code to it's essence.
1) unless you use On<event> methods, see also: addEventListener vs onclick
PowerShell Form-Control Wrapper
Function Form-Control {
[CmdletBinding(DefaultParametersetName='Self')]param(
[Parameter(Position = 0)]$Control = "Form",
[Parameter(Position = 1)][HashTable]$Member = #{},
[Parameter(ParameterSetName = 'AttachChild', Mandatory = $false)][Windows.Forms.Control[]]$Add = #(),
[Parameter(ParameterSetName = 'AttachParent', Mandatory = $false)][HashTable]$Set = #{},
[Parameter(ParameterSetName = 'AttachParent', Mandatory = $false)][Alias("Parent")][Switch]$GetParent,
[Parameter(ParameterSetName = 'AttachParent', Mandatory = $true, ValueFromPipeline = $true)][Windows.Forms.Control]$Container
)
If ($Control -isnot [Windows.Forms.Control]) {Try {$Control = New-Object Windows.Forms.$Control} Catch {$PSCmdlet.WriteError($_)}}
$Styles = #{RowStyles = "RowStyle"; ColumnStyles = "ColumnStyle"}
ForEach ($Key in $Member.Keys) {
If ($Style = $Styles.$Key) {[Void]$Control.$Key.Clear()
For ($i = 0; $i -lt $Member.$Key.Length; $i++) {[Void]$Control.$Key.Add((New-Object Windows.Forms.$Style($Member.$Key[$i])))}
} Else {
Switch (($Control | Get-Member $Key).MemberType) {
"Property" {$Control.$Key = $Member.$Key}
"Method" {Invoke-Expression "[Void](`$Control.$Key($($Member.$Key)))"}
"Event" {Invoke-Expression "`$Control.Add_$Key(`$Member.`$Key)"}
Default {Write-Error("The $($Control.GetType().Name) control doesn't have a '$Key' member.")}
}
}
}
$Add | ForEach {$Control.Controls.Add($_)}
If ($Container) {$Container.Controls.Add($Control)}
If ($Set) {$Set.Keys | ForEach {Invoke-Expression "`$Container.Set$_(`$Control, `$Set.`$_)"}}
If ($GetParent) {$Container} Else {$Control}
}; Set-Alias Form Form-Control
Syntax
Creating a control
<System.Windows.Forms.Control> = Form-Control [-Control <String>] [-Member <HashTable>]
Modifying a control
<Void> = Form-Control [-Control <System.Windows.Forms.Control>] [-Member <HashTable>]
Adding a (new) control to a container
<System.Windows.Forms.Control> = Form-Control [-Control <String>|<System.Windows.Forms.Control>] [-Member <HashTable>] [-Add <System.Windows.Forms.Control[]>]
Piping a container to a (new) control
<System.Windows.Forms.Control> = <System.Windows.Forms.Control> | Form-Control [-Control <String>|<System.Windows.Forms.Control>] [-Member <HashTable>] [-Set <HashTable>] [-PassParent]
Parameters
-Control <String>|<System.Windows.Forms.Control> (position 0, default: Form)
The -Control parameter accepts either a Windows form control type name ([String]) or an existing form control ([System.Windows.Forms.Control] ). Windows form control type names are like Form, Label, TextBox, Button, Panel, ..., etc.
If a Windows form control type name ([String]) is supplied, the wrapper will create and return a new Windows form control with properties and settings as defined by the rest of the parameters.
If an existing Windows form control ([System.Windows.Forms.Control] ) is supplied, the wrapper will update the existing Windows form control using the properties and settings as defined by the rest of the parameters.
-Member <HashTable> (position 1)
Sets property values, invokes methods and add events on a new or existing object.
If the hash name represents property on the control, e.g. Size = "50, 50", the value will be assigned to the control property value.
If the hash name represents method on the control, e.g. Scale = {1.5, 1.5}, the control method will be invoked using the value for arguments .
If the hash name represents event on the control, take e.g. Click = {$Form.Close()}, the value ( [ScriptBlock]) will be added to the control events.
Two collection properties, ColumnStyles and RowStyles, are simplified especially for the TableLayoutPanel control which is considered a general substitute for the WPF Grid control:
- The ColumnStyles property, clears all column widths and reset them with the ColumnStyle array supplied by the hash value.
- The RowStyles property, clears all row Heigths and reset them with the RowStyle array supplied by the hash value.
Note: If want to add or insert a single specific ColumnStyle or RowStyle item, you need to fallback on the native statement, as e.g.: [Void]$Control.Control.ColumnStyles.Add((New-Object Windows.Forms.ColumnStyle("Percent", 100)).
-Add <Array>
The -Addparameter adds one or more child controls to the current control.
Note: the -add parameter cannot be used if container is piped to the control.
-Container <System.Windows.Forms.Control> (from pipeline)
The parent container is usually provided from the pipeline: $ParentContainer | Form $ChildControl and attached a (new) child control to the concerned container.
-Set <HashTable>
The -Setparameter sets (SetCellPosition, SetColumn, SetColumnSpan, SetRow, SetRowSpan and SetStyle) the specific child control properties related its parent panel container, e.g. .Set RowSpan = 2
Note: the -set column - and row parameters can only be used if a container is piped to the control.
-GetParent
By default the (child) control will be returned by the form-control function unless the -GetParent switch is supplied which will return the parent container instead.
Note: the -set column - and row parameters can only be used if a container is piped to the control.
Examples
There are two way to setup the Windows Forms hierarchy:
Adding a (new) control to a container
Piping a container to a (new) control
Adding a (new) control to a container
For this example I have reworked the Creating a Custom Input Box at learn.microsoft.com using the PowerShell Form-Control wrapper:
$TextBox = Form TextBox #{Location = "10, 40"; Size = "260, 20"}
$OKButton = Form Button #{Location = "75, 120"; Size = "75, 23"; Text = "OK"; DialogResult = "OK"}
$CancelButton = Form Button #{Location = "150, 120"; Size = "75, 23"; Text = "Cancel"; DialogResult = "Cancel"}
$Result = (Form-Control Form #{
Size = "300, 200"
Text = "Data Entry Form"
StartPosition = "CenterScreen"
KeyPreview = $True
Topmost = $True
AcceptButton = $OKButton
CancelButton = $CancelButton
} -Add (
(Form Label #{Text = "Please enter the information below:"; Location = "10, 20"; Size = "280, 20"}),
$TextBox, $OKButton, $CancelButton
)
).ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
$x = $TextBox.Text
$x
}
Note 1: Although the adding controls appears more structured especially for small forms, the drawback is that can't invoke methods that relate to both the parent container and child control (like -Set RowSpan).
Note 2: You might easily get lost in open and close parenthesis if try build child (or even grandchild) controls directly in a parent container (like the above Label control). Besides it more difficult to reference such a child (e.g. $OKButton vs. $Form.Controls["OKButton"], presuming you have set the button property Name = "OKButton)
Piping a container to a (new) control
For this example, I have created a user interface to test the dockproperty behavior. The form looks like this:
The PowerShell Form-Control code required for this:
$Form = Form-Control Form #{Text = "Dock test"; StartPosition = "CenterScreen"; Padding = 4; Activated = {$Dock[0].Select()}}
$Table = $Form | Form TableLayoutPanel #{RowCount = 2; ColumnCount = 2; ColumnStyles = ("Percent", 50), "AutoSize"; Dock = "Fill"}
$Panel = $Table | Form Panel #{Dock = "Fill"; BorderStyle = "FixedSingle"; BackColor = "Teal"} -Set #{RowSpan = 2}
$Button = $Panel | Form Button #{Location = "50, 50"; Size = "50, 50"; BackColor = "Silver"; Enabled = $False}
$Group = $Table | Form GroupBox #{Text = "Dock"; AutoSize = $True}
$Flow = $Group | Form FlowLayoutPanel #{AutoSize = $True; FlowDirection = "TopDown"; Dock = "Fill"; Padding = 4}
$Dock = "None", "Top", "Left", "Bottom", "Right", "Fill" | ForEach {
$Flow | Form RadioButton #{Text = $_; AutoSize = $True; Click = {$Button.Dock = $This.Text}}
}
$Close = $Table | Form Button #{Text = "Close"; Dock = "Bottom"; Click = {$Form.Close()}}
$Form.ShowDialog()
I'm doing some scripting in PowerShell involving sending some mail automatically. I'm aware that the olMailItem object (on 2003 at least) has a couple of methods, Display() and Close() but is it possible to get the current visibility status?
If I run the following:
$Outlook = New-Object -ComObject Outlook.Application
$Mail1 = $Outlook.CreateItem(0)
$Mail1.To = $_.UserID
$Mail1.SentOnBehalfOfName = "me#mydomain.com"
$Mail1.Subject = $Subject1
$Mail1.Body = $BodyText1
$Mail1.Display()
$a = $Mail1
$Mail1.Close()
$b = $Mail1
I can't see any difference between $a and $b
What I was hoping for was a $Mail.IsVisible bool property or something.
Can it be done?
You can loop through the Application.Inspectors collection and compare each Inspector.CurrentItem.EntryId with MailItem.EntryId property of the item in question. If it is you who create the item and do not save it first, how can be opened in one of the Inspectors? You are the only one who can show it.