【问题标题】:How to perform hit testing on the position within in a string如何对字符串中的位置执行命中测试
【发布时间】:2017-04-17 02:25:53
【问题描述】:

我正在使用Graphics.DrawString 写出一个字符串,并且需要在给定鼠标位置的字符串中获取字符索引。

这看起来应该很简单,但我真的很难找到一种可行的方法。

我找到了e.Graphics.MeasureString,它返回了它设法写入的字符数,但它有一些issues

目前我唯一的工作方法是测量a,然后是ab,然后是abc,直到我超过我的x位置,但这是一个糟糕的解决方案......

【问题讨论】:

标签: c# .net winforms gdi


【解决方案1】:

您可以在编写字符串时计算占用字符串中每个字符的矩形的坐标。之后你可以扫描这个列表,看看鼠标坐标是否在这个矩形内。

【讨论】:

  • Kerning 在一般情况下使这不可能。字母实际上是重叠的:例如“Wa”:“a”开始“W”结束之前。 en.wikipedia.org/wiki/Kerning
【解决方案2】:

正如一条评论告诉您的,您可以使用MeasureCharacterRanges 来实现此目的。我必须首先说我编辑并删除了我以前的答案,因为它太长了。其次,所有这些代码都改编自here

这个解决方案远非完美,但它可能会引导您实现您想要的。首先,我们有一个方法可以测量最长 32 个字符的字符串的字符:

// Measure the characters in a string with
// no more than 32 characters.
private List<RectangleF> MeasureCharactersInWord(
    Graphics gr, Font font, string text)
{
    List<RectangleF> result = new List<RectangleF>();

    using (StringFormat string_format = new StringFormat())
    {
        string_format.Alignment = StringAlignment.Near;
        string_format.LineAlignment = StringAlignment.Near;
        string_format.Trimming = StringTrimming.None;
        string_format.FormatFlags =
            StringFormatFlags.MeasureTrailingSpaces;

        CharacterRange[] ranges = new CharacterRange[text.Length];
        for (int i = 0; i < text.Length; i++)
        {
            ranges[i] = new CharacterRange(i, 1);
        }
        string_format.SetMeasurableCharacterRanges(ranges);

        RectangleF rect = new RectangleF(0, 0, 10000, 100);
        Region[] regions =
            gr.MeasureCharacterRanges(
                text, font, this.ClientRectangle,
                string_format);

        foreach (Region region in regions)
            result.Add(region.GetBounds(gr));
    }

    return result;
}

在这之后,我们现在需要一个方法来测量整个字符串,不管多长:

private List<RectangleF> MeasureCharacters(Graphics gr,Font font, int      
                                           posY,string text)
{
    List<RectangleF> results = new List<RectangleF>();

    // The X location for the next character.
    float x = 0;

    // Get the character sizes 31 characters at a time.
    for (int start = 0; start < text.Length; start += 32)
    {
        // Get the substring.
        int len = 32;
        if (start + len >= text.Length) len = text.Length - start;
        string substring = text.Substring(start, len);

        // Measure the characters.
        List<RectangleF> rects =
            MeasureCharactersInWord(gr, font, substring);

        // Remove lead-in for the first character.
        if (start == 0) x += rects[0].Left;

        // Save all but the last rectangle.
        for (int i = 0; i < rects.Count + 1 - 1; i++)
        {
            RectangleF new_rect = new RectangleF(
                x, posY,
                rects[i].Width, rects[i].Height);
            results.Add(new_rect);

            // Move to the next character's X position.
            x += rects[i].Width;
        }
    }

    // Return the results.
    return results;
}

这是绘制字符串并将所有字符位置存储在 2 行中的方法:

List<List<RectangleF>> linesCharactersPositions = new List<List<RectangleF>>();
private void Form3_Paint(object sender, PaintEventArgs e)
{
    linesCharactersPositions = new List<List<RectangleF>>();
    string[] lines = new string[] { "12345678  0234567890123456789012asdjkhfkjasdhfklhasdlkfjhlasdkjhfasdlfhlasdc",
                                        "vm,xv,cxznvrtrutyquiortorutoqwruyoiurweyoquitiqwrtiqwetryqweiufhsduafh" };
    Font f = new Font("Arial", 18);

    int lineY = 10;
    foreach (string line in lines)
    {
        DrawLongStringWithCharacterBounds(e.Graphics, line, new Font("Arial", 18), new PointF(10, lineY));
        linesCharactersPositions.Add(MeasureCharacters(e.Graphics, f,lineY, line));
        lineY += 20;
    }


}

最后,MouseMove 事件:

private void Form_MouseMove(object sender, MouseEventArgs e)
{
    Graphics g = this.CreateGraphics();
    int characterCount = 0;
    foreach(var linePositions in linesCharactersPositions)
    {
        for (int i = 0; i < linePositions.Count; i++)
        {
            if (linePositions[i].Contains(e.Location))
            {

                label1.Text = "Index: " + (i+characterCount);
            }
        }
        characterCount += linePositions.Count;
    }

}

【讨论】:

  • 好的,它是一个解决方案,但我不确定它是否能在我的用例中工作,因为我经常有一个满屏的行,这些行以 1024 个字符换行,所以屏幕上满是这些(即使它们不是全部可见我仍然需要跟踪那里的位置,所以我知道在哪里画什么)可能是 150k 个字符。我也许可以通过更改绘图代码来减少这一点,但屏幕上可能仍有 30k 可见字符。
  • @Sproty 查看我更新的答案。它可能仍然不完全适合您的情况,但我相信您可能会适应它。希望对您有所帮助。
  • 即使屏幕充满了字符,这也能工作并且速度惊人,但是字符开始不同步。我想这是因为 32 个字符的块是单独测量的。我设法获得度量并同步绘制的唯一方法是将 TextRenderer.DrawText 和 TextRenderer.MeasureText 与 TextFormatFlags.TextBoxControl 一起使用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-05
  • 2011-08-08
  • 2012-07-29
  • 1970-01-01
相关资源
最近更新 更多