A part of this question concerns doing the reverse of the following one:
VB.net get location of userControl in another container
No matter what I do, I cannot seem to pinpoint the location of my chart inside a userform (D inside A), therefore what I save is something very different than the chart alone.
So, provided that my userform is too like this:
I need to save a screenshot of D, being D a polar chart with a title and a legend.
My code is:
Dim PicFile As String = FilesFolder & "Pic.png"
Dim myBounds As Rectangle = A.D.Bounds
Dim BitMap As New Bitmap(myBounds.Width, myBounds.Height)
Dim PtChart As Point
PtChart = A.PointToScreen(New Point(0, 0))
PtChart = A.D.PointToClient(PtChart)
Using g As Graphics = Graphics.FromImage(BitMap)
g.CopyFromScreen(PtChart, Point.Empty, myBounds.Size)
End Using
BitMap.Save(PicFile, System.Drawing.Imaging.ImageFormat.Png)
Why does this not save the chart (D) correctly?
BONUS: how can I make it work even when I have another application open (say, internet browser) on top of the userform?
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.
I want to draw TextBoxes/Labels on my form in code and connect them with lines - based on data that I have stored in a datatable ("treedata"). If I use the following code everything works fine:
For i = 0 To treedata.Rows.Count - 1
Dim tb As New TextBox
hor = treedata.Rows(i)(11)
vern = ver + 120 * treedata.Rows(i)(4)
tb.Text = "sometext"
tb.Location = New Point(hor, vern)
Form8.Controls.Add(tb)
posofmodif = treedata.Rows(i)(10)
vero = treedata.Rows(i)(6)
Dim myPen As New System.Drawing.Pen(System.Drawing.Color.Green)
Dim formGraphics As System.Drawing.Graphics
myPen.SetLineCap(LineCap.RoundAnchor, LineCap.ArrowAnchor, DashCap.Flat)
formGraphics = Form8.CreateGraphics()
formGraphics.DrawLine(myPen, Convert.ToSingle(posofmodif), Convert.ToSingle(vero), Convert.ToSingle(hor), Convert.ToSingle(vern))
myPen.Dispose()
formGraphics.Dispose()
Next
However I would like to use labels instead of TextBoxes because it makes no sense to use heavier TextBoxes in this case. But when I simply replace
Dim tb As New TextBox
by
Dim tb As New Label
the labels do appear on the Form as expected but the lines connecting them appear only for a moment and then turn invisible.
I first thought that the problem might be caused by labels being over or below the lines but even when I make sure that no line is crossing any label it happens.
Does anyone have an idea what I could do to avoid this?
This is your problem: Form8.CreateGraphics(). That method is volatile, as it creates a Graphics instance that does not survive the scope in which it's used.
You need to be using the Paint event for whatever control on which you intend to draw. The form, the label...whatever that is. The Paint event provides a Graphics object for you to use, and it gets called whenever the drawing needs to be refreshed.
Because the event fires frequently, you need to be mindful of what you do there. Heavy lifting in a Paint handler can slow an app down considerably.
I use the following VB.NET (VSTO) code to add a shape in MS-Word,
Dim app As Word.Application = Globals.ThisAddIn.Application
Dim doc As Word.Document = app.ActiveDocument
Dim left As Single = CSng(Convert.ToDouble(app.Selection.Information(Word.WdInformation.wdHorizontalPositionRelativeToPage)))
Dim top As Single = CSng(Convert.ToDouble(app.Selection.Information(Word.WdInformation.wdVerticalPositionRelativeToPage)))
Dim shape As Word.Shape = doc.Shapes.AddShape(1, left, top, 225.1F, 224.5F)
shape.Fill.BackColor.RGB = ColorTranslator.ToOle(Color.Transparent)
shape.Fill.Visible = Microsoft.Office.Core.MsoTriState.msoFalse
shape.Fill.Transparency = 0.0F
shape.Line.Transparency = 0.0F
shape.Line.Visible = Microsoft.Office.Core.MsoTriState.msoFalse
What this code does is, it adds a rectangle shape at cursor point and makes it transparent (both background and line).
Now I like to change the absolute positions' type. To explain further, when you select the rectangle shape, then if you select the Ribbon tab Format > Position > More Layout Options... as shown in the image below,
It will open the following dialog,
In the above dialog I like to change Column and Paragraph marked by the red rectangles into the type Margin. How to do this by code?
Word provides a Macro recorder. You may use it to get the code generated for you in the background. Thus, you will find what properties and methods exactly should be used to get the job done. See Record or run a macro for more information.
The solution to this was solved in the link below,
https://social.msdn.microsoft.com/Forums/vstudio/en-US/e69584d7-24fe-4396-9a82-26b7dae02584/word-vsto-change-the-absolute-positions-type?forum=vsto
I have an application written in VB.NET that reads data from a file and displays the data on the screen.
Depending on the data in the file, the program has a TabControl with up to 3 tabs and each tab in turn has a DataGridView for displaying data. For example I have a TabControl that has a tab called "Saturday" and a tab called "Sunday".
The problem I am having is that when I read data from a file, the program displays all the data on the Saturday's tab grid because I am not sure how to reference the Grid on the Sunday tab.
To add the DataGridView I am using the following code:
Grid = New DataGridView
Grid.Dock = DockStyle.Fill
Grid.Name = "Grid" & TabControl.SelectedIndex
Grid.Tag = "Grid" & TabControl.SelectedIndex
And this is how I am reading the data in:
If reader.GetAttribute("controltype") = "Tab" Then
SelectedTab = reader.Name
End If
If reader.Name = "cell" Then
y = y + 1
Grid.Rows(i).Cells(y).Style.BackColor = Color.FromName(reader.ReadElementString("cell"))
End If
What I almost want to do is something like (pseudocode):
SelectedTab.Grid.Rows(i).Cells(y).Style.BackColor = Color.FromName(reader.ReadElementString("cell"))
However when I use the above code it complains:
'Grid' is not a member of 'String'
I hope you understand the issue. Let me know if you need clarification
Your code is a little unclear. However, it appears to me that the following line:
If reader.GetAttribute("controltype") = "Tab" Then
SelectedTab = reader.Name
End If
is creating at least one problem. It looks like you are attempting to refer to a Tabpage control by the string representation of its name, but unless I missed something, what that line is actually doing is trying to make a tabpage control type("SelectedTab") refer to a string type. If that is the case, then you will want to try this instead:
If reader.GetAttribute("controltype") = "Tab" Then
TabControl1.SelectedTab = TabControl1.TabPages(reader.name)
End If
It is a little hard to tell from the code you have posted, but that might get you headed down the right path.
++++++++++++
UPDATE: It appears from your code that you are naming each DGV control by appending the index of the tab on which it is located to the string "grid." I am going to assume that you are using a class member variable named "SelectedTab" to represent the current tab selected in the control. I will assume that at the top of your class you have done something like this:
'Form-or-class scoped memebr variables:
Private SelectedTab As TabPage
Private SelectedGrid As DataGridView
You should be able to refer to the active grid control using something like this:
Private Sub TabControl1_SelectedIndexChanged(sender As Object, e As System.EventArgs) Handles TabControl1.SelectedIndexChanged
' Set SelectedTab member variable to refer to the new selected tab page:
SelectedTab = TabControl1.SelectedTab
' Set the SelectedGrid to refer to the grid control hosted on the selected tab page:
SelectedGrid = TabControl1.SelectedTab.Controls("Grid" & TabControl1.SelectedIndex.ToString())
End Sub
From here, you should be able to use the member variable for SelectedGrid to refer to the grid present on which ever tab page is selected in your tab control.
It is challenging to address your concerns with only fragments of your code. If you have additional difficulties, please post more of your code, so we can better see what else is going on.
Hope that helps!
Okay, I would go about something like this. Maybe you can simply use a DataSet to load the XML data in one line (if they have been saved with DataSet.WriteXML before).
Dim ds As New DataSet
Dim p As TabPage
Dim gv As DataGridView
ds.ReadXml("F:\testdata.xml")
For i As Integer = TabControl1.TabPages.Count - 1 To 0 Step -1
TabControl1.TabPages.RemoveAt(i)
Next
For Each dt As DataTable In ds.Tables
p = New TabPage(dt.TableName)
gv = New DataGridView
' ... configure the gv here...
gv.AutoGenerateColumns = True
gv.Dock = DockStyle.Fill
' ...
gv.DataSource = dt
TabControl1.TabPages.Add(p)
p.Controls.Add(gv)
Next