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.)
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);
}
}
So I created a simple form to test out using an SVG image to draw a custom shaped window. Inspiration found here
It seems to work fine, but no matter what I do my window size is too small to put any controls on.
Reasons for doing this: It's cool? Windows needs better themeing support. I was bored!
I am using Svg from nuget.com from within Visual Studio
Code:
using Svg;
public const int WM_NCLBUTTONDOWN = 0xA1; public const int HT_CAPTION = 0x2;
internal class NativeMethods
{
// Allows forms with Toolbox property set to false to be moved
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool ReleaseCapture();
}
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
System.Drawing.Drawing2D.GraphicsPath frmshp = new System.Drawing.Drawing2D.GraphicsPath();
//frmshp.AddEllipse(0, 0, this.Width, this.Height);
//SvgDocument.Open(#"TestWindowsshape.svg");
SvgDocument newshp = SvgDocument.Open(#"TestWindowsshape.svg");
frmshp = (newshp.Path);
this.Region = new Region(frmshp);
}
private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Make settings window movable without a titlebar
if (e.Button == MouseButtons.Left)
{
NativeMethods.ReleaseCapture();
NativeMethods.SendMessage(Handle, WM_NCLBUTTONDOWN (IntPtr)HT_CAPTION, (IntPtr)0);
}
}
I have tried to increase the svg size, played with the code some, but nothing I do will make the drawn window bigger. I know I can do this with a BMP, and the TransparancyKey option, but I would like to not do it that way, since the BMP & transparency method has the drawback of not being able to use one color in the bitmap itself. Any advice would be appreciated
Edit:
Matrix m = new Matrix();
m.Scale(100, 100, MatrixOrder.Append);
m.Translate(100, 100, MatrixOrder.Append);
newshp.Path.Transform(m);
Has been tried, with no effect. I would assume that this should have worked does that mean the problem is within my SVG?
The problem seems to be in the SVG file, i adjusted the size of a Rectangle in Notepad++ and got a bigger windows, however more complex shapes will be a hassle. It seems Inkscape cannot create SVGs of a reliable size... I have the "Document properties" set to my screen resolution, but the vectors all turn out too small. Perhaps Illustrator can do this properly.
I need to get the DPI scale, as set from Control Panel > Display, for each of the screens connected to the computer, even those that do not have a WPF window open. I have seen a number of ways to get DPI (for example, http://dzimchuk.net/post/Best-way-to-get-DPI-value-in-WPF) but these seem to be dependent on either Graphics.FromHwnd(IntPtr.Zero) or PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice.
Is there a way to get the DPI settings for each individual screen?
Background - I am creating a layout configuration editor so that the user can set up their configuration prior to launch. For this, I draw each of the screens relative to each other. For one configuration we are using a 4K display that has a larger than default DPI scale set. It is drawing much smaller than it physically appears in relation to the other screens because it reports as the same resolution as the other screens.
I found a way to get the dpi’s with the WinAPI.
As first needs references to System.Drawing and System.Windows.Forms. It is possible to get the monitor handle with the WinAPI from a point on the display area - the Screen class can give us this points. Then the GetDpiForMonitor function returns the dpi of the specified monitor.
public static class ScreenExtensions
{
public static void GetDpi(this System.Windows.Forms.Screen screen, DpiType dpiType, out uint dpiX, out uint dpiY)
{
var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
var mon = MonitorFromPoint(pnt, 2/*MONITOR_DEFAULTTONEAREST*/);
GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062(v=vs.85).aspx
[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);
//https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/dn280511(v=vs.85).aspx
public enum DpiType
{
Effective = 0,
Angular = 1,
Raw = 2,
}
There are three types of scaling, you can find a description in the MSDN.
I tested it quickly with a new WPF application:
private void Window_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
var sb = new StringBuilder();
sb.Append("Angular\n");
sb.Append(string.Join("\n", Display(DpiType.Angular)));
sb.Append("\nEffective\n");
sb.Append(string.Join("\n", Display(DpiType.Effective)));
sb.Append("\nRaw\n");
sb.Append(string.Join("\n", Display(DpiType.Raw)));
this.Content = new TextBox() { Text = sb.ToString() };
}
private IEnumerable<string> Display(DpiType type)
{
foreach (var screen in System.Windows.Forms.Screen.AllScreens)
{
uint x, y;
screen.GetDpi(type, out x, out y);
yield return screen.DeviceName + " - dpiX=" + x + ", dpiY=" + y;
}
}
I hope it helps!
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.
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;