将文件加载到RichTextBox中时,某些超链接不会触发LinkClicked事件 [英] Some hyperlinks don't trigger the LinkClicked event when loading a file into RichTextBox
问题描述
行为如下: 如果超链接后面跟了足够多的字符(视情况而定),则由LinkClicked事件触发。如果我删除超链接后面的字符,则不会触发事件。
更新 经过一些测试后,需要在最后一个URL之后插入的字符数等于要加载的*.rtf文件中所有URL的总字符数。
我无法发布图片,括号中的文字是超链接
不起作用: [单击此处]了解更多信息。
{
tf1ansiansicpg1252deff0
ouicompatdeflang4105{fonttbl{f0fnilfcharset0 Calibri;}}
{*generator Riched20 10.0.17134}viewkind4uc1
{field{*fldinst { HYPERLINK "http://www.google.com" }}{fldrslt {Click here}}}
pardsa200sl276slmult1f0fs22lang9 for more information.par
}
作品: [单击此处]了解更多信息。Lorem ipsum
{
tf1ansiansicpg1252deff0
ouicompatdeflang4105{fonttbl{f0fnilfcharset0 Calibri;}}
{*generator Riched20 10.0.17134}viewkind4uc1
{field{*fldinst { HYPERLINK "http://www.google.com" }}{fldrslt {Click here}}}
pardsa200sl276slmult1f0fs22lang9 for more information. Lorem ipsumpar
}
链接工作所需的字符数在大约20到大约100个字符之间变化。
我创建了一个小项目,以确保问题不会来自主项目中的其他任何地方。该项目仅包含RichTextBox。我已经将DetectUrls设置为True,这没有什么不同。我还尝试在Google Docs中创建*.rtf文件,以检查Word版本是否可能是问题所在。我还使用写字板进行了测试,包括在Notepad++中手动设置URL。这个问题在.Net Framework4.6中不会出现,但我要求使用.NET4.7。如果我动态添加链接,问题也不会发生,但我无法根据我的要求执行此操作。
Public Sub Form1_Load(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles MyBase.Load
Dim LoadFileName As Object
LoadFileName = "C:Usersanononymsource
eposWindowsApp1Test.rtf"
RichTextBox1.LoadFile(LoadFileName, RichTextBoxStreamType.RichText)
End Sub
Private Sub RichTextBox_LinkClicked(sender As Object, e As LinkClickedEventArgs) Handles RichTextBox1.LinkClicked
System.Diagnostics.Process.Start(e.LinkText)
End Sub
预期结果是将超链接重定向到Word中设置的网站。在所有情况下,我都使用www.google.com进行测试。
推荐答案
从.NET4.7开始,RichTextBox使用RichEdit50控件;以前的版本使用RichEdit20控件。我不知道控件版本之间在处理超链接方面存在差异的原因,但显然存在一些差异。
一种解决方法是将.Net 4.7应用程序配置为使用较旧的控件。这可以通过将以下内容添加到您的App.config
文件来完成。
<runtime>
<AppContextSwitchOverrides value="Switch.System.Windows.Forms.DoNotLoadLatestRichEditControl=true" />
</runtime>
编辑:
问题的根源似乎是原始RichTextBox.CharRangeToString Method中的黑客攻击。
//Windows bug: 64-bit windows returns a bad range for us. VSWhidbey 504502.
//Putting in a hack to avoid an unhandled exception.
if (c.cpMax > Text.Length || c.cpMax-c.cpMin <= 0) {
return string.Empty;
}
使用RichEdit50控件中提供的Friendly Name Hyperlinks时,RichTextBox.Text.Length
属性可以小于c.cpMax
值,因为返回的属性值中不包括链接。这会导致该方法将String.Empty
返回给调用RichTextBox.EnLinkMsgHandler Method,如果返回Empty.String
,则调用RichTextBox.EnLinkMsgHandler Method不会引发LickClicked事件。
case NativeMethods.WM_LBUTTONDOWN:
string linktext = CharRangeToString(enlink.charrange);
if (!string.IsNullOrEmpty(linktext))
{
OnLinkClicked(new LinkClickedEventArgs(linktext));
}
m.Result = (IntPtr)1;
return;
为了处理这个错误,下面定义了一个自定义RichTextBox
类来修改CharRangeToString
方法的逻辑。此修改后的逻辑在WndProc
过程中调用以绕过默认逻辑。
Imports System.Runtime.InteropServices
Imports WindowsApp2.NativeMthods ' *** change WindowsApp2 to match your project
Public Class RichTextBoxFixedForFriendlyLinks : Inherits RichTextBox
Friend Function ConvertFromENLINK64(es64 As ENLINK64) As ENLINK
' Note: the RichTextBox.ConvertFromENLINK64 method is written using C# unsafe code
' this is version uses a GCHandle to pin the byte array so that
' the same Marshal.Read_Xyz methods can be used
Dim es As New ENLINK()
Dim hndl As GCHandle
Try
hndl = GCHandle.Alloc(es64.contents, GCHandleType.Pinned)
Dim es64p As IntPtr = hndl.AddrOfPinnedObject
es.nmhdr = New NMHDR()
es.charrange = New CHARRANGE()
es.nmhdr.hwndFrom = Marshal.ReadIntPtr(es64p)
es.nmhdr.idFrom = Marshal.ReadIntPtr(es64p + 8)
es.nmhdr.code = Marshal.ReadInt32(es64p + 16)
es.msg = Marshal.ReadInt32(es64p + 24)
es.wParam = Marshal.ReadIntPtr(es64p + 28)
es.lParam = Marshal.ReadIntPtr(es64p + 36)
es.charrange.cpMin = Marshal.ReadInt32(es64p + 44)
es.charrange.cpMax = Marshal.ReadInt32(es64p + 48)
Finally
hndl.Free()
End Try
Return es
End Function
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = WM_ReflectNotify Then
Dim hdr As NMHDR = CType(m.GetLParam(GetType(NMHDR)), NMHDR)
If hdr.code = EN_Link Then
Dim lnk As ENLINK
If IntPtr.Size = 4 Then
lnk = CType(m.GetLParam(GetType(ENLINK)), ENLINK)
Else
lnk = ConvertFromENLINK64(CType(m.GetLParam(GetType(ENLINK64)), ENLINK64))
End If
If lnk.msg = WM_LBUTTONDOWN Then
Dim linkUrl As String = CharRangeToString(lnk.charrange)
' Still check if linkUrl is not empty
If Not String.IsNullOrEmpty(linkUrl) Then
OnLinkClicked(New LinkClickedEventArgs(linkUrl))
End If
m.Result = New IntPtr(1)
Exit Sub
End If
End If
End If
MyBase.WndProc(m)
End Sub
Private Function CharRangeToString(ByVal c As CHARRANGE) As String
Dim ret As String = String.Empty
Dim txrg As New TEXTRANGE With {.chrg = c}
''Windows bug: 64-bit windows returns a bad range for us. VSWhidbey 504502.
''Putting in a hack to avoid an unhandled exception.
'If c.cpMax > Text.Length OrElse c.cpMax - c.cpMin <= 0 Then
' Return String.Empty
'End If
' *********
' c.cpMax can be greater than Text.Length if using friendly links
' with RichEdit50. so that check is not valid.
' instead of the hack above, first check that the number of characters is positive
' and then use the result of sending EM_GETTEXTRANGE to handle the
' possibilty of Text.Length < c.cpMax
' *********
Dim numCharacters As Int32 = (c.cpMax - c.cpMin) + 1 ' +1 for null termination
If numCharacters > 0 Then
Dim charBuffer As CharBuffer
charBuffer = CharBuffer.CreateBuffer(numCharacters)
Dim unmanagedBuffer As IntPtr
Try
unmanagedBuffer = charBuffer.AllocCoTaskMem()
If unmanagedBuffer = IntPtr.Zero Then
Throw New OutOfMemoryException()
End If
txrg.lpstrText = unmanagedBuffer
Dim len As Int32 = CInt(SendMessage(New HandleRef(Me, Handle), EM_GETTEXTRANGE, 0, txrg))
If len > 0 Then
charBuffer.PutCoTaskMem(unmanagedBuffer)
ret = charBuffer.GetString()
End If
Finally
If txrg.lpstrText <> IntPtr.Zero Then
Marshal.FreeCoTaskMem(unmanagedBuffer)
End If
End Try
End If
Return ret
End Function
End Class
虽然上面的代码不是很重要,但它需要来自基本实现的几个方法/结构,这些方法/结构不能公开访问。这些方法的VB版本如下所示。大多数是来自原始C#源代码的直接转换。
Imports System.Runtime.InteropServices
Imports System.Text
Public Class NativeMthods
Friend Const EN_Link As Int32 = &H70B
Friend Const WM_NOTIFY As Int32 = &H4E
Friend Const WM_User As Int32 = &H400
Friend Const WM_REFLECT As Int32 = WM_User + &H1C00
Friend Const WM_ReflectNotify As Int32 = WM_REFLECT Or WM_NOTIFY
Friend Const WM_LBUTTONDOWN As Int32 = &H201
Friend Const EM_GETTEXTRANGE As Int32 = WM_User + 75
Public Structure NMHDR
Public hwndFrom As IntPtr
Public idFrom As IntPtr 'This is declared as UINT_PTR in winuser.h
Public code As Int32
End Structure
<StructLayout(LayoutKind.Sequential)>
Public Class ENLINK
Public nmhdr As NMHDR
Public msg As Int32 = 0
Public wParam As IntPtr = IntPtr.Zero
Public lParam As IntPtr = IntPtr.Zero
Public charrange As CHARRANGE = Nothing
End Class
<StructLayout(LayoutKind.Sequential)>
Public Class ENLINK64
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=56)>
Public contents(0 To 55) As Byte
End Class
<StructLayout(LayoutKind.Sequential)>
Public Class CHARRANGE
Public cpMin As Int32
Public cpMax As Int32
End Class
<StructLayout(LayoutKind.Sequential)>
Public Class TEXTRANGE
Public chrg As CHARRANGE
Public lpstrText As IntPtr ' allocated by caller, zero terminated by RichEdit
End Class
Public MustInherit Class CharBuffer
Public Shared Function CreateBuffer(ByVal size As Int32) As CharBuffer
If Marshal.SystemDefaultCharSize = 1 Then
Return New AnsiCharBuffer(size)
End If
Return New UnicodeCharBuffer(size)
End Function
Public MustOverride Function AllocCoTaskMem() As IntPtr
Public MustOverride Function GetString() As String
Public MustOverride Sub PutCoTaskMem(ByVal ptr As IntPtr)
Public MustOverride Sub PutString(ByVal s As String)
End Class
Public Class AnsiCharBuffer : Inherits CharBuffer
Friend buffer() As Byte
Friend offset As Int32
Public Sub New(ByVal size As Int32)
buffer = New Byte(0 To size - 1) {}
End Sub
Public Overrides Function AllocCoTaskMem() As IntPtr
Dim result As IntPtr = Marshal.AllocCoTaskMem(buffer.Length)
Marshal.Copy(buffer, 0, result, buffer.Length)
Return result
End Function
Public Overrides Function GetString() As String
Dim i As Int32 = offset
Do While i < buffer.Length AndAlso buffer(i) <> 0
i += 1
Loop
Dim result As String = Encoding.Default.GetString(buffer, offset, i - offset)
If i < buffer.Length Then
i += 1
End If
offset = i
Return result
End Function
Public Overrides Sub PutCoTaskMem(ByVal ptr As IntPtr)
Marshal.Copy(ptr, buffer, 0, buffer.Length)
offset = 0
End Sub
Public Overrides Sub PutString(ByVal s As String)
Dim bytes() As Byte = Encoding.Default.GetBytes(s)
Dim count As Int32 = Math.Min(bytes.Length, buffer.Length - offset)
Array.Copy(bytes, 0, buffer, offset, count)
offset += count
If offset < buffer.Length Then
buffer(offset) = 0
offset += 1
End If
End Sub
End Class
Public Class UnicodeCharBuffer : Inherits CharBuffer
Friend buffer() As Char
Friend offset As Int32
Public Sub New(ByVal size As Int32)
buffer = New Char(size - 1) {}
End Sub
Public Overrides Function AllocCoTaskMem() As IntPtr
Dim result As IntPtr = Marshal.AllocCoTaskMem(buffer.Length * 2)
Marshal.Copy(buffer, 0, result, buffer.Length)
Return result
End Function
Public Overrides Function GetString() As String
Dim i As Int32 = offset
Do While i < buffer.Length AndAlso AscW(buffer(i)) <> 0
i += 1
Loop
Dim result As New String(buffer, offset, i - offset)
If i < buffer.Length Then
i += 1
End If
offset = i
Return result
End Function
Public Overrides Sub PutCoTaskMem(ByVal ptr As IntPtr)
Marshal.Copy(ptr, buffer, 0, buffer.Length)
offset = 0
End Sub
Public Overrides Sub PutString(ByVal s As String)
Dim count As Int32 = Math.Min(s.Length, buffer.Length - offset)
s.CopyTo(0, buffer, offset, count)
offset += count
If offset < buffer.Length Then
buffer(offset) = ChrW(0)
offset += 1
End If
End Sub
End Class
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Public Shared Function SendMessage(ByVal hWnd As HandleRef, ByVal msg As Int32, ByVal wParam As Int32, ByVal lParam As TEXTRANGE) As IntPtr
End Function
End Class
将这些类添加到我们的项目中并执行构建。RichTextBoxFixedForFriendlyLinks
应在工具箱中可用。您可以在通常使用RichTextBox
控件的地方使用它。
编辑2:
此问题已在MS开发人员社区上发布为:WinForm RichTextBox LinkClicked event fails to fire when control loaded with RTF containing a friendly name hyperlink
这篇关于将文件加载到RichTextBox中时,某些超链接不会触发LinkClicked事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!