Get Font Width within VB.net Without Measuring a Control? - vb.net

I need to display a table in rich text within a form window. It is only a two column table, so tabbing works fine to line everything up (since RichTextBox does not support RTF tables). However, sometimes the tab stops are incorrect because of non-fixed width fonts.
So, I need a way to measure the pixel width of a particular string with a particular font (Arial 10) and space or tab pad to make sure that everything aligns.
I know about the Graphics.MeaureString method, but since this is in a rich text box, I don't have an initilzed PaintEventArgs variable, and that would seem like overkill to create JUST to measure one string.
From MSDN:
Private Sub MeasureStringMin(ByVal e As PaintEventArgs)
' Set up string.
Dim measureString As String = "Measure String"
Dim stringFont As New Font("Arial", 16)
' Measure string.
Dim stringSize As New SizeF
stringSize = e.Graphics.MeasureString(measureString, stringFont)
' Draw rectangle representing size of string.
e.Graphics.DrawRectangle(New Pen(Color.Red, 1), 0.0F, 0.0F, _
stringSize.Width, stringSize.Height)
' Draw string to screen.
e.Graphics.DrawString(measureString, stringFont, Brushes.Black, _
New PointF(0, 0))
End Sub
So is the best bet just to create a dummy PaintEventArgs instance? If so, what is the best way to do that (since I'll have to call this string measuring method several hundred times)?
Also, I don't really want to have to use a fixed width font - they just don't look as good.

Use this
Dim g as Graphics = richbox.CreateGraphics()
Dim sz as SizeF = g.MeasureString(...)

Related

Get the position where drawn text is truncated by GraphicsPath.DrawString

I have a couple of methods that draw outlined text. The details of this are unimportant, but it serves to illustrate the problem:
(source code from Graphics DrawPath produces unexpected results when rendering text)
Private Sub FillTextSolid(g As Graphics, bounds As RectangleF, text As String, font As Font, fillColor As Color, sf As StringFormat)
Using gp As GraphicsPath = New GraphicsPath(),
brush As New SolidBrush(fillColor)
gp.AddString(text, font.FontFamily, font.Style, font.Size, bounds, sf)
g.FillPath(brush, gp)
End Using
End Sub
correctly converts a long string into one with an ellipsis inside the bounds. E.g.
Manic Miner is a platform video game originally written for the ZX
Spectrum by Matthew Smith and released by Bug-Byte in 1983. It is the
first game in the Miner Willy series and among the early titles in the
platform game genre.
becomes:
Manic Miner is a platform video game originally written for the ZX
Spectrum by Matthew Smith and released by Bug-Byte in 1983. It is the
first game in the Miner...
All well and good. What I need is a way in code to see exactly what part of the full text has been displayed. This will then be used to cycle through the text in the same bounds (almost paging if you will) to display all the text.
I looked at MeasureString but this didn't seem to achieve this. Is there any way I can discern this? In pseudo code, something like:
Dim textShown as string = gp.AddString(text, font.FontFamily, font.Style, font.Size, bounds, sf).TextShown
Thanks
Given the FillTextSolid() method shown before in:
Graphics DrawPath produces unexpected results when rendering text
Private Sub FillTextSolid(g As Graphics, bounds As RectangleF, text As String, font As Font, fillColor As Color)
Using gp As GraphicsPath = New GraphicsPath(),
brush As New SolidBrush(fillColor),
format = New StringFormat(StringFormat.GenericTypographic)
format.Trimming = StringTrimming.EllipsisWord
gp.AddString(text, font.FontFamily, font.Style, font.Size, bounds, StringFormat.GenericTypographic)
g.FillPath(brush, gp)
Dim lastCharPosition = GetPathLastCharPosition(g, format, gp, bounds, text, font)
End Using
End Sub
you can use the current GraphicsPath, Rectangle bounds, Font size and style used for drawing the the text in a graphics context, to calculate the position of the last character drawn and, as a consequence, the last word, if necessary.
I've added in FillTextSolid() a call to the GetPathLastCharPosition() method, which is responsible for the calculation. Pass to the method the objects described, as they're currently configured (these settings can of course change at any time: see the animation at the bottom).
Dim [Last Char Position] = GetPathLastCharPosition(
[Graphics],
[StringFormat],
[GraphicsPath],
[RectangleF],
[String],
[Font]
)
To determine the current last word printed using a GraphicsPath object, you cannot split the string in parts separated by, e.g., a white space, since each char is part of the rendering.
Also to note: for the measure to work as intended, you cannot set the drawing Font size in Points, the Font size must be expressed in pixels.
You could also use Point units, but the GraphicsPath class, when Points are specified, generates (correctly) a Font measure in EMs - considering the Font Cell Ascent and Descent - which is not the same as the Font.Height.
You can of course convert the measure from Ems to Pixels, but it just adds complexity for no good reason (in the context of this question, at least).
See a description of these details and how to calculate the GraphicsPath EMs in:
Properly draw text using GraphicsPath
GetPathLastCharPosition() uses Graphics.MeasureCharacterRanges to measure the bounding rectangle of each char in the Text string, in chunks of 32 chars per iteration. This is because StringFormat.SetMeasurableCharacterRanges only takes a maximum of 32 CharacterRange elements.
So, we take the Text in chunks of 32 chars, get the bounding Region of each and verify whether the Region contains the last Point in the GraphicsPath.
The last Point generated by a GraphicsPath is returned by the GraphicsPath.GetLastPoint().
This procedure only considers text drawn from top to bottom and left to right.
It can be adapted to handle right to left languages.
Of course, you could also ignore the last point and just consider whether a Region bounds fall outside the bounding rectangle of the canvas.
Anyway, when the a Region that contains the last point is found, the method stops and returns the position of the last character in the string that is part of the drawing.
Private Function GetPathLastCharPosition(g As Graphics, format As StringFormat, path As GraphicsPath, bounds As RectangleF, text As String, font As Font) As Integer
Dim textLength As Integer = text.Length
Dim p = path.GetLastPoint()
bounds.Height += font.Height
For charPos As Integer = 0 To text.Length - 1 Step 32
Dim count As Integer = Math.Min(textLength - charPos, 32)
Dim charRanges = Enumerable.Range(charPos, count).Select(Function(c) New CharacterRange(c, 1)).ToArray()
format.SetMeasurableCharacterRanges(charRanges)
Dim regions As Region() = g.MeasureCharacterRanges(text, font, bounds, format)
For r As Integer = 0 To regions.Length - 1
If regions(r).IsVisible(p.X, p.Y) Then
Return charRanges(r).First
End If
Next
Next
Return -1
End Function
This is how it works:
C# version of the method:
private int GetPathLastCharPosition(Graphics g, StringFormat format, GraphicsPath path, RectangleF bounds, string text, Font font)
{
int textLength = text.Length;
var p = path.GetLastPoint();
bounds.Height += font.Height;
for (int charPos = 0; charPos < text.Length; charPos += 32) {
int count = Math.Min(textLength - charPos, 32);
var charRanges = Enumerable.Range(charPos, count).Select(c => new CharacterRange(c, 1)).ToArray();
format.SetMeasurableCharacterRanges(charRanges);
Region[] regions = g.MeasureCharacterRanges(text, font, bounds, format);
for (int r = 0; r < regions.Length; r++) {
if (regions[r].IsVisible(p.X, p.Y)) {
return charRanges[r].First;
}
}
}
return -1;
}

Code to draw works in button click event but not in form load

why can I run the following code faultless under a button even but not under the form load even?
For Each line As String In System.IO.File.ReadAllLines("c:\pos.xml")
If line.Contains("<POS>") = True Then
Dim tagless As String = StripTags(line)
Dim parts As String() = tagless.Split(New Char() {","})
Dim XVAL As Decimal = parts(0)
Dim YVAL As Decimal = parts(1)
'paint markers...
Dim myBrush As New SolidBrush(Color.FromArgb(128, Color.Red))
Dim formGraphics As System.Drawing.Graphics
formGraphics = Me.PictureBox1.CreateGraphics()
formGraphics.FillEllipse(myBrush, New Rectangle(XVAL - 35, YVAL - 35, 70, 70))
myBrush.Dispose()
formGraphics.Dispose()
End If
Next
Here is the striptag function if requierd...
Function StripTags(ByVal html As String) As String
' Remove HTML tags.
Return Regex.Replace(html, "<.*?>", "")
End Function
The right way to draw is hardly ever with CreateGraphics. This will draw something that does not persist. When the area is invalidated such as the form is minimized or another form/app is dragged over it, your shapes will disappear.
You should also turn on Option Strict. There are numerous type errors in the code. For instance there is no Rectangle constructor which takes a Decimal. That is not even the right Class for non integers, but RectangleF doesn't take a Decimal either.
The core problem is that the form is shown at the end of the form load event. So, your code is running/drawing before the form is visible and nothing is shown. Even if the form was already showing, whatever you draw would not be retained if the user minimized the form or moved another window across it.
' form level list to store the data
Private XY As New List(Of PointF) ' pts
Then in form load event, read the data and add to the list
For Each line As String In System.IO.File.ReadAllLines("...")
If line.Contains("<POS>") = True Then
Dim tagless As String = StripTags(line)
' c required under Option Strict
Dim parts As String() = tagless.Split(New Char() {","c})
' convert values to single. create a PointF
Dim ptF As New PointF(Convert.ToSingle(parts(0)), Convert.ToSingle(parts(1)))
' add to list
XY.Add(ptF)
End If
Next
The next thing that happens should be the form being shown and the paint event invoked. The data is used in the Paint Event:
Dim rectF As RectangleF
Using myB As New SolidBrush(Color.FromArgb(128, Color.Red))
For Each ptF As PointF In XY
rectF = New RectangleF(ptF.X - 35, ptF.Y - 35,
70, 70)
e.Graphics.FillEllipse(myB, rectF)
Next
End Using
If you have other code to add Point data, like a Button click, after you add, change or remove data, use Invalidate to force a redraw: Me.Invaludate() is you are drawing to the form, or PictureBox1.Invalidate() if you are drawing over a control.
The lesson is that now, every time the form needs to be repainted, your shapes will be redrawn as well.

How to control the size of ToolTip which appears when AutoEllipsis Enabled for Label in .Vb.Net

I Have a Label Control, and the text I pass to that Control is sometime larger then the viewable area of the control, I set 'AutoEllipsis" property True.
When I hover my mouse over to that control a long long ToolTip appears in front, which is quite annoying.
I want to control that behavior and want to wrap text to multi lines.
Kindly help anybody.
After suggestion, I have think around and an idea came into my mind.
I broken string where text was going off the sight of Label region , but keeping in mind that very next of word completion so it wont add new line in the mid of word.
here is the code:
Dim str As String = "Chorioactis is a genus of fungus that contains the single species Chorioactis geaster, an extremely rare mushroom found only in select locales in Texas and Japan. In the former, it is commonly known a"
Dim startindex As Integer = str.IndexOf(" ", 100)
Dim secondhalf As String = str.Substring(startindex, (str.Length) - startindex)
Dim firsthalf As String = str.Substring(0, startindex)
str = firsthalf + Environment.NewLine + secondhalf
Label1.Text = str
below are attached Images,
Before
After
Before applying such sub-string operations, make sure to verify the string length is greater then to that number which we set to breakup line

Invert or Flip Text in RDLC report

Okay, I've learned a bit more and have rephrased my question. I've got a need to flip or invert text 180 degrees (so it appears upside-down) on a RDLC report. I have some custom VB code that takes the text, converts it to a bitmap, then flips the rotates the canvas 180 degrees. The effect of this makes the text look a bit.. dithered... or fuzzy. It's not a sharp font anymore. The problem I'm experiencing is I'm using a special TTF Barcode font that creates something a scanner can read. When I flip the barcode font, the fuzziness isn't good since the barcode lines are so close together and the scanner cannot read it. Here's the code:
Function LoadImage(ByVal sImageText as String, iRotationAngle as Integer, ByVal sFontName as String, iFontSize as Integer)
Dim bmpImage As New Drawing.Bitmap(1, 1)
Dim iWidth As Integer = 0
Dim iHeight As Integer = 0
'// Create the Font object for the image text drawing.
Dim MyFont As New Drawing.Font(sFontName, iFontSize) ', System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point)
'// Create a graphics object to measure the text's width and height.
Dim MyGraphics As Drawing.Graphics = Drawing.Graphics.FromImage(bmpImage)
'// This is where the bitmap size is determined.
iWidth = MyGraphics.MeasureString(sImageText, MyFont).Width
iHeight = MyGraphics.MeasureString(sImageText, MyFont).Height
'// Create the bmpImage again with the correct size for the text and font.
bmpImage = New Drawing.Bitmap(bmpImage, New Drawing.Size(iWidth, iHeight))
'// Add the colors to the new bitmap.
MyGraphics = Drawing.Graphics.FromImage(bmpImage)
MyGraphics.Clear(Drawing.Color.White)
MyGraphics.TextRenderingHint = Drawing.Text.TextRenderingHint.ClearTypeGridFit
MyGraphics.TranslateTransform(iWidth,iHeight)
MyGraphics.RotateTransform(iRotationAngle)
MyGraphics.DrawString(sImageText, MyFont, New Drawing.SolidBrush(Drawing.Color.Black), 0, 0)
MyGraphics.Flush()
Dim stream As IO.MemoryStream = New IO.MemoryStream
Dim bitmapBytes As Byte()
'Create bitmap
bmpImage.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp)
bitmapBytes = stream.ToArray
stream.Close()
bmpImage.Dispose()
Return bitmapBytes
End Function
I really don't know why there's not a built-in way to just flip text. It'll let me reverse it left-to-right. Ridiculous.
Thanks

Data grid view header Grid color

This is a VB .NET application where we are showing the output of a SQL statement in a Datagrid view. I'm using .NET 2005.
We need to get the separators of the headers on the grid control to be the same colors as the GridColor on the form.
We've tried looking through all of the properties of the DataGridView control, and found some interesting things that looked promising such as the DataGridViewAdvancedHeaderStyle, and DataGridViewHeaderBorderStyle, but none of it seems to allow you to change the colors on it.
Does anyone know how to do this without remaking the entire thing with a GDI+ control?
Well, I never did find a property for this, so I ended up creating a custom component, and overloading the OnPaint event handler to draw a line over the existing one.
Here is the code for it if anyone else ever comes across this post looking for a solution:
Private Sub CustomDataGridView_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim g As Graphics = e.Graphics
Dim pen As New Pen(Me.GridColor)
Dim TWidth As Integer = 2
Dim HeaderWidth As Integer = 0
If Me.RowHeadersVisible Then
HeaderWidth = Me.RowHeadersWidth
End If
For Each column As DataGridViewColumn In Me.Columns
Dim x As Integer = HeaderWidth + TWidth - 1
TWidth += column.Width
Dim top As Integer = column.HeaderCell.ContentBounds.Top
Dim bottom As Integer = column.HeaderCell.ContentBounds.Bottom + 1
pen.Width = 2
g.DrawLine(pen, x, top, x, bottom)
Next column
End Sub
To change the backcolor of the Column Headers in a datagridview, choose False for EnableHeadersVisualStyles. Then open ColumnHeadersDefaultCellStyle and choose the background color.
I can't see the picture but what about playing with these?
DataGridView.ColumnBordersHeaderStyle
DataGridView.RowBordersHeaderStyle