ClipboardInterceptor.vb 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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 CBool(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. Dim start = tb.SelectionStart
  84. tb.SelectedText = pasteText
  85. tb.CaretIndex = start + pasteText.Length
  86. tb.SelectionLength = 0
  87. End If
  88. e.Handled = True
  89. End Sub
  90. Private Shared Sub OnCopyRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  91. Dim rtb = TryCast(sender, RichTextBox)
  92. If rtb Is Nothing Then Return
  93. Dim textRange = New TextRange(rtb.Selection.Start, rtb.Selection.End)
  94. If String.IsNullOrEmpty(textRange.Text) Then Return
  95. Try
  96. Forms.Clipboard.Clear()
  97. Forms.Clipboard.SetDataObject(textRange.Text, True)
  98. Catch
  99. End Try
  100. e.Handled = True
  101. End Sub
  102. Private Shared Sub OnCutRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  103. Dim rtb = TryCast(sender, RichTextBox)
  104. If rtb Is Nothing Then Return
  105. Dim selection = New TextRange(rtb.Selection.Start, rtb.Selection.End)
  106. If String.IsNullOrEmpty(selection.Text) Then Return
  107. Try
  108. Forms.Clipboard.Clear()
  109. Forms.Clipboard.SetDataObject(selection.Text, True)
  110. Catch
  111. End Try
  112. selection.Text = String.Empty
  113. e.Handled = True
  114. End Sub
  115. Private Shared Sub OnPasteRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
  116. Dim rtb = TryCast(sender, RichTextBox)
  117. If rtb Is Nothing Then Return
  118. If Not Forms.Clipboard.ContainsText() Then Return
  119. Dim pasteText = Forms.Clipboard.GetText()
  120. Dim selection = rtb.Selection
  121. selection.Text = pasteText
  122. Dim caretPos = selection.End
  123. rtb.CaretPosition = caretPos
  124. rtb.Selection.Select(caretPos, caretPos)
  125. e.Handled = True
  126. End Sub
  127. Private Shared Sub OnCopyDataGrid(sender As Object, e As ExecutedRoutedEventArgs)
  128. Dim dg = TryCast(sender, DataGrid)
  129. If dg Is Nothing OrElse dg.SelectedCells Is Nothing OrElse dg.SelectedCells.Count = 0 Then Return
  130. Dim sb = New System.Text.StringBuilder()
  131. Dim rowGroups = dg.SelectedCells.GroupBy(Function(c) c.Item)
  132. For Each row In rowGroups
  133. Dim rowText = String.Join(vbTab, row.Select(Function(cell)
  134. Dim tb = TryCast(cell.Column.GetCellContent(cell.Item), TextBlock)
  135. Return If(tb IsNot Nothing, tb.Text, "")
  136. End Function))
  137. sb.AppendLine(rowText)
  138. Next
  139. Dim sbStr = sb.ToString().TrimEnd(ControlChars.Cr, ControlChars.Lf)
  140. Try
  141. Forms.Clipboard.Clear()
  142. Forms.Clipboard.SetDataObject(sbStr, True)
  143. Catch
  144. End Try
  145. e.Handled = True
  146. End Sub
  147. End Class
  148. End Namespace