Goal
To be able to paint rows in my DataGridViews in order to help a user better understand what is going on in the application. Take this image for an example:
Light red = Deleted
Yellow = Relationship to the Option Type Select (top left)
Bright Red = Deleted & Related to the Option Type Selected (top left)
Problem
When I go up / down in my Option Types DataGridView, it will paint the Mandatory and Additional sections depending on which Option Type I choose. After doing this for a while (70 or so times), I get the generic error in the GDI.
As you can see in the screenshot above, one of the additional edit buttons are not displayed correctly (this is where the error occurs). It is always on the following line:
ico = Icon.FromHandle(bmpFind.GetHicon)
See complete code below:
Code
Private Sub dgvAdditionalOptions_CellPainting(sender As Object, e As System.Windows.Forms.DataGridViewCellPaintingEventArgs) Handles dgvAdditionalOptions.CellPainting
'Dim bmpFind As Bitmap
Dim drRow As HunterManagement.dtConveyorFunctionAdditionalOptionsRow
Dim myOptn As clsOptions
If dgvAdditionalOptions.Columns(e.ColumnIndex).Name = "ColBtnEditAdditional" AndAlso e.RowIndex >= 0 Then
drRow = dsHunterManagement.dtConveyorFunctionAdditionalOptions.FindByPK_ConveyorFunctionAdditionalOption(dgvAdditionalOptions.Rows(e.RowIndex).Cells("PK_ConveyorFunctionAdditionalOption").Value)
myOptn = New clsOptions(CInt(drRow.FK_Option))
e.Paint(e.CellBounds, DataGridViewPaintParts.All)
If Not drRow Is Nothing AndAlso myOptn.InDrawing Then
Using bmpFind As Bitmap = My.Resources.Edit_16x16_2 'Use 16x16 PNG / BitMap images
Using ico As Icon = Icon.FromHandle(bmpFind.GetHicon)
e.Graphics.DrawIcon(ico, e.CellBounds.Left + 3, e.CellBounds.Top + 2.5)
e.Handled = True
End Using
End Using
Else
Using bmpFind As Bitmap = My.Resources.Edit_Disabled_16x16_2 'Use 16x16 PNG / BitMap images
Using ico As Icon = Icon.FromHandle(bmpFind.GetHicon)
e.Graphics.DrawIcon(ico, e.CellBounds.Left + 3, e.CellBounds.Top + 2.5)
e.Handled = True
End Using
End Using
End If
ElseIf dgvAdditionalOptions.Columns(e.ColumnIndex).Name = "ColBtnDeleteAdditional" AndAlso e.RowIndex >= 0 Then
e.Paint(e.CellBounds, DataGridViewPaintParts.All)
Using bmpFind As Bitmap = My.Resources.Delete_16x16 'Use 16x16 PNG / BitMap images
Using ico As Icon = Icon.FromHandle(bmpFind.GetHicon)
e.Graphics.DrawIcon(ico, e.CellBounds.Left + 2, e.CellBounds.Top + 2.5)
e.Handled = True
End Using
End Using
End If
End Sub
I tried searching this problem and it is all related to 'saving' an image... I am not saving an image though, I am only getting the icon of a BMP image. I must be forgetting to dispose something.
Any ideas?
Typically this error is caused my a gdi+ leak. You need to Dispose the Icon which is a gdi+ resource. Add ico.Dispose once you're done with it.
When using Icon.FromHandle, you need to manually clean it up via DestroyIcon API.
When using this method you must dispose of the resulting icon using
the DestroyIcon method in the Win32 API to ensure the resources are
released.
Refer this answer, Hans gives a workaround for this problem.
Also note that you must Dispose the Bitmap also when you get it from Resources.YourBitmap because Resources.YourBitmap basically creates a new bitmap everytime it is called. Bitmap is also a gdi+ resource, you ought to dispose that also.
As #Hanspassant noted in comments prefer Using statements to Dispose the resources. They provide a convenient and reliable way of disposing a resource.
Related
This question already has answers here:
How can I save a Panel in my form as a picture?
(2 answers)
How to print hidden and visible content of a Container with ScrollBars
(1 answer)
Closed last year.
I'm writing a simple utility to help teachers create sentence diagrams, which are contained in a standard Panel control. To save the diagram I am doing this:
Private Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
dlgSave.DefaultExt = "png"
dlgSave.Filter = "PNG files (*.png)|*.png|All files (*.*)|*.*"
Dim result As DialogResult = dlgSave.ShowDialog()
If result <> DialogResult.Cancel Then
Dim bounds As Rectangle = DisplayPanel.Bounds
Dim pt As Point = DisplayPanel.PointToScreen(bounds.Location)
Dim bitmap As New Bitmap(bounds.Width, bounds.Height, Imaging.PixelFormat.Format32bppArgb)
Using g As Graphics = Graphics.FromImage(bitmap)
g.CopyFromScreen(New Point(pt.X, pt.Y), Point.Empty, bounds.Size, CopyPixelOperation.SourceCopy)
End Using
bitmap.Save(dlgSave.FileName, Imaging.ImageFormat.Png)
End If
End Sub
The result is an incomplete image. Here's what the form with the panel looks like and what the saved image looks like:
Complete form
Incomplete copy of panel
I should mention that the text in the panel is actually contained in label controls, so it wouldn't surprise me if that wasn't included and I would need to find a fix. But I'm puzzled as to why the entire panel client area (at least) isn't getting into the saved image. Any help would be greatly appreciated.
Edit: fixed thanks to a kind user who pointed me to a similar post. Turns out I should have been using DrawToBitmap and resetting the bounds to 0, like so
Dim bounds As Rectangle = DisplayPanel.Bounds
Dim bmp As Bitmap = New Bitmap(DisplayPanel.Width, DisplayPanel.Height)
bounds.X = 0
bounds.Y = 0
DisplayPanel.DrawToBitmap(bmp, bounds)
bmp.Save(dlgSave.FileName, Imaging.ImageFormat.Png)
New edit: the above works for the visible area, but not if content extends beyond it. Jimi's solution (see comments, and many thanks) covers the entire scrollable area but does not include lines drawn. If I can fix this I'll post result in case anyone finds it useful.
New Edit: solution posted in comments.
I want to take a screenshot of a RichTextBox using the following code.
The problem is it takes a screenshot of another part of the Form:
Dim memoryImage As Bitmap
Dim myGraphics As Graphics = Me.CreateGraphics()
Dim s As Size = RichTextBox2.Size
memoryImage = New Bitmap(s.Width, s.Height, myGraphics)
Dim memoryGraphics As Graphics = Graphics.FromImage(memoryImage)
memoryGraphics.CopyFromScreen(RichTextBox2.Bounds.X, RichTextBox2.Bounds.Y, 0, 0, s)
memoryImage.Save(audiooutputfolder & name & ".png")
Graphics.CopyFromScreen() requires that you specify screen coordinates.
You can transform local coordinates into screen coordinates using the Control.RectangleToScreen() and Control.PointToScreen() methods.
Other methods do the opposite, see the Docs.
To compute the client area of a Control in screen coordinates, you can use its RectangleToScreen() method and pass the value of the ClientRectangle property:
Dim clientRectToScreen = [Control].RectangleToScreen([Control].ClientRectangle)
To include the non-client area (e.g., the borders of a Control, including the Scrollbars, if any), you need the screen coordinates of its Bounds.
There are different ways to do this. A simple method is to ask the Parent of a Control to get them, passing to the Parent's RectangleToScreen() method the Bounds of a child Control.
If you want to print a Form, which is a Top-Level Control, so it has no Parent, just use its Bounds directly: these measures already express screen coordinates.
It's shown in the ControlToBitmap() method:
Private Function ControlToBitmap(ctrl As Control, clientAreaOnly As Boolean) As Bitmap
If ctrl Is Nothing Then Return Nothing
Dim rect As Rectangle
If clientAreaOnly Then
rect = ctrl.RectangleToScreen(ctrl.ClientRectangle)
Else
rect = If(ctrl.Parent Is Nothing, ctrl.Bounds, ctrl.Parent.RectangleToScreen(ctrl.Bounds))
End If
Dim img As New Bitmap(rect.Width, rect.Height)
Using g As Graphics = Graphics.FromImage(img)
g.CopyFromScreen(rect.Location, Point.Empty, img.Size)
End Using
Return img
End Function
To take a screenshot of a Control, call this method, passing the Control you want to print to a Bitmap and specify whether you just want its content (the client area) or you want to include the non-client area (for example, if the control to print is a Form, you want to include the Caption and borders).
Important: use Path.Combine() to build a path:
Path.Combine(audiooutputfolder, $"{imageName}.png"
if string interpolation is not available ($"{variable} other parts"), you can glue the file extension to the file name:
Path.Combine(audiooutputfolder, imageName & ".png")
' Get the screenshot, client area only
Dim controlImage = ControlToBitmap(RichTextBox2, True)
' Save the image to the specified Path using the default PNG format
controlImage.Save(Path.Combine(audiooutputfolder, $"{imageName}.png"), ImageFormat.Png)
' [...] when done with the bitmap
controlImage.Dispose()
Side note:
If your app is not DpiAware, you may get wrong screen coordinates.
See these notes about this.
Its a bit complicated and I will try my best to explain what I want to achieve.
I've already worked a bit with bitmaps and copy from screen functions.
This all worked very well. But these bitmaps are always rectangles and I would like to use my own bitmap if possible.
As an example:
Somewhere at position x, y on my screen is a map
This map has several paths and junctions.
There are also houses and things that don't interest me, I only focus on the paths in between.
Now I want to scan this map for Argb Pixels using a bitmap and Copy from screen function. I have already done this and it works great, but instead of only scanning the paths for pixels, the whole map is scanned for pixels. But I only want these paths to be scanned.
I have this map as a template and could make a screenshot of it. Can I now import this map into Photoshop, color the houses and everything that I don't need black and make the paths transparent and import them as bitmap so that the Copy From Screen function only scans the paths or is that even possible? I haven't found anything for vb.net if someone could lead me in a right direction I would be very thankful :)
This is the code I got so far - simple pixelsearch function:
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim xd3 = Color.FromArgb(255, 100, 100, 100) 'Searching for this color on the screen
Dim b As New Bitmap(2210, 1100) 'Position of Bitmap
Dim g As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(b)
g.CopyFromScreen(Me.Left, Me.Top, 0, 0, b.Size) 'Searching on Screen
For i = 0 To (Me.Width - 0) 'searching each pixel
For j = 0 To (Me.Height - 0) 'searching each pixel
If b.GetPixel(i, j) = xd3 Then 'If pixel has same color that im searching for it will show a messagebox true
MessageBox.Show("true")
End If
Next
Next
End Sub
Is there any way to include a .swf on my form and also preserve its original transparency? That is, so you can see any controls 'behind it' (obviously in the transparent areas). It's looking unlikely as I understand vb can't control the flash activex control (and hence the control's rendered background?) but wondering whether there's any workarounds?
relevant code thusfar:
Dim flash1 As New AxShockwaveFlash
.....
flash1.Location = New System.Drawing.Point(300, 23)
Me.Controls.Add(flash1)
flash1.Movie = "C:\Users\Steve\Scripts\Projects\CPWizBiz\Assets\Test SWFs\Artwork4.swf"
flash1.Size = New System.Drawing.Size(192, 400)
flash1.Play()
In the absence of any other solutions, here's what I did:
Basically, I just created a new form with a transparetn background. You then overlay this onto your main form (you can link any forms to move and resize with each other etc). Then you set the flash background to match the form transparency key and you're done. Brief, illustrative code:
Public Sub MarqueeFlash_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.TransparencyKey = Color.FromArgb(1, 4, 3)
Me.BackColor = Color.FromArgb(1, 4, 3)
dim FlashCtrl as As New AxShockwaveFlash
Controls.Add(FlashCtrl)
With FlashCtrl
.BGColor = Me.BackColor.R.ToString("X2") & Me.BackColor.G.ToString("X2") & Me.BackColor.B.ToString("X2")
.Movie = IndexedDR(0).Item("File")
.Location = New Point(10,10)
End With
FlashCtrl.Play()
FlashCtrl.Size = New Size(400,300)
end sub
i am using vb.net 2.0 and i would like to set an icon or image in a DataGridViewButtonColumn. I know the class has no member for it. Anyone has an idea?
Best answer is from http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/b0dfb55c-8bad-4c2d-aa72-36d8c138484e/
But as usual, you Google vb.Net and get answers in C#. I use C# to vb.net tool on the developerfusionwebsite to help me convert to vb.net but usually end up changing a fair amount.
Once you have a pointer this one is quite easy really.
In the dataGridView_CellPainting event add something like this
Private Sub InvoiceLinesDataGridView_CellPainting(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellPaintingEventArgs) Handles InvoiceLinesDataGridView.CellPainting
If e.ColumnIndex = 3 AndAlso e.RowIndex >= 0 Then
e.Paint(e.CellBounds, DataGridViewPaintParts.All)
Dim bmpFind As Bitmap = My.Resources.binoc16_h1
Dim ico As Icon = Icon.FromHandle(bmpFind.GetHicon)
e.Graphics.DrawIcon(ico, e.CellBounds.Left + 3, e.CellBounds.Top + 3)
e.Handled = True
End If
End Sub
Just to explain this a little, I wanted to use a resource which is a bitmap so I am converting that to an icon also. This works really well for me and I get a proper button column with an image. The column index is a bit rough as that could change so I was looking to refer using the column name - shouldn't be too hard but you get the idea. SO much easier than the other options I have see around that get you making an extended custom column type.
This really should have just been part of the original control and I cannot fathom why MS crippled the grid so much. I have been trying to use third party controls like Telerik but the originals always seem to be more stable so am now seeing if I can stick with vanilla controls and adding my on extensions where required.
I created a method that can be called in the CellPainting datagridview event.
Public Shared Sub SetImageToDataGridViewButtonColumn_CallInCellPaintingEvent(ByRef img As Bitmap, e As System.Windows.Forms.DataGridViewCellPaintingEventArgs)
e.Paint(e.CellBounds, DataGridViewPaintParts.All & (DataGridViewPaintParts.ContentBackground) & (DataGridViewPaintParts.ContentForeground))
Dim destRect As Rectangle = New Rectangle(e.CellBounds.X + (e.CellBounds.Width - img.Width) / 2, e.CellBounds.Y + (e.CellBounds.Height - img.Height) / 2, img.Width, img.Height)
Dim srcRect As Rectangle = New Rectangle(0, 0, img.Width, img.Height)
e.Graphics.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel)
e.Handled = True
End Sub
In the CellPainting event, call this method passing the image you want on the button and e. Ensure you use some sort of condition to set on the column you need, this example the If specifies column 0. Also notice I have my image in My.Resources:
Private Sub dgv_CellPainting(sender As Object, e As System.Windows.Forms.DataGridViewCellPaintingEventArgs) Handles dgv.CellPainting
If (e.ColumnIndex = 0 And e.RowIndex >= 0) Then
SetImageToDataGridViewButtonColumn_CallInCellPaintingEvent(My.Resources.myImg, e)
End If
End Sub
Tips: I found 16x16 png were perfect for my use. You can resize using http://images.my-addr.com/resize_png_online_tool-free_png_resizer_for_web.php
You can try using a DataGridViewImageColumn add attach an event to the grid's CellContentClick event (in the event process only the events comming from the column/columns that is/are images)