Unable to Calculate Position within Owner-Draw Text

Posted by Jonathan Wood on Stack Overflow See other posts from Stack Overflow or by Jonathan Wood
Published on 2012-11-23T16:54:42Z Indexed on 2012/11/25 17:04 UTC
Read the original article Hit count: 237

Filed under:
|
|
|
|

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 now tried numerous methods in C#. 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);
}

© Stack Overflow or respective owner

Related posts about c#

Related posts about winforms