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.
Related
Say for example I draw a box on my screen. The box's X and Y coord will change pretty much all the time. When I am drawing the box and all of it's new position, a new box keeps appearing. I want to draw the same box, and as its location is changed, draw that same box on the new location.
Example :
Box1 : X/Y = 0,0
Box1 (new X/Y) = 0,15
I now have 2 box on my screen.
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
Dim doo As Integer = 1
While doo = 1
For i As Integer = 0 To MonsterCount
Dim xx As Integer = GetPrivateProfileInt("iPC=" & i, "X-Int:", 0, INI)
Dim yy As Integer = GetPrivateProfileInt("iPC=" & i, "Y-Int:", 0, INI)
Box(i) = New Box(xx, yy)
If Box(i).x > -10 And Box(i).y > -10 And Box(0).x <= 1920 And Box(0).y <= 1080 Then
Dim rect As New Rectangle(Box(i).x, Box(i).y, 120, 80)
e.Graphics.DrawRectangle(Pens.Green, rect)
Invalidate()
End If
Next i
Threading.Thread.Sleep(5)
End While
End Sub
That code is very wrong. You need to handle the Paint event of the control you want to draw on and just do your drawing as it should be at that instant. All the data that describes the drawing should be stored in member variables. Any code that changes what needs to be drawn should be outside the Paint event handler. Once it has made changes that need implementing, it should call Invalidate and specify the smallest area that it reasonably can. The next time the control is painted, the Paint event handler will update the drawing and then the invalidated area will be repainted. You might check this out for an example.
In your specific case, you should declare a member variable to store the data required for the box. If the size remains the same then all you need is a Point, otherwise you should keep a Rectangle. Each time the box needs to move, you should store the new value in your field and then call Invalidate twice. The first time you should specify the old Rectangle and the second time you should specify the new Rectangle. By doing that, you ensure that any area that may have changed will be repainted but the rest of the area, which can't have changed, won't be repainted. It's the actual painting to screen, not the drawing code, that is the slow part so you should try to keep that to a minimum. If you really need the repaint done immediately then you can call Update but, otherwise, the new drawing will be displayed the next time the UI thread is free to do so.
After experiencing problems with Visual Basic's printform quality, I have decided to take a different approach. I am going to simulate an ALT-PRINTSCREEN keyboard key-press to capture the form in its current state.
However, before this happens, I need to remove certain elements from the form (such as the border), and after getting the image, I need to replace these elements. I am having some timing/synchronization problems with this, as I am noticing that certain elements are still on the image that has been copied to the clipboard.
I figured this was due to the fact that it takes time to process the changes, and time for Windows to process the simulated "send keys", so I set up sleep timers. I am having mixed results. I notice that if I don't replace the elements afterwords, the clipboard image appears correctly. However, if I do replace them, even if I set up a 20 second sleep timer, they do not appear.
Therefore, I know there is some sort of synchronization problem, but I am not sure how to fix it. Here is the code I am using:
'Make these forms invisble so they are not printed
Logo.Visible = False
MenuStrip1.Visible = False
ReportsButton.Visible = False
pnlmain.BorderStyle = 0 'remove the border
'Sleep
System.Threading.Thread.Sleep(1000)
'simulate an ALT and PRINTSCREEN key click to get ONLY the report
SendKeys.Send("%{PRTSC}")
'Sleep
System.Threading.Thread.Sleep(1000)
'Now, get the image from the clipboard
Dim formImage As System.Drawing.Image
formImage = My.Computer.Clipboard.GetImage()
Logo.Visible = True
MenuStrip1.Visible = True
ReportsButton.Visible = True
pnlmain.BorderStyle = 1
Changing the sleep durations does not seem to change much (unless they are very low).
Any suggestions?
EDIT: here is the code for draw to bitmap (after modifying the code above):
formImage = New Bitmap(Me.Width, Me.Height, Me.CreateGraphics())
Me.DrawToBitmap(formImage, New Rectangle(0, 0, Me.Width, Me.Height))
When it is sent to the print action:
Dim g As Graphics = e.Graphics
'transform the form image so it fits the height and width of the paper, with a 5 px margin
Dim res = printSettings.PrinterResolutions
Dim newSizeDestinationPoints As Point() = {
New Point(5, 5),
New Point(841, 5),
New Point(5, 956)
} 'These values are based on A4 sized paper
'g.DrawImage(formImage, 5, 5)
g.DrawImage(formImage, newSizeDestinationPoints)
The low quality is not coming from the stretch.
I have to create dynamic table layout panel with some controls with auto sized rows and and fixed columns size.
My problem is that i want to show whole checkbox text .
Any help
My code is
Dim textBox2 As New CheckBox()
textBox2.Text = "You forgot to add the ColumnStyles. Do this on a sample form first with the designer. Click the Show All Files icon in the Solution Explorer window. Open the node next to the form and double-click the Designer.vb file. "
textBox2.AutoSize = True
textBox2.Dock = DockStyle.Top
'' textBox2.Size = New Point(200, 90)
Dim lbl1 As New Label()
lbl1.Location = New Point(10, 10)
lbl1.Text = "Yoer.vb"
lbl1.AutoSize = True
lbl1.Location = New Point(120, 50)
lbl1.Dock = DockStyle.Top
'' dynamicTableLayoutPanel.Padding = New Padding(2, 17, 4, 5)
dynamicTableLayoutPanel.Controls.Add(lbl1, 0, 0)
dynamicTableLayoutPanel.Controls.Add(textBox2, 1, 0)
Me.dynamicTableLayoutPanel.SetColumnSpan(textBox2, 5)
If you mean you want the table to size to the controls within it, then:
dynamicTableLayoutPanel.AutoSize = True
I know this is old, but I stumbled across it and figured I'd throw my 2 cents in in case someone else comes along.
Note: I'm using Visual Studio 2015 with .NET 4.6. Functionality may differ between versions.
The problem is that the really long text is not word-wrapping to fit within the table or form. Instead, it is set to Dock = DockStyle.Top. This will cause it to make a single line that continues on and gets clipped, similar to a single-line textbox.
If you want it to automatically word wrap, you'll need to use Dock = DockStyle.Fill. Now, this doesn't completely resolve the problem if your row or table isn't large enough to display the text. Since all of the rows are set to AutoSize, it will only do the bare minimum to fit the control vertically. It doesn't care if text gets clipped off. The end result, using your example code against a 6-column, 10-row table, is this:
Since there isn't a word wrap property, you'll need to manually fit it. Now, to do this, you'll need to change the row to be Absolute instead of AutoSize. To figure out how big to make it, you can pretty much rely on PreferredSize. This reveals a much wider Width than the existing regular Width. From that, we can determine how many lines it would take if we wrap it.
This is what my code ended up looking like:
Dim h As Single = 0
Dim chk As New CheckBox()
chk.Text = "You forgot to add the ColumnStyles. Do this on a sample form first with the designer. Click the Show All Files icon in the Solution Explorer window. Open the node next to the form and double-click the Designer.vb file. "
chk.AutoSize = True
chk.Dock = DockStyle.Fill
Dim lbl1 As New Label()
lbl1.Text = "Yoer.vb"
lbl1.AutoSize = True
lbl1.Dock = DockStyle.Top
dynamicTableLayoutPanel.Controls.Add(lbl1, 0, 0)
dynamicTableLayoutPanel.Controls.Add(chk, 1, 0)
dynamicTableLayoutPanel.SetColumnSpan(chk, 5)
' Find the preferred width, divide by actual, and round up.
' This will be how many lines it should take.
h = Math.Ceiling(chk.PreferredSize.Width / chk.Width)
' Multiply the number of lines by the current height.
h = (h * chk.PreferredSize.Height)
' Absolute size the parent row to match this new height.
dynamicTableLayoutPanel.RowStyles.Item(0) = New RowStyle(SizeType.Absolute, h)
The changes included delaring a height variable, renaming the CheckBox variable, setting its Dock to Fill, removing the Location from lbl1, and adding in size calculation. The output:
This isn't perfect since the height includes the checkbox itself, and the checkbox takes up padding, so there can be too much or too little height calculated. There are other calculations that may need to be considered. But, this is a starting point.
So I have written a short section of code to add 6 pictureboxes to a form in random locations. It adds each picturebox to a collection, then loops through the collection and adds them to the form control. The bizarre issue is that the code only works when I step through it line by line in debug mode. If I just compile and run the code then only 1 picturebox is added to the form, but if I step through the code line by line then all 6 pictureboxes are successfully added to the form in random locations. Can anyone tell me why the hell this happening? It's driving me pretty nuts. Code below:
For i As Integer = 0 To 5
Dim pic As New PictureBox
Dim rnd As New Random
pic.Location = New Point(rnd.Next(200, 300), rnd.Next(200, 300))
pic.Size = New Size(5, 5)
pic.BackColor = Color.White
pic.Visible = True
pic.BringToFront()
_picCollection.Add(pic)
Next
For Each item As PictureBox In _picCollection
Controls.Add(item)
Next
ShowDialog()
Open to suggestions of how to do this better / in a way that actually works properly.
Had to declare RND object outside of loop. Thanks tinstaafl!
After reading this question, I wrote some code to create a label for each attribute of an xml element.
The problem is that when I run the project, my form only displays the first label. I've checked in the immediate window as well as debug window and all of the labels are loaded to the form, but none of them are displayed. Help?
Here's the code that runs when the form loads.
Dim doc As New XmlDocument()
doc.Load("xmlfile")
Dim ability As XmlNode = doc.GetElementsByTagName("ability").Item(0)
Dim numberofLabels = ability.Attributes.Count
ReDim labels(numberofLabels)
For counter As Integer = 0 To numberofLabels - 1
labels(counter) = New Label
labels(counter).Visible = True
labels(counter).Text = ability.Attributes.Item(counter).Name
labels(counter).Location = New System.Drawing.Point(10, 30 + counter * 10)
Me.Controls.Add(labels(counter))
Next
You should be using some layout manager, to help you with control positioning. Doing it manually is not worth the pain. Try using TableLayoutPanel or FlowLayoutPanel. Both can be docked or anchored to a parent control, so everything behaves very smoothly. Otherwise you are looking to write a lot of positioning/resizing code, and then maintaining it later.
Change the value of 10 in the original code line for a new point to a bigger value such as 40, so that new labels could appear separated visually:
labels(counter).Location = New System.Drawing.Point(10 + counter, 30 + counter * 40)