One of the controls in my application limits a user to be able to change only the font style (B, I, U) and colour of the text. I have created a custom control which inherits from the RichTextBox for this purpose. I am able to intercept CTRL-V, and set the font of the pasted text to SystemFonts.DefaultFont. The problem I am currently facing is if the pasted text contains, for example, half bold half regular style - the bold is lost.
I.e. "Foo Bar" will just paste as "Foo Bar".
My only idea currently is to go through the text character by character (very slow), and do something like:
public class MyRichTextBox : RichTextBox
{
private RichTextBox hiddenBuffer = new RichTextBox();
/// <summary>
/// This paste will strip the font size, family and alignment from the text being pasted.
/// </summary>
public void PasteUnformatted()
{
this.hiddenBuffer.Clear();
this.hiddenBuffer.Paste();
for (int x = 0; x < this.hiddenBuffer.TextLength; x++)
{
// select the next character
this.hiddenBuffer.Select(x, 1);
// Set the font family and size to default
this.hiddenBuffer.SelectionFont = new Font(SystemFonts.DefaultFont.FontFamily, SystemFonts.DefaultFont.Size, this.hiddenBuffer.SelectionFont.Style);
}
// Reset the alignment
this.hiddenBuffer.SelectionAlignment = HorizontalAlignment.Left;
base.SelectedRtf = this.hiddenBuffer.SelectedRtf;
this.hiddenBuffer.Clear();
}
}
Can anyone think of a cleaner (and faster) solution?
'nobugz' over on the MSDN Forums answered this for me (I needed an answer quickly, so after almost a day of tumbleweed from SO, I had to look elsewhere - don't judge me!):
using System.Runtime.InteropServices;
...
public static bool SetRtbFace(RichTextBox rtb, Font font, bool selectionOnly) {
CHARFORMATW fmt = new CHARFORMATW();
fmt.cbSize = Marshal.SizeOf(fmt);
fmt.szFaceName = font.FontFamily.Name;
fmt.dwMask = 0x20000000; // CFM_FACE
return IntPtr.Zero != SendMessage(rtb.Handle, 0x444, (IntPtr)(selectionOnly ? 1 : 4), fmt);
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private class CHARFORMATW {
public int cbSize;
public int dwMask;
public int dwEffects;
public int yHeight;
public int yOffset;
public int crTextColor;
public byte bCharSet;
public byte bPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x40)]
public string szFaceName;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, CHARFORMATW lParam);
For those wanting a Delphi answer, an extract to give you the basic idea:
using RichEdit; //reqd. for the constants and types
var
chformat : TCharFormat2;
fontname : string;
begin
FillChar(chformat,sizeof(chformat),0);
chformat.cbSize := sizeof(chformat);
//only modify the szFaceName field, height etc. left alone
chformat.dwMask := CFM_FACE;
//get the fontname set by the user
fontname := AdvFontSelector1.Text;
strpcopy(chformat.szFaceName,fontname);
RichEdit1.Perform(EM_SETCHARFORMAT, SCF_SELECTION, lparam(#chformat));
end;
Related
I am stuck with this problem of giving padding to all sides to a value inside NumericUpDown control in Winforms.
Apparently no one has asked this before.
Actual how control is looking: (https://i.stack.imgur.com/KIweV.png)
Expected how I want control to look: enter image description here
Ignore other differences in both the attached images. Just need to pad the value from top, bottom and left; as part of this question.
So far, I have only figured out that there is a TextAlign property which can align either Left or Right or Center, but that doesn't help in giving padding to top and bottom edges of the control.
Below code is not solving my problem
this.numericUpDown1.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
AFAIK, you can't control the height of a winforms NumericUpDown control, and therefor it's text-aling property only allows left, center, and right (as opposed to, say, a label which has 9 possibilities (TopLeft, MiddleCenter, BottomRight and so on).
You can, however, create your own control to replace the built in numericUpDown, but that's going to take a lot of work.
Perhaps someone have already done this work and released an open source somewhere.
The NumericUpDown control has an internal UpDownEdit child derives from the TextBox control used to display and edit the value. You can p/invoke to set the Left and/or the Right margins of a single-line TextBox. Add to that the Top and/or Bottom margins of a multiline TextBox.
Consider the listed extension class which targets the derived types from the TextBoxBase abstract class. It adds the SetInnerMargin method to these types to set the inner margins. Note, for the single-line text boxes, the values of the Top and Bottom properties of the Padding struct are ignored.
// TextBox, RichTextBox...etc.
public static class TextBoxBaseExtensions
{
private const int EM_SETRECT = 0xB3;
private const int EM_SETMARGINS = 0xD3;
private const int EC_LEFTMARGIN = 0x1;
private const int EC_RIGHTMARGIN = 0x2;
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public RECT(Rectangle r)
{
Left = r.Left;
Top = r.Top;
Right = r.Right;
Bottom = r.Bottom;
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref RECT rect);
public static void SetInnerMargin(this TextBoxBase self, Padding pad)
{
if (self.Multiline)
{
var r = new Rectangle(
pad.Left,
pad.Top,
self.ClientSize.Width - pad.Left - pad.Right,
self.ClientSize.Height - pad.Top - pad.Bottom);
var nr = new RECT(r);
SendMessage(self.Handle, EM_SETRECT, 0, ref nr);
}
else
{
SendMessage(
self.Handle,
EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
pad.Right * 0x10000 + pad.Left);
}
}
}
Usage examples:
var pad = new Padding(5);
textBox1.SetInnerMargin(pad);
richTextBox1.SetInnerMargin(pad);
(numericUpDown1.Controls[1] as TextBox).SetInnerMargin(pad);
Make sure that the handles of the target controls are created and assigned before you call this method. Alternatively, subscribe to their HandleCreated events to call it.
Also, you can create custom controls to implement this feature. For example:
public class NumericUpDownEx : NumericUpDown
{
private Padding inMargin = Padding.Empty;
/// <summary>
/// Gets or sets the inner margins. The Left and Right only.
/// The Top and Bottom margins are ignored.
/// </summary>
[DefaultValue(typeof(Padding), "0, 0, 0, 0")]
public Padding InnerMargin
{
get => inMargin;
set
{
if (inMargin != value)
{
inMargin = value;
SetInnerMargins();
}
}
}
/// <inheritdoc cref="TextBox.TextAlign"/>
new public HorizontalAlignment TextAlign
{
get => base.TextAlign;
set
{
if (base.TextAlign != value)
{
base.TextAlign = value;
SetInnerMargins();
}
}
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetInnerMargins();
}
private void SetInnerMargins()
{
if (Controls[1] is TextBox tb)
tb.SetInnerMargin(InnerMargin);
}
}
In my WinForms app I have a RichTextBox control containing a long text. I need to scroll it programmatically to a given point (expressed as a character index), regardless of where the selection caret is located. I need a method like this:
//scroll the control so that the 3512th character is visible.
rtf.ScrollToPosition(3512);
All answers to similar questions that I have found use the ScrollToCaret() method, which is fine if you want to scroll to the caret position. But I need to scroll to a different position rather than the caret, and without changing the caret position. How do I do this?
Thank you.
You can use the same methodology implemented in the TextBoxBase.ScrollToCaret Method to accomplish this. This methodology is based on using the COM based Text Object Model implemented by the underlying RichEdit control.
The following defines the extension method ScrollToCharPosition. It uses abbreviated definitions of the ITextDocument and ITextRange interfaces.
public static class RTBExtensions
{
public static void ScrollToCharPosition(this RichTextBox rtb, Int32 charPosition)
{
const Int32 WM_USER = 0x400;
const Int32 EM_GETOLEINTERFACE = WM_USER + 60;
const Int32 tomStart = 32;
if (charPosition < 0 || charPosition > rtb.TextLength - 1)
{
throw new ArgumentOutOfRangeException(nameof(charPosition), $"{nameof(charPosition)} must be in the range of 0 to {rtb.TextLength - 1}.");
}
// retrieve the rtb's OLEINTERFACE and use the Interop Marshaller to cast it as an ITextDocument
// The control calls the AddRef method for the object before returning, so the calling application must call the Release method when it is done with the object.
ITextDocument doc = null;
SendMessage(new HandleRef(rtb, rtb.Handle), EM_GETOLEINTERFACE, IntPtr.Zero, ref doc);
ITextRange rng = null;
if (doc != null)
{
try
{
rng = (RTBExtensions.ITextRange)doc.Range(charPosition, charPosition);
rng.ScrollIntoView(tomStart);
}
finally
{
if (rng != null)
{
Marshal.ReleaseComObject(rng);
}
Marshal.ReleaseComObject(doc);
}
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private extern static IntPtr SendMessage(HandleRef hWnd, Int32 msg, IntPtr wParam, ref ITextDocument lParam);
[ComImport, Guid("8CC497C0-A1DF-11CE-8098-00AA0047BE5D")]
private interface ITextDocument
{
[MethodImpl((short)0, MethodCodeType = MethodCodeType.Runtime)]
void _VtblGap1_17();
[return: MarshalAs(UnmanagedType.Interface)]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(15)]
ITextRange Range([In] int cp1, [In] int cp2);
}
[ComImport, Guid("8CC497C2-A1DF-11CE-8098-00AA0047BE5D")]
private interface ITextRange
{
[MethodImpl((short)0, MethodCodeType = MethodCodeType.Runtime)]
void _VtblGap1_49();
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0x242)]
void ScrollIntoView([In] int Value);
}
}
Example Usage: richtextbox1.ScrollToCharPosition(50)
You can use SendMessage to send a WM_VSCROLL message to the RichEdit control, specifying SB_THUMBPOSITION in the LOWORD of wParam and the absolute vertical position to scroll to in the HIWORD.
The GetPositionFromCharIndex method (belongs to TextBoxBase, so it applies to the TextBox class, too) returns the relative physical position where a char in a specific position is displayed (the value can be negative if the char position is above the current scroll position and it's the difference between the current scroll position and the char position if it's below it - unless the current scroll position is 0).
Assume your RichTextBox is named richTextBox1:
Use, e.g., Regex.Match to determine the position of a word or phrase; the position of the matched patterned is returned by the Index property of the Match.
Check the current offset with GetPositionFromCharIndex(0)
Add the absolute value of the offset defined by the current vertical position, to the value - expressed in pixels - returned by GetPositionFromCharIndex(matchPos), where matchPos is the position of a char / word / pattern to scroll to.
Call SendMessage using the calculated position and specifying to move the thumb to this position passing SB_THUMBPOSITION as part of wParam.
var matchPos = Regex.Match(richTextBox1.Text, #"some words").Index;
var pos0 = richTextBox1.GetPositionFromCharIndex(0).Y;
var pos = richTextBox1.GetPositionFromCharIndex(matchPos).Y + Math.Abs(pos0 - 1);
SendMessage(richTextBox1.Handle, WM_VSCROLL, pos << 16 | SB_THUMBPOSITION, 0);
Native methods declaration:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, int lParam);
private const uint WM_VSCROLL = 0x0115;
private const int SB_THUMBPOSITION = 4;
I'm trying to create a DateTimePicker with week numbers displayed, as shown here (Code project example).
It works fairly well, except for one minor detail; The calender popping up when trying to select a date is not the right size. As you can see, the calendar area is sort of "cramped", especially along the right edge.
I can click the bottom right corner here, and drag it out a little - just enough to expand it so that it looks right:
I can't seem to find any way to force the calendar to be the correct/full size from the beginning, or to resize it.
Finally found a solution that seems to work - at least for now.
It seems there are two windows in the calendar part of the DateTimePicker. Apparently my code would automatically find the correct size for the inner one (more or less at least?), but not the outer one.
A bit of research has led to the code below. The following links provide some useful and relevant info:
GetWindowLong function (Used for getting info about the window to edit)
GetParent function (Finding the outer window, so we could apply settings to that too)
The trick was to add a little to the height and width of the (inner) window, then apply the same height and width to the outer window (which I access using the GetParrent() function). I found the "correct" size by trial and error: When the size matched what was needed for the contents of the calendar, it could not be resized any longer.
Yes, this feels a little like a hack, and no, I haven't been able to verify that it works perfectly on other computers than my own yet. I'm a little worried about having to give specific values for height and width, but I'm hoping this won't be affected by screen resolutions or whatever else.
Hope someone else in a similar situation will find the code useful.
(The following can directly replace a regular DateTimePicker to show week numbers in the calendar)
using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class DatePickerWithWeekNumbers : DateTimePicker
{
[DllImport("User32.dll")]
private static extern int GetWindowLong(IntPtr handleToWindow,
int offsetToValueToGet);
[DllImport("User32.dll")]
private static extern int SetWindowLong(IntPtr h,
int index,
int value);
private const int McmFirst = 0x1000;
private const int McmGetminreqrect = (McmFirst + 9);
private const int McsWeeknumbers = 0x4;
private const int DtmFirst = 0x1000;
private const int DtmGetmonthcal = (DtmFirst + 8);
[DllImport("User32.dll")]
private static extern IntPtr SendMessage(IntPtr h,
int msg,
int param,
int data);
[DllImport("User32.dll")]
private static extern IntPtr GetParent(IntPtr h);
[DllImport("User32.dll")]
private static extern int SendMessage(IntPtr h,
int msg,
int param,
ref Rectangle data);
[DllImport("User32.dll")]
private static extern int MoveWindow(IntPtr h,
int x,
int y,
int width,
int height,
bool repaint);
[Browsable(true), DesignerSerializationVisibility(
DesignerSerializationVisibility.Visible)]
public bool DisplayWeekNumbers { get; set; }
protected override void OnDropDown(EventArgs e)
{
// Hex value to specify that we want the style-attributes
// for the window:
const int offsetToGetWindowsStyles = (-16);
IntPtr pointerToCalenderWindow = SendMessage(Handle,
DtmGetmonthcal,
0,
0);
int styleForWindow = GetWindowLong(pointerToCalenderWindow,
offsetToGetWindowsStyles);
// Check properties for the control - matches available
// property in the graphical properties for the DateTimePicker:
if (DisplayWeekNumbers)
{
styleForWindow = styleForWindow | McsWeeknumbers;
}
else
{
styleForWindow = styleForWindow & ~McsWeeknumbers;
}
// Get the size needed to display the calendar (inner window)
var rect = new Rectangle();
SendMessage(pointerToCalenderWindow, McmGetminreqrect, 0, ref rect);
// Add to size as needed (I don't know why
// this was not correct initially!)
rect.Width = rect.Width + 28;
rect.Height = rect.Height + 6;
// Set window styles..
SetWindowLong(pointerToCalenderWindow,
offsetToGetWindowsStyles,
styleForWindow);
// Dont move the window - just resize it as needed:
MoveWindow(pointerToCalenderWindow,
0,
0,
rect.Right,
rect.Bottom,
true);
// Now access the parrent window..
var parentWindow = GetParent(pointerToCalenderWindow);
// ...and resize that the same way:
MoveWindow(parentWindow, 0, 0, rect.Right, rect.Bottom, true);
base.OnDropDown(e);
}
}
For me, setting MCS_WEEKNUMBERS via the DateTimePicker's DTM_SETMCSTYLE message automatically resulted in the correct size of the MonthCal control:
SendMessage(Handle, DTM_FIRST + 11, 0, SendMessage(Handle, DTM_FIRST + 12, 0, 0) | MCS_WEEKNUMBERS);
Where DTM_FIRST = 0x1000 and MCS_WEEKNUMBERS = 0x4 as in Kjartan's solution. DTM_FIRST + 11 is DTM_SETMCSTYLE and DTM_FIRST + 12 is DTM_GETMCSTYLE in Microsoft's documentation.
Unlike Kjartan's solution, this call must be used before the first dropdown, but right at form initialization didn't work for me in some cases, so I delayed it to when the form was already created and visible in these cases. One call is enough, the DateTimePicker will save the style for future dropdowns.
Ok, Try to comment line in Program.cs
Application.EnableVisualStyles();
and then try to execute.
I'm trying to use Visual Studio 2012 to create a Windows Forms application that can place the caret at the current position within a owner-drawn string. However, I've been unable to find a way to accurately calculate that position.
I've done this successfully before in C++. I've tried numerous methods in C# but have not yet been able to position the caret accurately. Originally, I tried using .NET classes to determine the correct position, but then I tried accessing the Windows API directly. In some cases, I came close, but after some time I still cannot place the caret accurately.
I've created a small test program and posted key parts below. I've also posted the entire project here.
The exact font used is not important to me; however, my application assumes a mono-spaced font. Any help is appreciated.
Form1.cs
This is my main form.
public partial class Form1 : Form
{
private string TestString;
private int AveCharWidth;
private int Position;
public Form1()
{
InitializeComponent();
TestString = "123456789012345678901234567890123456789012345678901234567890";
AveCharWidth = GetFontWidth();
Position = 0;
}
private void Form1_Load(object sender, EventArgs e)
{
Font = new Font(FontFamily.GenericMonospace, 12, FontStyle.Regular, GraphicsUnit.Pixel);
}
protected override void OnGotFocus(EventArgs e)
{
Windows.CreateCaret(Handle, (IntPtr)0, 2, (int)Font.Height);
Windows.ShowCaret(Handle);
UpdateCaretPosition();
base.OnGotFocus(e);
}
protected void UpdateCaretPosition()
{
Windows.SetCaretPos(Padding.Left + (Position * AveCharWidth), Padding.Top);
}
protected override void OnLostFocus(EventArgs e)
{
Windows.HideCaret(Handle);
Windows.DestroyCaret();
base.OnLostFocus(e);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawString(TestString, Font, SystemBrushes.WindowText,
new PointF(Padding.Left, Padding.Top));
}
protected override bool IsInputKey(Keys keyData)
{
switch (keyData)
{
case Keys.Right:
case Keys.Left:
return true;
}
return base.IsInputKey(keyData);
}
protected override void OnKeyDown(KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Left:
Position = Math.Max(Position - 1, 0);
UpdateCaretPosition();
break;
case Keys.Right:
Position = Math.Min(Position + 1, TestString.Length);
UpdateCaretPosition();
break;
}
base.OnKeyDown(e);
}
protected int GetFontWidth()
{
int AverageCharWidth = 0;
using (var graphics = this.CreateGraphics())
{
try
{
Windows.TEXTMETRIC tm;
var hdc = graphics.GetHdc();
IntPtr hFont = this.Font.ToHfont();
IntPtr hOldFont = Windows.SelectObject(hdc, hFont);
var a = Windows.GetTextMetrics(hdc, out tm);
var b = Windows.SelectObject(hdc, hOldFont);
var c = Windows.DeleteObject(hFont);
AverageCharWidth = tm.tmAveCharWidth;
}
catch
{
}
finally
{
graphics.ReleaseHdc();
}
}
return AverageCharWidth;
}
}
Windows.cs
Here are my Windows API declarations.
public static class Windows
{
[Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct TEXTMETRIC
{
public int tmHeight;
public int tmAscent;
public int tmDescent;
public int tmInternalLeading;
public int tmExternalLeading;
public int tmAveCharWidth;
public int tmMaxCharWidth;
public int tmWeight;
public int tmOverhang;
public int tmDigitizedAspectX;
public int tmDigitizedAspectY;
public short tmFirstChar;
public short tmLastChar;
public short tmDefaultChar;
public short tmBreakChar;
public byte tmItalic;
public byte tmUnderlined;
public byte tmStruckOut;
public byte tmPitchAndFamily;
public byte tmCharSet;
}
[DllImport("user32.dll")]
public static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight);
[DllImport("User32.dll")]
public static extern bool SetCaretPos(int x, int y);
[DllImport("User32.dll")]
public static extern bool DestroyCaret();
[DllImport("User32.dll")]
public static extern bool ShowCaret(IntPtr hWnd);
[DllImport("User32.dll")]
public static extern bool HideCaret(IntPtr hWnd);
[DllImport("gdi32.dll", CharSet = CharSet.Auto)]
public static extern bool GetTextMetrics(IntPtr hdc, out TEXTMETRIC lptm);
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("GDI32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
}
Edit
The code I've posted has an issue that makes it even more inaccurate. This is a result of trying many different approaches, some more accurate than this. What I'm looking for is a fix that makes it "fully accurate", as it is in my MFC Hex Editor Control in C++.
I tried out your GetFontWidth(), and the width of a character returned was 7.
I then tried out TextRenderer.MearureText on varying lengths of text and had values ranging from 14 through to 7.14 for text of length 1 to 50 respectively with an average character width of 7.62988874736612.
Here is the code I used:
var text = "";
var sizes = new System.Collections.Generic.List<double>();
for (int i = 1; i <= 50; i++)
{
text += (i % 10).ToString();
var ts = TextRenderer.MeasureText(text, this.Font);
sizes.Add((ts.Width * 1.0) / text.Length);
}
sizes.Add(sizes.Average());
Clipboard.SetText(string.Join("\r\n",sizes));
Not satisfied with the results of my little 'experiment', I decided to see how the text was rendered onto the form. Below is a screen capture of the form (magnified 8x).
On close inspection, I observed that
There was an amount of separation between the characters. This made the length of a block of text (1234567890) is 74 pixels long.
There is some space (3px) in front of the text being drawn even though the left padding is 0.
What does this mean to you?
If you use your code to calculate the width of a font character, you fail to account for the separating space between two characters.
Using the TextRenderer.DrawText can give you varying character widths rendering it quite uselesss.
What are your remaining options?
The best way I can see out of this is to hard-code the placement of your text. That way you know the position of each character and can accurately place the cursor at any desired location.
Needless to say, this is likely going to call for a lot of code.
Your second option is to run tests like I did to find the length of a block of text and then divide by the length of the block to find the average character width.
The problem with this is that your code is not likely to scale properly. For example, changing the size of the font or the user's screen DPI can cause a lot of trouble for the program.
Other things I observed
The space inserted in-front of the text is equivalent to the width of the caret (2px in my case) plus 1px (Total of 3px).
Hard-coding the width of each character to 7.4 works perfectly.
You can use the System.Windows.Forms.TextRenderer to in order to draw the string as well as to calculate its metrics. Various method overloads for both operations exist
TextRenderer.DrawText(e.Graphics, "abc", font, point, Color.Black);
Size measure = TextRenderer.MeasureText(e.Graphics, "1234567890", font);
I have made good experiences with TextRenderer and its accuracy.
UPDATE
I determined the font size like this in one of my applications and it worked perfectly
const TextFormatFlags textFormatFlags =
TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix |
TextFormatFlags.PreserveGraphicsClipping;
fontSize = TextRenderer.MeasureText(this.g, "_", font,
new Size(short.MaxValue, short.MaxValue),
textFormatFlags);
height = fontSize.Height;
width = fontSize.Width;
Make sure to use the same format flags for both drawing and measuring.
(This way of determining the font size of cause works only for monospaced fonts.)
Can somebody please explain how I would go about measuring the string inside a richtextbox control so that the I can automatically resize the richtextbox control according to its content?
Thank you
Edit:
I've thought about it, and since the below answer won't work if there are different fonts in the RichTextBox Control, what if, I could get the upper-left coords of the richtextbox control and then get the bottom-right coords of the very last line of text inside the rtb. That would essentially give me the Width and Height of the string inside the RichTextBox Control. Is this possible? Or is this a bad idea to do it this way?
Put the following code in the ContentsResized event:
Private Sub rtb_ContentsResized(ByVal sender As Object, ByVal e As System.Windows.Forms.ContentsResizedEventArgs) Handles txtQuestion.ContentsResized
Dim h = e.NewRectangle.Height, w = e.NewRectangle.Width
h = Math.Max(h, sender.Font.Height)
h = Math.Min(h, Me.ClientSize.Height - 10 - sender.Top)
h += sender.Height - sender.ClientSize.Height + 1
sender.Height = h
End Sub
Assuming that someone is typing into the control, you could use an event to fire every time a character is entered (increment counter) and decrement when it is deleted. This would give you a true count.
Edit:
Have you tried this to adjust the height?
richTextBox1.Height = (int)(1.5 * richTextBox1.Font.Height) + richTextBox1.GetLineFromCharIndex(richTextBox1.Text.Length + 1) * richTextBox1.Font.Height + 1 + richTextBox1.Margin.Vertical;
richTextBox1.SelectionStart = 0;
richTextBox1.SelectionStart = richTextBox1.Text.Length;
Or you can do this using Width:
Graphics g = Graphics.FromHwnd(richTextBox1.Handle);
SizeF f = g.MeasureString(richTextBox1.Text, richTextBox1.Font);
richTextBox1.Width = (int)(f.Width)+5;
Try calling GetPreferredSize(Size.Empty). It is defined in the Control class, and if overriden property by the RichTextBoxControl, ought to give you what you are looking for.
If you pass something other than Size.Empty into the method, then it will use that value as a maximum constraint. Using Size.Empty means that the potential size is unbounded.
You can measure a string by calling TextRenderer.MeasureText.
However, if the text contains multiple fonts, this will not work.
EDIT: You're looking for the GetPositionFromCharIndex method.
Note that if there are multiple lines, you should take the max of the X coordinates of the last character on each line.
I found a solution for the Rich text box height issues.. i have modified it a for general use..
Create following structs in your application....
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public Int32 left;
public Int32 top;
public Int32 right;
public Int32 bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLBARINFO {
public Int32 cbSize;
public RECT rcScrollBar;
public Int32 dxyLineButton;
public Int32 xyThumbTop;
public Int32 xyThumbBottom;
public Int32 reserved;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public Int32[] rgstate;
}
Create following private variables in your class for form (where ever you need to calculate rich text height)
private UInt32 SB_VERT = 1;
private UInt32 OBJID_VSCROLL = 0xFFFFFFFB;
[DllImport("user32.dll")]
private static extern
Int32 GetScrollRange(IntPtr hWnd, UInt32 nBar, out Int32 lpMinPos, out Int32 lpMaxPos);
[DllImport("user32.dll")]
private static extern
Int32 GetScrollBarInfo(IntPtr hWnd, UInt32 idObject, ref SCROLLBARINFO psbi);
Add following method to your Class for form
private int CalculateRichTextHeight(string richText) {
int height = 0;
RichTextBox richTextBox = new RichTextBox();
richTextBox.Rtf = richText;
richTextBox.Height = this.Bounds.Height;
richTextBox.Width = this.Bounds.Width;
richTextBox.WordWrap = false;
int nHeight = 0;
int nMin = 0, nMax = 0;
SCROLLBARINFO psbi = new SCROLLBARINFO();
psbi.cbSize = Marshal.SizeOf(psbi);
richTextBox.Height = 10;
richTextBox.ScrollBars = RichTextBoxScrollBars.Vertical;
int nResult = GetScrollBarInfo(richTextBox.Handle, OBJID_VSCROLL, ref psbi);
if (psbi.rgstate[0] == 0) {
GetScrollRange(richTextBox.Handle, SB_VERT, out nMin, out nMax);
height = (nMax - nMin);
}
return height;
}
You may need to modify above method to make it work as per your requirement...
Make sure to send Rtf string as parameter to method not normal text and also make sure to assign available width and height to the Richtextbox variable in the method...
You can play with WordWrap depending on your requirement...
Add on to bathineni's great answer:
Background: I needed to measure RTF output height for rendering onto paper and because I have custom dynamic page headers/footers I needed to control paging).
(RichTextBox.GetLineFromCharIndex let me down because of complex RTF; including lines & multi column Tables with wordwrap).
Anyhow all was working fine, until someone else used my app with the dreaded windows "Make text and other items larger or smaller" (DPI settings.) - in short now measuring bigger sized fonts it screwed up the page length calculations. (the printer still rendered the text and columns correctly - only the page lengths were now all wrong.)
Only factoring DPI difference failed as in short bigger text didn't fit properly into source RTF tx and cellx values.
Anyhow, in case others are doing similar crazy things bit of trial and error came up with the following (eventually very few) mods to the bathineni CalculateRichTextHeight method:
RichTextBox richTextBox = new RichTextBox(); // same as original
int dpix = richTextBox.CreateGraphics().DpiX; // get dpi
richTextBox.WordWrap = true; // I needed this, you many not
// ... set size etc - same as original answer
richTextBox.Scale(new System.Drawing.SizeF(dpix / 96, dpix / 96)); // scale RTB
// ...
// 96? my original calculations based on windows default 96dpi settings.
Seems the otherwise obscure Control.Scale(sizef) is actually useful for something after all.
Note: if converting results to actual printed lines, (in my case all my \pard's were "\sl-240\slmult0" which comes out to 16 (pix?) per line) also remember to re-factor the divisor.
i.e. in my case:
lines = height / (int)(16 * (dpix / 96))