2

在 Windows 窗体 C# 应用程序中,我有许多 RichTextBox 控件,它们将链接显示为文本框的最后一行,之后没有换行符。

问题是物理上位于链接下方的所有空白区域都是可点击的链接。我知道文本下方的空白区域通常用作 Windows 中该行的“一部分”——例如,将光标放在这篇文章的正下方,然后单击并拖动——您将选择最后一行。但通常这不包括可点击的链接。用这篇文章的标题试试——你可以选择标题,但你的光标不是可点击的“手”,直到你真正直接在标题上。

我可以通过将数据更改为始终包含尾随换行符来解决此问题,或者修改我将框的文本设置为始终添加一个的点。但这两个似乎都很乱。有没有办法让 RichTextBox 的链接更像网络浏览器中的链接?

我可以通过创建一个示例 WinForms 应用程序、放入 RichTextBox 并使用设计器将文本设置为“ http://www.google.com ”来重现此行为,链接下方的任何地方都会显示手形光标。

我正在使用 Windows 7 / VS2010 / C# / .net Framework 4.0

感谢您的建议。

4

2 回答 2

3

链接下方的任何地方都会显示手形光标。

您需要换行才能看到链接下方的文本光标而不是手形光标。它的设计。

我可以通过将数据更改为始终包含尾随换行符来解决此问题,或者修改我将框的文本设置为始终添加一个的点。但这两个似乎都很乱。有没有办法让 RichTextBox 的链接更像网络浏览器中的链接?

不,在后面加上一个换行符。或者将 RichTexbox DetectUrls 属性设置为 false。或者正如汉斯所说,使用网络浏览器。或者使用第 3 方或开源 RichTextBox 控件。

如果将鼠标悬停在超链接上时会触发 Cursor 更改事件,但它不会:(

于 2013-06-22T01:08:06.500 回答
2

如果将鼠标悬停在超链接上时会触发 Cursor 更改事件,但它不会:(

Jeremy 的评论给了我一个想法:当用户将鼠标悬停在超链接上时,本机 RichTextBox 控件肯定会收到某种类型的通知,它显然没有被 WinForms 包装类公开。

一些研究证实了我的假设。设置为检测超链接的 RichTextBox 控件通过消息向其父级发送EN_LINK通知。然后,通过处理这些通知,您可以在超链接悬停时覆盖其行为。WM_NOTIFYEN_LINK

WinForms 包装器在私有代码中处理所有这些,并且不允许客户端对此行为进行任何直接控制。但是通过覆盖父窗口(即您的窗体)的窗口过程 ( WndProc),您可以手动截取WM_NOTIFY消息并观察EN_LINK通知。

它需要一些代码,但它可以工作。例如,如果您禁止WM_SETCURSOR所有EN_LINK通知的消息,则根本看不到手形光标。

[StructLayout(LayoutKind.Sequential)]
struct CHARRANGE
{
   public int cpMin;
   public int cpMax;
};

[StructLayout(LayoutKind.Sequential)]
struct NMHDR
{
   public IntPtr hwndFrom;
   public IntPtr idFrom;
   public int code;
};

[StructLayout(LayoutKind.Sequential)]
struct ENLINK
{
   public NMHDR nmhdr;
   public int msg;
   public IntPtr wParam;
   public IntPtr lParam;
   public CHARRANGE chrg;
};

public class MyForm : Form
{
   // ... other code ...

   protected override void WndProc(ref Message m)
   {
      const int WM_NOTIFY    = 0x004E;
      const int EN_LINK      = 0x070B;
      const int WM_SETCURSOR = 0x0020;

      if (m.Msg == WM_NOTIFY)
      {
         NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
         if (nmhdr.code == EN_LINK)
         {
            ENLINK enlink = (ENLINK)m.GetLParam(typeof(ENLINK));
            if (enlink.msg == WM_SETCURSOR)
            {
                // Set the result to indicate this message has been handled,
                // and return without calling the default window procedure.
                m.Result = (IntPtr)1;
                return;
            }
         }
      }
      base.WndProc(ref m);
   }
}

不幸的是,这很容易。现在出现了一个丑陋的黑客,我们在其中解决您描述的控件的默认行为,如果最后一行是超链接,它将控件高度的其余部分视为最后一行的一部分。

为此,我们需要获取鼠标指针的当前位置,并将其与控件检测到的超链接文本的位置进行比较。如果鼠标指针在超链接行内,我们允许默认行为并显示手形光标。否则,我们抑制手形光标。请参阅下面的注释代码以获得对该过程的更好解释(显然,rtb是您的 RichTextBox 控件):

protected override void WndProc(ref Message m)
{
   const int WM_NOTIFY    = 0x004E;
   const int EN_LINK      = 0x070B;
   const int WM_SETCURSOR = 0x0020;

   if (m.Msg == WM_NOTIFY)
   {
      NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
      if (nmhdr.code == EN_LINK)
      {
         ENLINK enlink = (ENLINK)m.GetLParam(typeof(ENLINK));
         if (enlink.msg == WM_SETCURSOR)
         {
            // Get the position of the last line of text in the RichTextBox.
            Point ptLastLine = rtb.GetPositionFromCharIndex(rtb.TextLength);

            // That point was in client coordinates, so convert it to
            // screen coordinates so that we can match it against the
            // position of the mouse pointer.
            ptLastLine = rtb.PointToScreen(ptLastLine);

            // Determine the height of a line of text in the RichTextBox.
            // 
            // For this simple demo, it doesn't matter which line we use for
            // this since they all use the same size and style. However, you
            // cannot generally rely on this being the case.
            Size szTextLine = TextRenderer.MeasureText(rtb.Lines[0], rtb.Font);

            // Then add that text height to the vertical position of the
            // last line of text in the RichTextBox.
            ptLastLine.Y += szTextLine.Height;

            // Now that we know the maximum height of all lines of text in the
            // RichTextBox, we can compare that to the pointer position.
            if (Cursor.Position.Y > ptLastLine.Y)
            {
               // If the mouse pointer is beyond the last line of text,
               // do not treat it as a hyperlink.
               m.Result = (IntPtr)1;
               return;
            }
         }
      }
   }
   base.WndProc(ref m);
}

经过测试和工作......但我有没有提到这是一个丑陋的黑客?将其更像是概念证明。我当然不建议在生产代码中使用它。我非常同意 Hans 和 Jeremy 的观点,即您应该采用更简单的方法来添加换行符,或者使用更合适的控件来显示超链接。

于 2013-06-24T07:44:40.507 回答