初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using Clipboard = System.Windows.Forms.Clipboard;
|
||||
|
||||
// Author: uye (owner of the MaaAssistantArknights team)
|
||||
// Original Source: MaaAssistantArknights project - https://github.com/MaaAssistantArknights/MaaAssistantArknights
|
||||
// License: Apache License 2.0 (this file only)
|
||||
//
|
||||
// This file is based on work originally developed in the MaaAssistantArknights project,
|
||||
// which is licensed under the GNU AGPL v3.0 only.
|
||||
//
|
||||
// As the original author of this code, I am re-licensing this specific file under
|
||||
// the Apache License 2.0 for use in PCL2-CE.
|
||||
//
|
||||
// Description:
|
||||
// Implements a WPF clipboard fix to handle OpenClipboard failures in TextBox,
|
||||
// RichTextBox, and DataGrid, typically caused by focus issues or external hooks.
|
||||
//
|
||||
// Date: 2025-07-03
|
||||
|
||||
namespace PCL.Controls.Behaviors;
|
||||
|
||||
public sealed class ClipboardInterceptor
|
||||
{
|
||||
public static readonly DependencyProperty EnableSafeClipboardProperty =
|
||||
DependencyProperty.RegisterAttached("EnableSafeClipboard", typeof(bool), typeof(ClipboardInterceptor),
|
||||
new PropertyMetadata(false, OnEnableSafeClipboardChanged));
|
||||
|
||||
private ClipboardInterceptor()
|
||||
{
|
||||
}
|
||||
|
||||
public static void SetEnableSafeClipboard(DependencyObject element, bool value)
|
||||
{
|
||||
element.SetValue(EnableSafeClipboardProperty, value);
|
||||
}
|
||||
|
||||
public static bool GetEnableSafeClipboard(DependencyObject element)
|
||||
{
|
||||
return (bool)element.GetValue(EnableSafeClipboardProperty);
|
||||
}
|
||||
|
||||
private static void OnEnableSafeClipboardChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!(bool)e.NewValue)
|
||||
return;
|
||||
|
||||
switch (d)
|
||||
{
|
||||
case TextBox box:
|
||||
AddCommandBindingsToTextBox(box);
|
||||
break;
|
||||
case RichTextBox box:
|
||||
AddCommandBindingsToRichTextBox(box);
|
||||
break;
|
||||
case DataGrid grid:
|
||||
AddCommandBindingsToDataGrid(grid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddCommandBindingsToTextBox(TextBox tb)
|
||||
{
|
||||
tb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopyTextBox));
|
||||
tb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCutTextBox));
|
||||
tb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPasteTextBox));
|
||||
}
|
||||
|
||||
private static void AddCommandBindingsToRichTextBox(RichTextBox rtb)
|
||||
{
|
||||
rtb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopyRichTextBox));
|
||||
rtb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCutRichTextBox));
|
||||
rtb.CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPasteRichTextBox));
|
||||
}
|
||||
|
||||
private static void AddCommandBindingsToDataGrid(DataGrid dg)
|
||||
{
|
||||
dg.CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopyDataGrid));
|
||||
}
|
||||
|
||||
private static void OnCopyTextBox(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var tb = sender as TextBox;
|
||||
if (tb is null || tb.SelectionLength <= 0)
|
||||
return;
|
||||
|
||||
TrySetClipboardText(tb.SelectedText);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static void OnCutTextBox(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var tb = sender as TextBox;
|
||||
if (tb is null || tb.SelectionLength <= 0)
|
||||
return;
|
||||
|
||||
TrySetClipboardText(tb.SelectedText);
|
||||
|
||||
tb.SelectedText = string.Empty;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static void OnPasteTextBox(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var tb = sender as TextBox;
|
||||
if (tb is null)
|
||||
return;
|
||||
|
||||
if (Clipboard.ContainsText())
|
||||
{
|
||||
var pasteText = Clipboard.GetText();
|
||||
|
||||
if (!tb.AcceptsReturn)
|
||||
pasteText = pasteText.Replace("\r\n", " ").Replace("\r", " ")
|
||||
.Replace("\n", " ");
|
||||
|
||||
var start = tb.SelectionStart;
|
||||
|
||||
tb.SelectedText = pasteText;
|
||||
tb.CaretIndex = start + pasteText.Length;
|
||||
tb.SelectionLength = 0;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static void OnCopyRichTextBox(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var rtb = sender as RichTextBox;
|
||||
if (rtb is null)
|
||||
return;
|
||||
|
||||
var textRange = new TextRange(rtb.Selection.Start, rtb.Selection.End);
|
||||
if (string.IsNullOrEmpty(textRange.Text))
|
||||
return;
|
||||
|
||||
TrySetClipboardText(textRange.Text);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static void OnCutRichTextBox(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var rtb = sender as RichTextBox;
|
||||
if (rtb is null)
|
||||
return;
|
||||
|
||||
var selection = new TextRange(rtb.Selection.Start, rtb.Selection.End);
|
||||
if (string.IsNullOrEmpty(selection.Text))
|
||||
return;
|
||||
|
||||
TrySetClipboardText(selection.Text);
|
||||
|
||||
selection.Text = string.Empty;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static void OnPasteRichTextBox(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var rtb = sender as RichTextBox;
|
||||
if (rtb is null)
|
||||
return;
|
||||
|
||||
if (!Clipboard.ContainsText())
|
||||
return;
|
||||
|
||||
var pasteText = Clipboard.GetText();
|
||||
var selection = rtb.Selection;
|
||||
|
||||
selection.Text = pasteText;
|
||||
|
||||
var caretPos = selection.End;
|
||||
rtb.CaretPosition = caretPos;
|
||||
rtb.Selection.Select(caretPos, caretPos);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static void OnCopyDataGrid(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var dg = sender as DataGrid;
|
||||
if (dg is null || dg.SelectedCells is null || dg.SelectedCells.Count == 0)
|
||||
return;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var rowGroups = dg.SelectedCells.GroupBy(c => c.Item);
|
||||
|
||||
foreach (var row in rowGroups)
|
||||
{
|
||||
var rowText = string.Join("\t", row.Select(cell =>
|
||||
{
|
||||
var tb = cell.Column.GetCellContent(cell.Item) as TextBlock;
|
||||
return tb is not null ? tb.Text : "";
|
||||
}));
|
||||
sb.AppendLine(rowText);
|
||||
}
|
||||
|
||||
TrySetClipboardText(sb.ToString().TrimEnd('\r', '\n'));
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private static bool TrySetClipboardText(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.Clear();
|
||||
Clipboard.SetDataObject(text, true);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace PCL;
|
||||
|
||||
internal static class LazyLoader
|
||||
{
|
||||
public static void EnableLazyLoad(this FrameworkElement element, Action action)
|
||||
{
|
||||
var behavior = new LazyLoadBehavior();
|
||||
behavior.Action = action;
|
||||
Interaction.GetBehaviors(element).Add(behavior);
|
||||
}
|
||||
}
|
||||
|
||||
public class LazyLoadBehavior : Behavior<FrameworkElement>
|
||||
{
|
||||
public static readonly DependencyProperty ActionProperty = DependencyProperty.Register(nameof(Action),
|
||||
typeof(Action), typeof(LazyLoadBehavior), new PropertyMetadata(null));
|
||||
|
||||
public Action Action
|
||||
{
|
||||
get => (Action)GetValue(ActionProperty);
|
||||
set => SetValue(ActionProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
AssociatedObject.LayoutUpdated += OnLayoutUpdated;
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
AssociatedObject.LayoutUpdated -= OnLayoutUpdated;
|
||||
base.OnDetaching();
|
||||
}
|
||||
|
||||
private void OnLayoutUpdated(object sender, EventArgs e)
|
||||
{
|
||||
var element = AssociatedObject;
|
||||
if (element is null || element.RenderSize.Width < double.Epsilon || !element.IsVisible)
|
||||
return;
|
||||
|
||||
var scrollViewer = FindParentScrollViewer(element);
|
||||
if (scrollViewer is null)
|
||||
return;
|
||||
|
||||
var elementBounds = element.TransformToAncestor(scrollViewer)
|
||||
.TransformBounds(new Rect(new Point(0d, 0d), element.RenderSize));
|
||||
var viewport = new Rect(0d, 0d, scrollViewer.ViewportWidth, scrollViewer.ViewportHeight);
|
||||
|
||||
if (!viewport.IntersectsWith(elementBounds))
|
||||
return;
|
||||
|
||||
Action?.Invoke();
|
||||
// 仅执行一次
|
||||
element.LayoutUpdated -= OnLayoutUpdated;
|
||||
}
|
||||
|
||||
private static ScrollViewer FindParentScrollViewer(DependencyObject d)
|
||||
{
|
||||
for (var current = d; current is not null; current = VisualTreeHelper.GetParent(current))
|
||||
{
|
||||
if (current is ScrollViewer scrollViewer)
|
||||
return scrollViewer;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user