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++中一样.
解决方法
然后,我在不同长度的文本上尝试了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像素长.
>即使左边填充为0,也会在绘制文本前面留出一些空格(3px).
这对你意味着什么?
>如果使用代码计算字体字符的宽度,则无法考虑两个字符之间的分隔空间.
>使用TextRenderer.DrawText可以为您提供不同的字符宽度,使其变得毫无用处.
你剩下的选择是什么?
>我能看到的最好的方法是硬编码文本的位置.这样你就可以知道每个角色的位置,并且可以准确地将光标放在任何所需的位置.
毋庸置疑,这可能需要大量的代码.
>您的第二个选择是像我一样运行测试以查找文本块的长度,然后除以块的长度以找到平均字符宽度.
这样做的问题是您的代码不太可能正确扩展.例如,更改字体大小或用户屏幕DPI可能会给程序带来很多麻烦.
我观察到的其他事情
>插入文本前面的空格相当于插入符号的宽度(在我的情况下为2px)加上1px(总共3px).>将每个字符的宽度硬编码为7.4可以完美地工作.