ClipboardInterceptor.vb 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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. Namespace Controls.Behaviors
  18. Public NotInheritable Class ClipboardInterceptor
  19. Private Sub New()
  20. End Sub
  21. Public Shared ReadOnly EnableSafeClipboardProperty As DependencyProperty =
  22. DependencyProperty.RegisterAttached("EnableSafeClipboard", GetType(Boolean), GetType(ClipboardInterceptor),
  23. New PropertyMetadata(False, AddressOf OnEnableSafeClipboardChanged))
  24. Public Shared Sub SetEnableSafeClipboard(element As DependencyObject, value As Boolean)
  25. element.SetValue(EnableSafeClipboardProperty, value)
  26. End Sub
  27. Public Shared Function GetEnableSafeClipboard(element As DependencyObject) As Boolean
  28. Return element.GetValue(EnableSafeClipboardProperty)
  29. End Function
  30. Private Shared Sub OnEnableSafeClipboardChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
  31. If TypeOf d Is TextBox AndAlso CBool(e.NewValue) Then
  32. AddCommandBindingsToTextBox(DirectCast(d, TextBox))
  33. ElseIf TypeOf d Is RichTextBox AndAlso CBool(e.NewValue) Then
  34. AddCommandBindingsToRichTextBox(DirectCast(d, RichTextBox))
  35. ElseIf TypeOf d Is DataGrid AndAlso CBool(e.NewValue) Then
  36. AddCommandBindingsToDataGrid(DirectCast(d, DataGrid))
  37. End If
  38. End Sub
  39. Private Shared Sub AddCommandBindingsToTextBox(tb As TextBox)
  40. tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyTextBox))
  41. tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox))
  42. tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox))
  43. End Sub
  44. Private Shared Sub AddCommandBindingsToRichTextBox(rtb As RichTextBox)
  45. rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyRichTextBox))
  46. rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutRichTextBox))
  47. rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteRichTextBox))
  48. End Sub
  49. Private Shared Sub AddCommandBindingsToDataGrid(dg As DataGrid)
  50. dg.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyDataGrid))
  51. End Sub
  52. Private Shared Sub OnCopyTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  53. Dim tb = TryCast(sender, TextBox)
  54. If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return
  55. Try
  56. Forms.Clipboard.Clear()
  57. Forms.Clipboard.SetDataObject(tb.SelectedText, True)
  58. Catch ex As Exception
  59. Logger.Warn(ex, "复制失败,请稍后再试", LogBehavior.Toast)
  60. End Try
  61. e.Handled = True
  62. End Sub
  63. Private Shared Sub OnCutTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  64. Dim tb = TryCast(sender, TextBox)
  65. If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return
  66. Try
  67. Forms.Clipboard.Clear()
  68. Forms.Clipboard.SetDataObject(tb.SelectedText, True)
  69. Catch ex As Exception
  70. Logger.Warn(ex, "剪切失败,请稍后再试", LogBehavior.Toast)
  71. End Try
  72. tb.SelectedText = String.Empty
  73. e.Handled = True
  74. End Sub
  75. Private Shared Sub OnPasteTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  76. Dim tb = TryCast(sender, TextBox)
  77. If tb Is Nothing Then Return
  78. Try
  79. If Forms.Clipboard.ContainsText() Then
  80. Dim pasteText = Forms.Clipboard.GetText()
  81. '将换行符替换为空格(#7825)
  82. If Not tb.AcceptsReturn Then pasteText = pasteText.ReplaceLineEndings(" ", mergeMultiple:=True)
  83. Dim start = tb.SelectionStart
  84. tb.SelectedText = pasteText
  85. tb.CaretIndex = start + pasteText.Length
  86. tb.SelectionLength = 0
  87. End If
  88. Catch ex As Exception
  89. Logger.Warn(ex, "粘贴失败,请稍后再试", LogBehavior.Toast)
  90. End Try
  91. e.Handled = True
  92. End Sub
  93. Private Shared Sub OnCopyRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  94. Dim rtb = TryCast(sender, RichTextBox)
  95. If rtb Is Nothing Then Return
  96. Dim textRange = New TextRange(rtb.Selection.Start, rtb.Selection.End)
  97. If String.IsNullOrEmpty(textRange.Text) Then Return
  98. Try
  99. Forms.Clipboard.Clear()
  100. Forms.Clipboard.SetDataObject(textRange.Text, True)
  101. Catch ex As Exception
  102. Logger.Warn(ex, "复制失败,请稍后再试", LogBehavior.Toast)
  103. End Try
  104. e.Handled = True
  105. End Sub
  106. Private Shared Sub OnCutRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  107. Dim rtb = TryCast(sender, RichTextBox)
  108. If rtb Is Nothing Then Return
  109. Dim selection = New TextRange(rtb.Selection.Start, rtb.Selection.End)
  110. If String.IsNullOrEmpty(selection.Text) Then Return
  111. Try
  112. Forms.Clipboard.Clear()
  113. Forms.Clipboard.SetDataObject(selection.Text, True)
  114. Catch ex As Exception
  115. Logger.Warn(ex, "剪切失败,请稍后再试", LogBehavior.Toast)
  116. End Try
  117. selection.Text = String.Empty
  118. e.Handled = True
  119. End Sub
  120. Private Shared Sub OnPasteRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  121. Dim rtb = TryCast(sender, RichTextBox)
  122. If rtb Is Nothing Then Return
  123. Try
  124. If Not Forms.Clipboard.ContainsText() Then Return
  125. Dim pasteText = Forms.Clipboard.GetText()
  126. Dim selection = rtb.Selection
  127. selection.Text = pasteText
  128. Dim caretPos = selection.End
  129. rtb.CaretPosition = caretPos
  130. rtb.Selection.Select(caretPos, caretPos)
  131. Catch ex As Exception
  132. Logger.Warn(ex, "粘贴失败,请稍后再试", LogBehavior.Toast)
  133. End Try
  134. e.Handled = True
  135. End Sub
  136. Private Shared Sub OnCopyDataGrid(sender As Object, e As ExecutedRoutedEventArgs)
  137. Dim dg = TryCast(sender, DataGrid)
  138. If dg Is Nothing OrElse dg.SelectedCells Is Nothing OrElse dg.SelectedCells.Count = 0 Then Return
  139. Dim sb = New StringBuilder
  140. Dim rowGroups = dg.SelectedCells.GroupBy(Function(c) c.Item)
  141. For Each row In rowGroups
  142. Dim rowText = String.Join(vbTab, row.Select(
  143. Function(cell)
  144. Dim tb = TryCast(cell.Column.GetCellContent(cell.Item), TextBlock)
  145. Return If(tb IsNot Nothing, tb.Text, "")
  146. End Function))
  147. sb.AppendLine(rowText)
  148. Next
  149. Dim sbStr = sb.ToString().TrimEnd(ControlChars.Cr, ControlChars.Lf)
  150. Try
  151. Forms.Clipboard.Clear()
  152. Forms.Clipboard.SetDataObject(sbStr, True)
  153. Catch ex As Exception
  154. Logger.Warn(ex, "复制失败,请稍后再试", LogBehavior.Toast)
  155. End Try
  156. e.Handled = True
  157. End Sub
  158. End Class
  159. End Namespace