You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF实现类浏览器的TextBox矩形选框跨框文本复制功能

实现跨多TextBox的鼠标拖拽选框与文本复制功能

一、添加全局选框容器

修改你的Grid布局,在ItemsControl上方叠加一个透明Canvas,用来动态绘制黄色选框:

<Grid>
    <!-- 选框容器,置于最上层以覆盖所有TextBox -->
    <Canvas x:Name="SelectionCanvas" Background="Transparent" />
    
    <ItemsControl Grid.Row="1" x:Name="TextItemsControl">
        <ItemsControl.Template>
            <ControlTemplate TargetType="ItemsControl">
                <ItemsPresenter/>
            </ControlTemplate>
        </ItemsControl.Template>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Vertical" MinWidth="550" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</Grid>

二、实现鼠标拖拽绘制选框逻辑

在后台代码中添加鼠标事件处理,维护选框的创建、更新与销毁:

首先定义全局变量:

private Rectangle _selectionRect;
private Point _dragStartPoint;

然后绑定Canvas的鼠标事件:

public MainWindow()
{
    InitializeComponent();
    SelectionCanvas.MouseDown += SelectionCanvas_MouseDown;
    SelectionCanvas.MouseMove += SelectionCanvas_MouseMove;
    SelectionCanvas.MouseUp += SelectionCanvas_MouseUp;
}

private void SelectionCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed) return;
    
    // 清除历史选框
    SelectionCanvas.Children.Clear();
    
    // 记录拖拽起始坐标
    _dragStartPoint = e.GetPosition(SelectionCanvas);
    
    // 创建黄色半透明选框
    _selectionRect = new Rectangle
    {
        Stroke = Brushes.Yellow,
        Fill = Brushes.Yellow,
        Opacity = 0.3,
        StrokeThickness = 1
    };
    SelectionCanvas.Children.Add(_selectionRect);
    
    // 捕获鼠标,防止拖拽时移出控件丢失事件
    SelectionCanvas.CaptureMouse();
}

private void SelectionCanvas_MouseMove(object sender, MouseEventArgs e)
{
    if (_selectionRect == null || e.LeftButton != MouseButtonState.Pressed) return;
    
    Point currentPoint = e.GetPosition(SelectionCanvas);
    
    // 计算选框的位置和大小(确保左上角为起始点)
    double left = Math.Min(_dragStartPoint.X, currentPoint.X);
    double top = Math.Min(_dragStartPoint.Y, currentPoint.Y);
    double width = Math.Abs(currentPoint.X - _dragStartPoint.X);
    double height = Math.Abs(currentPoint.Y - _dragStartPoint.Y);
    
    Canvas.SetLeft(_selectionRect, left);
    Canvas.SetTop(_selectionRect, top);
    _selectionRect.Width = width;
    _selectionRect.Height = height;
}

private void SelectionCanvas_MouseUp(object sender, MouseButtonEventArgs e)
{
    if (_selectionRect == null) return;
    
    SelectionCanvas.ReleaseMouseCapture();
    
    // 触发文本提取与复制逻辑
    CaptureSelectedText();
    
    // 释放选框资源
    _selectionRect = null;
}

三、实现跨TextBox的文本捕获与复制

编写CaptureSelectedText方法,遍历所有TextBox,判断选框覆盖区域并提取对应文本:

private void CaptureSelectedText()
{
    var selectedText = new StringBuilder();
    // 获取选框的全局区域
    Rect selectionArea = new Rect(
        Canvas.GetLeft(_selectionRect),
        Canvas.GetTop(_selectionRect),
        _selectionRect.Width,
        _selectionRect.Height
    );
    
    // 遍历ItemsControl中的所有文本项
    foreach (var item in TextItemsControl.Items)
    {
        if (TextItemsControl.ItemContainerGenerator.ContainerFromItem(item) is FrameworkElement container)
        {
            // 找到DataTemplate中的TextBox
            if (container.FindName("Text") is TextBox textBox)
            {
                // 将TextBox的坐标转换为Canvas全局坐标
                Point textBoxTopLeft = textBox.TransformToAncestor(SelectionCanvas).Transform(new Point(0, 0));
                Rect textBoxArea = new Rect(textBoxTopLeft, new Size(textBox.ActualWidth, textBox.ActualHeight));
                
                // 跳过与选框不相交的TextBox
                if (!selectionArea.IntersectsWith(textBoxArea)) continue;
                
                // 计算选框与TextBox的交集区域
                Rect intersectRect = Rect.Intersect(selectionArea, textBoxArea);
                // 将交集区域转换为TextBox内部坐标
                Point localStart = textBox.TransformFromAncestor(SelectionCanvas).Transform(new Point(intersectRect.Left, intersectRect.Top));
                Point localEnd = textBox.TransformFromAncestor(SelectionCanvas).Transform(new Point(intersectRect.Right, intersectRect.Bottom));
                
                // 获取对应位置的字符索引
                int startIndex = textBox.GetCharacterIndexFromPoint(localStart, true);
                int endIndex = textBox.GetCharacterIndexFromPoint(localEnd, false);
                
                // 处理索引反转的情况
                if (startIndex > endIndex)
                {
                    (startIndex, endIndex) = (endIndex, startIndex);
                }
                
                // 提取文本并拼接(防止索引越界)
                if (startIndex >= 0 && endIndex <= textBox.Text.Length)
                {
                    selectedText.Append(textBox.Text.Substring(startIndex, endIndex - startIndex));
                    // 可选:添加换行符,根据垂直排列的TextBox需求调整
                    selectedText.AppendLine();
                }
            }
        }
    }
    
    // 将最终文本复制到剪贴板
    if (selectedText.Length > 0)
    {
        Clipboard.SetText(selectedText.ToString().TrimEnd('\r', '\n'));
    }
}

四、额外优化

  • 禁用TextBox默认选择行为:在DataTemplate的TextBox中添加PreviewMouseDown事件,阻止默认文本选择:
<TextBox Name="Text" Text="{Binding Path=Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
         IsReadOnly="True"
         PreviewMouseDown="TextBox_PreviewMouseDown">
</TextBox>

后台代码:

private void TextBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    // 阻止TextBox自身的文本选择逻辑
    e.Handled = true;
}
  • 滚动场景适配:如果ItemsControl包含ScrollViewer,需要在坐标转换时加入滚动偏移量计算。

内容的提问来源于stack exchange,提问作者Heena Thakkar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 22:32:06