ClipboardInterceptor.vb 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. ' Author: uye (owner of the MaaAssistantArknights team)
  2. ' Original Source: MaaAssistantArknights project - https://github.com/MaaAssistantArknights/MaaAssistantArknights
  3. ' License: Apache License 2.0 (this file only)
  4. '
  5. ' This file is originally developed for MaaAssistantArknights and licensed under AGPL v3.0.
  6. ' As the original author and copyright holder,
  7. ' I hereby re-license this file under the Apache License 2.0 for use in the Plain Craft Launcher project.
  8. '
  9. ' This file is contributed under the spirit of good faith and open cooperation.
  10. ' It does not implement any core launcher functions of PCL.
  11. '
  12. ' Description:
  13. ' Provides a WPF clipboard handling fix to avoid OpenClipboard exceptions
  14. ' in TextBox, RichTextBox, and DataGrid under Windows focus issues or clipboard hooks.
  15. '
  16. ' Date: 2025-07-03
  17. Imports System.Windows
  18. Imports System.Windows.Controls
  19. Imports System.Windows.Input
  20. Imports System.Windows.Documents
  21. Imports System.Linq
  22. Namespace Controls.Behaviors
  23. Public NotInheritable Class ClipboardInterceptor
  24. Private Sub New()
  25. End Sub
  26. Public Shared ReadOnly EnableSafeClipboardProperty As DependencyProperty =
  27. DependencyProperty.RegisterAttached("EnableSafeClipboard", GetType(Boolean), GetType(ClipboardInterceptor),
  28. New PropertyMetadata(False, AddressOf OnEnableSafeClipboardChanged))
  29. Public Shared Sub SetEnableSafeClipboard(element As DependencyObject, value As Boolean)
  30. element.SetValue(EnableSafeClipboardProperty, value)
  31. End Sub
  32. Public Shared Function GetEnableSafeClipboard(element As DependencyObject) As Boolean
  33. Return element.GetValue(EnableSafeClipboardProperty)
  34. End Function
  35. Private Shared Sub OnEnableSafeClipboardChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
  36. If TypeOf d Is TextBox AndAlso CBool(e.NewValue) Then
  37. AddCommandBindingsToTextBox(DirectCast(d, TextBox))
  38. ElseIf TypeOf d Is RichTextBox AndAlso CBool(e.NewValue) Then
  39. AddCommandBindingsToRichTextBox(DirectCast(d, RichTextBox))
  40. ElseIf TypeOf d Is DataGrid AndAlso CBool(e.NewValue) Then
  41. AddCommandBindingsToDataGrid(DirectCast(d, DataGrid))
  42. End If
  43. End Sub
  44. Private Shared Sub AddCommandBindingsToTextBox(tb As TextBox)
  45. tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyTextBox))
  46. tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox))
  47. tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox))
  48. End Sub
  49. Private Shared Sub AddCommandBindingsToRichTextBox(rtb As RichTextBox)
  50. rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyRichTextBox))
  51. rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutRichTextBox))
  52. rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteRichTextBox))
  53. End Sub
  54. Private Shared Sub AddCommandBindingsToDataGrid(dg As DataGrid)
  55. dg.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyDataGrid))
  56. End Sub
  57. Private Shared Sub OnCopyTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  58. Dim tb = TryCast(sender, TextBox)
  59. If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return
  60. Try
  61. Forms.Clipboard.Clear()
  62. Forms.Clipboard.SetDataObject(tb.SelectedText, True)
  63. Catch
  64. End Try
  65. e.Handled = True
  66. End Sub
  67. Private Shared Sub OnCutTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  68. Dim tb = TryCast(sender, TextBox)
  69. If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return
  70. Try
  71. Forms.Clipboard.Clear()
  72. Forms.Clipboard.SetDataObject(tb.SelectedText, True)
  73. Catch
  74. End Try
  75. tb.SelectedText = String.Empty
  76. e.Handled = True
  77. End Sub
  78. Private Shared Sub OnPasteTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  79. Dim tb = TryCast(sender, TextBox)
  80. If tb Is Nothing Then Return
  81. If Forms.Clipboard.ContainsText() Then
  82. Dim pasteText = Forms.Clipboard.GetText()
  83. '将换行符替换为空格(#7825)
  84. If Not tb.AcceptsReturn Then pasteText = pasteText.Replace(vbCrLf, " ").Replace(vbCr, " ").Replace(vbLf, " ")
  85. Dim start = tb.SelectionStart
  86. tb.SelectedText = pasteText
  87. tb.CaretIndex = start + pasteText.Length
  88. tb.SelectionLength = 0
  89. End If
  90. e.Handled = True
  91. End Sub
  92. Private Shared Sub OnCopyRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  93. Dim rtb = TryCast(sender, RichTextBox)
  94. If rtb Is Nothing Then Return
  95. Dim textRange = New TextRange(rtb.Selection.Start, rtb.Selection.End)
  96. If String.IsNullOrEmpty(textRange.Text) Then Return
  97. Try
  98. Forms.Clipboard.Clear()
  99. Forms.Clipboard.SetDataObject(textRange.Text, True)
  100. Catch
  101. End Try
  102. e.Handled = True
  103. End Sub
  104. Private Shared Sub OnCutRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  105. Dim rtb = TryCast(sender, RichTextBox)
  106. If rtb Is Nothing Then Return
  107. Dim selection = New TextRange(rtb.Selection.Start, rtb.Selection.End)
  108. If String.IsNullOrEmpty(selection.Text) Then Return
  109. Try
  110. Forms.Clipboard.Clear()
  111. Forms.Clipboard.SetDataObject(selection.Text, True)
  112. Catch
  113. End Try
  114. selection.Text = String.Empty
  115. e.Handled = True
  116. End Sub
  117. Private Shared Sub OnPasteRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  118. Dim rtb = TryCast(sender, RichTextBox)
  119. If rtb Is Nothing Then Return
  120. If Not Forms.Clipboard.ContainsText() Then Return
  121. Dim pasteText = Forms.Clipboard.GetText()
  122. Dim selection = rtb.Selection
  123. selection.Text = pasteText
  124. Dim caretPos = selection.End
  125. rtb.CaretPosition = caretPos
  126. rtb.Selection.Select(caretPos, caretPos)
  127. e.Handled = True
  128. End Sub
  129. Private Shared Sub OnCopyDataGrid(sender As Object, e As ExecutedRoutedEventArgs)
  130. Dim dg = TryCast(sender, DataGrid)
  131. If dg Is Nothing OrElse dg.SelectedCells Is Nothing OrElse dg.SelectedCells.Count = 0 Then Return
  132. Dim sb = New StringBuilder
  133. Dim rowGroups = dg.SelectedCells.GroupBy(Function(c) c.Item)
  134. For Each row In rowGroups
  135. Dim rowText = String.Join(vbTab, row.Select(
  136. Function(cell)
  137. Dim tb = TryCast(cell.Column.GetCellContent(cell.Item), TextBlock)
  138. Return If(tb IsNot Nothing, tb.Text, "")
  139. End Function))
  140. sb.AppendLine(rowText)
  141. Next
  142. Dim sbStr = sb.ToString().TrimEnd(ControlChars.Cr, ControlChars.Lf)
  143. Try
  144. Forms.Clipboard.Clear()
  145. Forms.Clipboard.SetDataObject(sbStr, True)
  146. Catch
  147. End Try
  148. e.Handled = True
  149. End Sub
  150. End Class
  151. End Namespace