我尝试使用Visual Studio 2012创建一个Windows窗体应用程序,该应用程序可以将插入符号放在所有者绘制的字符串中的当前位置。但是,我一直无法找到准确计算该位置的方法。
I've done this successfully before in C++。我在C#中尝试了很多方法,但尚未能准确定位插入符号。最初,我尝试使用.NET类来确定正确的位置,但后来我尝试直接访问Windows API。在某些情况下,我接近了,但过了一段时间我仍然无法准确地放置插入符号。
我已经创建了一个小型测试程序并在下面发布了关键部分。我还发布了整个项目here。
使用的确切字体对我来说并不重要;但是,我的应用程序采用单倍间距字体。任何帮助表示赞赏。
Form1.cs的 这是我的主要形式。
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 这是我的Windows API声明。
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);
}
修改
我发布的代码存在一个问题,使其更加不准确。这是尝试许多不同方法的结果,一些方法比这更准确。我正在寻找的是一种解决方案,使其完全准确",就像在MFC Hex Editor Control in C++中一样。
答案 0 :(得分:3)
您可以使用System.Windows.Forms.TextRenderer
来绘制字符串以及计算其指标。存在两种操作的各种方法重载
TextRenderer.DrawText(e.Graphics, "abc", font, point, Color.Black);
Size measure = TextRenderer.MeasureText(e.Graphics, "1234567890", font);
我在TextRenderer
及其准确性方面取得了很好的经验。
<强>更新强>
我在我的一个应用程序中确定了这样的字体大小,并且它运行良好
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;
确保为绘图和测量使用相同的格式标记。
(这种确定原因字体大小的方法仅适用于等宽字体。)
答案 1 :(得分:3)
我试用了您的GetFontWidth()
,并且返回的字符宽度为 7 。
然后,我针对不同长度的文本尝试了TextRenderer.MearureText
,对于长度为1到50的文本,其值分别为 14 到 7.14 ,平均字符宽度 7.62988874736612 。
以下是我使用的代码:
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));
对我的小'实验'的结果不满意,我决定看看文本是如何呈现在表单上的。下面是表格的屏幕截图(放大8倍)。
仔细检查后,我观察到了
1234567890
)的长度 74 像素长。TextRenderer.DrawText
可以为您提供不同的字符宽度,使其无用。