Draw multiple circles on an image in windows.forms using powershell - winforms

I am mostly done creating a GUI that will pop up with an image in a picturebox, and allow the user to draw multiple red circles on it by clicking (where I'll then have it save to a passed filepath/name).
User draws the circle by clicking where on the image they want the top left starting point to be, and dragging to resize it (where the previous circle disappears and the correctly sized one is displayed) until they release the mouse.
The first circle appears correctly, but for all following circles it does not refresh properly and thus rather than resizing one circle dragging the mouse displays a bunch of different sized circles corresponding to all the past dragged mouse positions.
I have been banging my head on this one all day, what am I missing that will make the follow-up circles display correctly?
$file = "...TestImage.PNG"
$script:DrawingStatus = "In_Progress" #switch case for mouse drag
$panel1 = New-Object Windows.Forms.Panel
$Panel1.AutoScroll = $true;
$win = New-Object Windows.Forms.Form
$box = New-Object Windows.Forms.PictureBox
$box.Image = [System.Drawing.Image]::FromFile($file)
$box.AutoSize = $true
$src = $box.Image
$bmp=new-object System.Drawing.Bitmap $src.width, $src.height
$script:graphics=[System.Drawing.Graphics]::FromImage($bmp)
$units = [System.Drawing.GraphicsUnit]::Pixel
$destRect = new-object Drawing.Rectangle 0, 0, $src.width, $src.height
$srcRect = new-object Drawing.Rectangle 0, 0, $src.width, $src.height
$graphics.DrawImage($src, $destRect, $srcRect.X, $srcRect.Y, $srcRect.width, $srcRect.height, $units)
$image = $src
$Clicky4 = { #Executed on Mouse Drag while mouse is down
param($point)
$graphics.DrawImage($image, $destRect, $srcRect.X, $srcRect.Y, $srcRect.width, $srcRect.height, $units)
$box.image = $bmp
$mouse = [System.Windows.Forms.Cursor]::Position;
$point2 = $box.PointToClient($mouse);
write-host $point;
$mypen = new-object System.Drawing.Pen black
$mypen.color = "red"
$mypen.width = 3
$graphics.DrawEllipse($mypen, $point.X, $point.Y, ($point2.X-$point.X), ($point2.Y-$point.Y))
}
$clicky2 = { #executed on mouse down, calls clicky4
$script:DrawingStatus = "In_Progress"
write-host "Test 2 Passed!";
$mouse = [System.Windows.Forms.Cursor]::Position;
$point = $box.PointToClient($mouse);
write-host $point;
$box.tag = $point;
$superclick = {
switch ($script:DrawingStatus){
"In_Progress" {
Invoke-Command $clicky4 -ArgumentList $box.tag
}
"Done"{ #does nothing, so dragging mouse with mouse up does not trigger circle resizing
}
}
}
$box.add_MouseMove($superclick)
}
$clicky3 = { #executed on mouse up
write-host "Test 3 Passed!";
$script:DrawingStatus = "Done"
$script:image = $script:BMP #this is my attempt to create a BMP with the drawn circle after resizing so that the box image can be refreshed to this when drawing the next circle
$script:graphics=[System.Drawing.Graphics]::FromImage($script:image)
$mouse = [System.Windows.Forms.Cursor]::Position;
$point = $box.PointToClient($mouse);
write-host $point;
$array = #();
$array += $box.tag;
$array += $point;
$win.tag = $array
}
$Panel1.controls.add($box)
$win.width = 1500
$win.height = 800
$panel1.width = ($win.width - 15)
$panel1.height = ($win.height -50)
$box.add_MouseDown($clicky2)
$box.add_MouseUp($clicky3)
$win.Controls.Add($panel1)
$win.ShowDialog()
write-host "Coords: " $win.tag
$initial = $win.tag[0]
$Final = $win.tag[1]
write-host "Initial: " $initial
write-host "Final: " $Final
write-host "X1: " $initial.X
write-host "Y1: " $initial.Y
write-host "X2: " $final.X
write-host "Y2: " $final.Y

The biggest reason you're getting multiple circles is because your code is not erasing the old circles. Since you want them to be able to draw multiple circles in the window, the best way to do this would be with two System.Drawing.Drawing2D.GraphicsPath objects. One with the permanent circles, and one with the temporary circle being resized. MouseUp would add the current temp ellipse to the permanent GraphicsPath, while mouse move would clear and redraw the temp GraphicsPath and call Invalidate. In an Add_Paint event call, you would draw both GraphicsPaths to the ImageBox directly to draw over your base image. When saving after you can draw the permanent Paths onto the image before saving to file to permanently alter them.

Related

Powershell WPF Runspace - Changing values in SyncHashtable is slow?

im building a tool with powershell wpf.
I finally managed to get my head around runspaces somewhat and am successfully updating the main forms progress bar from a runspace, woo!
However, it is really slow when i touch anything in the sync hash table it seems. Without updating a label &/or the progress bar in the synchash the function completes WAY faster. Am I missing something still?
My Runspace code is basically this:
$SyncHash = [hashtable]::Synchronized(#{
ProgressBar = $ProgressBar
Window = $Window
})
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ApartmentState = "STA"
$Runspace.ThreadOptions = "ReuseThread"
$Runspace.Open()
$Runspace.SessionStateProxy.SetVariable("syncHash", $syncHash)
$SessionScript = [powershell]::Create().AddScript( {
for ($i = 0; $i -lt 100 ; $i++) {
Start-Sleep -Seconds .1
$SyncHash.Progress.Dispatcher.Invoke([action] { $SyncHash.Progress.Value = $i })
}
})
$SessionScript.Runspace = $Runspace
$SessionScript.BeginInvoke()
So for example if i comment out the line to update the progress bar that will complete way faster.... whyyy????

Visual Studio PowerShell Drag & Drop Attachments From Outlook to List Box

i have got the drag and drop working on my Windows Form. i can drop items from my desktop or any folder but if i try to Drag an attachment straight from Outlook it won't do any thing. do i need to add extra PowerShell commands int my current code
######################################## This is For Drag And Drop
$listBox1_DragOver = [System.Windows.Forms.DragEventHandler]{
if ($_.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop))
{
$_.Effect = 'Copy'
}
Else
{
$_.Effect = 'None'
}
}
$listBox1_DragDrop = [System.Windows.Forms.DragEventHandler]{
foreach ($filename in $_.Data.GetData([Windows.Forms.DataFormats]::FileDrop))
{
$listBox1.Items.Add($filename)
}
}
### Add events to form ###
$listBox1.Add_DragOver($listBox1_DragOver)
$listBox1.Add_DragDrop($listBox1_DragDrop)
#$form.Add_FormClosed($form_FormClosed)
#### Show form and return result ###
$dialogResult = $Form12.ShowDialog()
if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK)
{
$Form12.SuspendLayout()
[array]$items = $listbox1.Items| sort -CaseSensitive
if ($items.Count -gt 1){
$items
}
ELSE
{
[string]$items[0]
}
$Form12.Close() | out-null
}
After lot's of research i came across the code below, to summarise what it does.
it will let you drag and drop files out of Outlook, it will then copy and paste that into a folder which then gives you the path and file name. it is pretty cool so if anyone else is stuck here is my working script and how i implemented it to my form
######################################## This is For Drag And Drop
$Listbox1.AllowDrop = $true
$Listbox1.Add_DragDrop({
if ($_.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop)) {
foreach ($FileName in $_.Data.GetData([Windows.Forms.DataFormats]::FileDrop)) {
Copy-Item -Path $FileName -Destination $textbox6.text -Force
$Listbox1.Items.Add($FileName)
}
}
else
{
$Outlook = New-Object -ComObject Outlook.Application;
$Selection = $Outlook.ActiveExplorer().Selection
foreach ($Item in $Selection) {
foreach ($Attachment in $Item.Attachments) {
Write-Verbose $Attachment.FileName
$Name = Join-Path -Path $textbox6.text-ChildPath $Attachment.FileName
$Attachment.SaveAsFile($Name)
}
}
}
})
$Listbox1.Add_DragEnter({$_.Effect = [Windows.Forms.DragDropEffects]::Copy})
$Form12.Controls.Add($Listbox1)
# Activate the form
[void] $Form12.ShowDialog()
i had to change my form a little by adding an Input Folder Button & Textbox6 to show the text of the folder that has been selected this is important as the Script above needs a directory to save the files to, please see code below.
###################################### Get Folder Using Folder Browser and output text into textbox
$button4_Click = {
$folderBrowserDialog3=New-Object System.Windows.Forms.FolderBrowserDialog
[void]$folderBrowserDialog3.ShowDialog()
$folderBrowserDialog3.SelectedPath
$textBox6.Text = $folderBrowserDialog3.SelectedPath
}
$button6_Click = {
$folderBrowserDialog1=New-Object System.Windows.Forms.FolderBrowserDialog
[void]$folderBrowserDialog1.ShowDialog()
$folderBrowserDialog1.SelectedPath
$textBox2.Text = $folderBrowserDialog1.SelectedPath
}
$button7_Click = {
$folderBrowserDialog2=New-Object System.Windows.Forms.FolderBrowserDialog
[void]$folderBrowserDialog2.ShowDialog()
$folderBrowserDialog2.SelectedPath
$textBox3.Text = $folderBrowserDialog2.SelectedPath
}
after i got that to work the thing that bugged me the most was i couldn't see the files in the listbox so i added a button to do exactly that please see code below
###################################### Shows Files In ListBox 1
$button5_Click = {
#$textbox8.Text = ""
$listBox1.Items.Clear()
$items = Get-ChildItem $textbox6.Text
ForEach($item in $items){
$listBox1.Items.Add($item.FullName)
}
}
as you can see i have added $listbox1.Items.Clear() this will enable you to keep clicking the show files button without it duplicating the path and file in the listbox
the final result of my Form has come out great please see my image layout below, if anyone needs help to get it working on your own Form please comment and i will do my best to help.

How Can I change the color of the Text on TabPage?

What properties need to be applied to change the forecolor and backcolor of the text on Tabpage?
See Picture:
https://imgur.com/a/Su8aSg7
Here is my Code:
$TabControl_Main = New-Object System.Windows.Forms.TabControl
$TabControl_Main.Location = New-Object System.Drawing.Size(20,550)
$TabControl_Main.Size = New-Object System.Drawing.Size(850,270)
$form_MainForm.Controls.Add($TabControl_Main)
$TabPage1 = New-Object System.Windows.Forms.TabPage
$TabPage1.Location = New-Object System.Drawing.Size(20,550)
$TabPage1.Size = New-Object System.Drawing.Size(850,270)
$TabPage1.Text = "Processes"
$TabControl_Main.Controls.Add($TabPage1)
You have to create an event and draw the area. Here is some code based on this example in c#, credits #Fun Mun Pieng.
# assign a color for each tab
$PageColor = #{0 = "lightgreen";
1 = "yellow";
2 = "lightblue"}
# define the event
$tabControl_Drawing = {
param([object]$Sender, [System.EventArgs]$e)
$Background = new-object Drawing.SolidBrush $PageColor[$e.Index]
$Foreground = new-object Drawing.SolidBrush black
$tabGraphics = $e.Graphics
$tabBounds = $e.Bounds
$tabGraphics.FillRectangle($Background,$tabBounds)
$tabTextSize = $tabGraphics.MeasureString($sender.TabPages[$e.Index].text, $e.Font)
$tabGraphics.DrawString($Sender.TabPages[$e.Index].Text,$e.Font,$Foreground,$tabBounds.Left + ($tabBounds.Width - $tabTextSize.Width) / 2,$tabBounds.Top + ($tabBounds.Height -$tabTextSize.Height) / 2 +1)
$e.DrawFocusRectangle()
}
# add the event
$TabControl_Main.add_DrawItem($tabControl_Drawing)
A little easier to use is HotTrack:
$TabControl_Main.HotTrack = $true
You will see the effect when you execute your script with powershell instead of powershell ISE.
BackColor does nothing. To use the words of MSDN:
BackColor > This member is not meaningful for this control.
edit: added the code.

Windows Forms: Cell Wrapping mode to display adaptive ellipsis

I'm using a Windows Forms Datagridview to diplay some (long) text. (The code is PowerShell, but the problem is related to the Cell Wrapping mode)
$TestGridView = New-Object System.Windows.Forms.DataGridView -Property #{
Name="TestDataGridView"
AllowUserToAddRows = $False
AllowUserToDeleteRows = $False
Location = "14,225"
Size = "1041,328"
TabIndex = 1
DefaultCellStyle= #{WrapMode ='True'}
RowHeadersVisible=$False
AutoSizeColumnsMode='Fill'
AutoSizeRowsMode = 'AllCells'
Anchor = 'Left, Right, Top, Bottom'
DefaultCellStyle.Padding = new-object Windows.Forms.Padding -a 2
}
I'm using Cell Wrapping and AutosizeRowMode, but I have found no way to have a DGV cell display up to a certain point, then truncate by ellipsis when the cell size is exceeded.
What I'd want to accomplish is this: (graphic edit)
but so far, I've been unable to do so:
WrapMode=False,AutoSizeRowsMode=AllCells
truncates by ellipsis, but removes all CRLFs and displays just one line
WrapMode=False,AutoSizeRowsMode=None
Row height set to desired value, but otherwise truncation same as above
WrapMode=True,AutoSizeRowsMode=AllCells
No truncation, all the text is displayed and the cell is adapted in height to fit all the text
WrapMode=True,AutoSizeRowsMode=None
Height stays as it shoulds, but no truncation is performed.
What I'm trying to accomplish is to have the rows adjust in size up to a maximum, after which text should be truncated by ellipsis [...]
I'have already tried truncating the content, but it has the adverse side effect that when the user COPY the cell content, the cell content is missing all the truncated part (of course) so it is not a viable option..
Many thanks
You need to handle CellPainting event yourself and draw text yourself by applying word-wrapping and ellipsis:
Function dgv_CellPainting{[CmdletBinding()]param(
[parameter()]
[Object]$sender,
[parameter()]
[System.Windows.Forms.DataGridViewCellPaintingEventArgs]$e
)
#Don't process if it's not the column which we want or it's a header row
if (($e.ColumnIndex -ne 0) -or ($e.RowIndex -lt 0)){ return }
#Paint all parts but text
$e.Paint($e.CellBounds, [System.Windows.Forms.DataGridViewPaintParts]::All `
-band (-bnot([System.Windows.Forms.DataGridViewPaintParts]::ContentForeground)))
$color = $e.CellStyle.ForeColor
if ($sender.Rows[$e.RowIndex].Cells[$e.ColumnIndex].Selected -eq $true){
$color = $e.CellStyle.SelectionForeColor}
#Paint text
[System.Windows.Forms.TextRenderer]::DrawText($e.Graphics, $e.FormattedValue, `
$e.CellStyle.Font, $e.CellBounds, $color, `
[System.Windows.Forms.TextFormatFlags]::VerticalCenter -bor `
[System.Windows.Forms.TextFormatFlags]::TextBoxControl -bor `
[System.Windows.Forms.TextFormatFlags]::WordBreak -bor `
[System.Windows.Forms.TextFormatFlags]::EndEllipsis)
#Event handled, stop default processing
$e.Handled = $true
}
Full Example
Here is a full working PowerShell example. To see the effect, you can try to resize the column or row.
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.Add_Load({form_Load -sender $form -e $_})
$dgv = New-Object System.Windows.Forms.DataGridView
$dgv.Dock = [System.Windows.Forms.DockStyle]::Fill
$dgv.RowTemplate.Height = 50
$dgv.Add_CellPainting({dgv_CellPainting -sender $dgv -e $_})
$form.Controls.Add($dgv)
Function form_Load {[CmdletBinding()]param(
[parameter()]
[Object]$sender,
[parameter()]
[System.EventArgs]$e
)
$dt = New-Object System.Data.DataTable
$dt.Columns.Add("Column1")
$dt.Rows.Add("Lorem ipsum dolor sit amet, " + `
"wisi fierent fabellas pri et, eum aeterno volumus no.")
$dgv.DataSource = $dt
#Enable multiline editing
$dgv.Columns[0].DefaultCellStyle.WrapMode = `
[System.Windows.Forms.DataGridViewTriState]::True
}
Function dgv_CellPainting{[CmdletBinding()]param(
[parameter()]
[Object]$sender,
[parameter()]
[System.Windows.Forms.DataGridViewCellPaintingEventArgs]$e
)
#Don't process if it's not the column which we want or it's a header row
if (($e.ColumnIndex -ne 0) -or ($e.RowIndex -lt 0)){ return }
#Paint all parts but text
$e.Paint($e.CellBounds, [System.Windows.Forms.DataGridViewPaintParts]::All `
-band (-bnot([System.Windows.Forms.DataGridViewPaintParts]::ContentForeground)))
$color = $e.CellStyle.ForeColor
if ($sender.Rows[$e.RowIndex].Cells[$e.ColumnIndex].Selected -eq $true){
$color = $e.CellStyle.SelectionForeColor}
#Paint text
[System.Windows.Forms.TextRenderer]::DrawText($e.Graphics, $e.FormattedValue, `
$e.CellStyle.Font, $e.CellBounds, $color, `
[System.Windows.Forms.TextFormatFlags]::VerticalCenter -bor `
[System.Windows.Forms.TextFormatFlags]::TextBoxControl -bor `
[System.Windows.Forms.TextFormatFlags]::WordBreak -bor `
[System.Windows.Forms.TextFormatFlags]::EndEllipsis)
#Event handled, stop default processing
$e.Handled = $true
}
$form.ShowDialog()
$form.Dispose()

In PowerShell and Windows Forms, how to capture user-entered data using multiple dynamically created input controls

I have a script I need to use for multiple parameter data collection, as follows:
function Build-FormPanel($FormTitle){
Add-Type -Assembly System.Windows.Forms ## Load the Windows Forms assembly
## Create the main form
$form = New-Object Windows.Forms.Form
$form.FormBorderStyle = "FixedToolWindow"
$form.Text = $FormTitle
$form.AutoScroll = $True
$form.StartPosition = "CenterScreen"
$form.Width = 740 ; $form.Height = 480 # Make the form wider
#Add Buttons- ## Create the button panel to hold the OK and Cancel buttons
$buttonPanel = New-Object Windows.Forms.Panel
$buttonPanel.Size = New-Object Drawing.Size #(400,40)
$buttonPanel.Dock = "Bottom"
$cancelButton = New-Object Windows.Forms.Button
$cancelButton.Top = $buttonPanel.Height - $cancelButton.Height - 10; $cancelButton.Left = $buttonPanel.Width - $cancelButton.Width - 10
$cancelButton.Text = "Cancel"
$cancelButton.DialogResult = "Cancel"
$cancelButton.Anchor = "Right"
## Create the OK button, which will anchor to the left of Cancel
$okButton = New-Object Windows.Forms.Button
$okButton.Top = $cancelButton.Top ; $okButton.Left = $cancelButton.Left - $okButton.Width - 5
$okButton.Text = "Ok"
$okButton.DialogResult = "Ok"
$okButton.Anchor = "Right"
## Add the buttons to the button panel
$buttonPanel.Controls.Add($okButton)
$buttonPanel.Controls.Add($cancelButton)
## Add the button panel to the form
$form.Controls.Add($buttonPanel)
## Set Default actions for the buttons
$form.AcceptButton = $okButton # ENTER = Ok
$form.CancelButton = $cancelButton # ESCAPE = Cancel
return $form
}
$LeftMargin = 25
$BottomMargin = 30
$i = 0
$form = Build-FormPanel "Please update server configurations"
foreach($param in $hash){#Where $hash is an "dictionary" of key/value pairs
$k = $param.Key
$v = $param.Value
$lblValue = New-Object System.Windows.Forms.Label
$lblValue.Text = $k+":"
$lblValue.Top = 20*$i ; $lblValue.Left = $LeftMargin; $lblValue.Width=150 ;$lblValue.AutoSize = $true
$form.Controls.Add($lblValue) # Add to Form
#
$txtValue = New-Object Windows.Forms.TextBox
$txtValue.Top = 20*$i; $txtValue.Left = 160; $txtValue.Width = 320;
$txtValue.Text = $v
$form.Controls.Add($txtValue) # Add to Form
$i++
}
$form.Topmost = $True
$form.Add_Shown( { $form.Activate(); } )
$result = $form.ShowDialog()
if($result -eq "OK")
{
$j = 0;
foreach($param in $hash){
${"txtValue_$j"}.Text
$j++
}
}
else {Write-Host "Cancel"}
Basically, this works OK to display the form and inputs. But after submission, I am unable to capture all the user inputs. Only the last input value is captured, obviously because the variables get overwritten in the loop.
How can I achieve capturing the data as described?
The issue as you have mentioned is because of the its getting overwritten.
I can give you a logical set off.
YOu can use it in a loop and in the loop , you store all the data either in array or if its dynamic then you can use arraylist by using
New-Object System.Collections.ArrayList
. But recommended is to create PSCustomObject , store in that and add that to the arraylist each time.
Finally you can get the output captured in the arraylist.
Further you can try making the arraylist Global so that it will be available for the entire script.
Hope it helps.

Resources