Today I got an email asking for help using my suggested Multiline MeasureString implementation when the control has a different font size.
Well, in the provided sample code I'm using the default font size on all the controls. To get a simpler code, I'm using always the form graphics as parameter for MeasureString:
textboxHeight = CFMeasureString.MeasureString(CreateGraphics(), newText,textboxRect, true).Height;
CreateGraphics() as shown gets the Graphics object for the form. MeasureString will calculate the height based on the selected font on that Graphics.
Let's work now with the adaptative UI sample. What if we change the font size in one of the controls?
Let's see what happen if we change it to 15:
The application will not work properly anymore, because the font of the label is not the same font of the provided Graphics:
Apparently, the solution could be to get the graphics from the control instead of still using the form graphics. We can create an overload for CreateGraphics like this:
private Graphics CreateGraphics(Control control)
But it doesn't work because the Graphics won't have any font selected. .Net CF releases fonts after using them when i.e. you call DrawString (thanks god!)... that's the reason for having the same measure based on the default font size in spite of what graphics we are using.
The real solution
Obviously, the problem has a solution. We can create a new overload for MeasureString, which receives the control as one of its parameters and: 1)creates the right graphics object 2)select the control's font in the graphics and 3)releases it after the string has been measured.
To get all this working, we'll add two imports into our CFMeasureString class: GetDC and SelectObject.
Please include the following code as part of the CFMeasureString class:
static extern IntPtr GetDC(IntPtr hWnd);
static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
/// Measure a multiline string for a Control
/// <param name="control">control</param>
/// <param name="text">string to measure</param>
/// <param name="rect">Original rect. The width will be taken as fixed.</param>
/// <returns>A Size object with the measure of the string according with the params</returns>
static public Size MeasureString(Control control, string text, Rectangle rect)
Size result = Size.Empty;
IntPtr controlFont = control.Font.ToHfont();
IntPtr hDC = GetDC(control.Handle);
using (Graphics gr = Graphics.FromHdc(hDC))
IntPtr originalObject = SelectObject(hDC, controlFont);
result = MeasureString(gr, text, rect, control is TextBox);
SelectObject(hDC, originalObject); //Release resources
In our sample application, we can replace the MeasureString calls in Form1.cs with the following:
labelHeight = CFMeasureString.MeasureString(label1, label1.Text, labelRect).Height;
textboxHeight = CFMeasureString.MeasureString(textBox1, newText, textboxRect).Height;
instructionsLabel.Height = CFMeasureString.MeasureString(instructionsLabel,instructionsLabel.Text, instructionsLabel.ClientRectangle).Height;
Now, even if we additionally change the textbox font size to 20, the application still working!
Thanks Sven for the feedback, I hope it helps!