C1RichTextBox与自定义复制/粘贴行为

本文关键字:自定义 复制 C1RichTextBox | 更新日期: 2023-09-27 18:05:33

在Silverlight 5和IE 10中使用C1RichTextBox时,我面临两个主要问题:

  1. 在剪贴板粘贴操作期间,我如何检测内容是否从我的Silverlight应用程序中的另一个C1RichTextBox或从外部应用程序复制?从外部应用程序,只能粘贴文本,不需要格式化。
  2. 从一个C1RichTextBox复制/粘贴大型内联图像到另一个不工作。<img>元素将图像内容存储在其数据URL中。如果图像变得太大(大约1MB), src属性在复制到剪贴板时被删除。

解决方案应该:

  • 对全局剪贴板或C1RichTextBox的编辑行为没有副作用。
  • C1RichTextBox实现的更改保持健壮性。
  • 不需要修改/解析/分析剪贴板中的HTML文档

C1RichTextBox与自定义复制/粘贴行为

我花了一段时间才弄清楚所有这些(以及更多……),我很高兴与任何必须处理这些问题的人分享。

我正在使用派生类来解决问题

public class C1RichTextBoxExt : C1RichTextBox
{
<标题> 1。从外部应用程序粘贴纯文本

解决方案在理论上很简单:从RichTextBox内的文本被复制/剪切到剪贴板后获取HTML。粘贴时,将剪贴板中的当前HTML与上次复制的HTML进行比较。因为ComponentOne中的剪贴板是全局的,所以如果在另一个应用程序中执行了复制/剪切操作,那么内容就会发生变化,因此HTML将会不同。

为了记住最后复制的HTML,我们在C1RichTextBoxExt内部使用了一个静态成员:

private static string _clipboardHtml;

坏消息是:C1RichTextBox.ClipboardCopy()等方法不是虚拟的。好消息是:调用这些方法的复制/剪切/粘贴快捷键可以被禁用,例如在构造函数中:

RemoveShortcut(ModifierKeys.Control, Key.C);
RemoveShortcut(ModifierKeys.Control, Key.Insert);
RemoveShortcut(ModifierKeys.Control, Key.V);
RemoveShortcut(ModifierKeys.Shift  , Key.Insert);
RemoveShortcut(ModifierKeys.Control, Key.X);
RemoveShortcut(ModifierKeys.Shift  , Key.Delete);

现在C1RichTextBox.ClipboardCopy()等方法不再被调用,我们可以通过重写OnKeyDown:

来连接我们自己的版本。
protected override void OnKeyDown(KeyEventArgs e)
{
    if      ((Keyboard.Modifiers == ModifierKeys.Control) && (e.Key == Key.C))      { ClipboardCopy();  }
    else if ((Keyboard.Modifiers == ModifierKeys.Control) && (e.Key == Key.Insert)) { ClipboardCopy();  }
    else if ((Keyboard.Modifiers == ModifierKeys.Control) && (e.Key == Key.V))      { ClipboardPaste(); }
    else if ((Keyboard.Modifiers == ModifierKeys.Control) && (e.Key == Key.X))      { ClipboardCut();   }
    else if ((Keyboard.Modifiers == ModifierKeys.Shift)   && (e.Key == Key.Insert)) { ClipboardPaste(); } 
    else if ((Keyboard.Modifiers == ModifierKeys.Shift)   && (e.Key == Key.Delete)) { ClipboardCut();   } 
    else
    {
        // default behaviour
        base.OnKeyDown(e);
        return;
    }
    e.Handled = true; // base class should not fire KeyDown event
}

为了不意外地调用基类方法,我重写了它们(见下文,使用new修饰符)。ClipboardCopy()方法只调用基类,然后存储剪贴板HTML。这里的一个小缺陷是使用Dispatcher.BeginInvoke(),因为C1RichTextBox.ClipboardCopy()Dispatcher.BeginInvoke()调用中存储剪贴板中的选定文本。因此,只有在调度程序有机会运行C1RichTextBox提供的操作后,内容才可用。

new public void ClipboardCopy()
{
    base.ClipboardCopy();
    Dispatcher.BeginInvoke(() =>
    {
        _clipboardHtml = C1.Silverlight.Clipboard.GetHtmlData();
    });
}

ClipboardCut方法非常相似:

new public void ClipboardCut()
{
    base.ClipboardCut();
    Dispatcher.BeginInvoke(() =>
    {
        _clipboardHtml = C1.Silverlight.Clipboard.GetHtmlData();
    });
}

ClipboardPaste方法现在可以检测是否粘贴外部数据。仅仅粘贴文本就没有那么简单了。我想到了用剪贴板的纯文本表示取代当前剪贴板内容的想法。粘贴完成后,应恢复剪贴板,以便在其他应用程序中再次粘贴内容。这也必须在Dispatcher.BeginInvoke()中完成,因为基类方法C1RichTextBox.ClipboardPaste()也在延迟动作中执行粘贴操作。

new public void ClipboardPaste()
{
    // If the text in the global clipboard matches the text stored in _clipboardText it is 
    // assumed that the HTML in the C1 clipboard is still valid 
    // (no other Copy was made by the user).
    string current = C1.Silverlight.Clipboard.GetHtmlData();
    if(current == _clipboardHtml)
    {
        // text is the same -> Let base class paste HTML
        base.ClipboardPaste();
    }
    else
    {
        // let base class paste text only
        string text = C1.Silverlight.Clipboard.GetTextData();
        C1.Silverlight.Clipboard.SetData(text);
        base.ClipboardPaste(); 
        Dispatcher.BeginInvoke(() =>
        {
            // restore clipboard
            C1.Silverlight.Clipboard.SetData(current);
        });
    }
}

2。复制/粘贴大型内联图像

这里的思想是类似的:记住复制时的图像,在粘贴时把它们放回去。

首先我们需要存储图像在文档中的位置:

private static List<C1TextElement> _clipboardImages;
private static int _imageCounter;

(_imageCounter的用法将在后面解释…)

然后,在执行Copy/Cut之前,我们搜索所有的图像:

new public void ClipboardCopy()
{
    _clipboardImages = FindImages(Selection);
    base.ClipboardCopy();
    // ... as posted above
}

和类似:

new public void ClipboardCut()
{
    _clipboardImages = FindImages(Selection);
    base.ClipboardCut();
    // ... as posted above
}

查找图像的方法有:

private List<BitmapImage> FindImages(C1TextRange selection = null)
{
    var result = new List<BitmapImage>();
    if (selection == null)
    {
        // Document Contains all elements at the document level.
        foreach (C1TextElement elem in Document)
        {
            FindImagesRecursive(elem, result);
        }
    }
    else
    {
        // Selection contains all (selected) elements -> no need to search recursively
        foreach (C1TextElement elem in selection.ContainedElements)
        {
            if (elem is C1InlineUIContainer)
            {
                FindImage(elem as C1InlineUIContainer, result);
            }
        }
    }
    return result;
}
private void FindImagesRecursive(C1TextElement elem, List<BitmapImage> list)
{
    if (elem is C1Paragraph)
    {
        var para = (C1Paragraph)elem;
        foreach (C1Inline inl in para.Inlines)
        {
            FindImagesRecursive(inl, list);
        }
    }
    else if (elem is C1Span)
    {
        var span = (C1Span)elem;
        foreach (C1Inline child in span.Inlines)
        {
            FindImagesRecursive(child, list);
        }
    }
    else if (elem is C1InlineUIContainer)
    {
        FindImage(elem as C1InlineUIContainer, list);
    }
}
private void FindImage(C1InlineUIContainer container, List<BitmapImage> list)
{
    if (container.Content is BitmapImage)
    {
        list.Add(container.Content as BitmapImage);
    }
}

我不会详细介绍上述方法,如果你分析C1RichTextBox.Document的结构,它们非常简单。

现在,我们如何恢复图像?我发现最好的方法是使用C1RichTextBox.HtmlFilterConvertingHtmlNode事件。每次将HTML节点转换为C1TextElement时都会触发此事件。我们在构造函数中订阅它:

HtmlFilter.ConvertingHtmlNode += new EventHandler<ConvertingHtmlNodeEventArgs>(HtmlFilter_ConvertingHtmlNode);

,并像这样实现它:

void HtmlFilter_ConvertingHtmlNode(object sender, ConvertingHtmlNodeEventArgs e)
{
    if (e.HtmlNode is C1HtmlElement)
    {
        var elem = e.HtmlNode as C1HtmlElement;
        if (elem.Name.ToLower() == "img" && _clipboardImages != null && _clipboardImages.Count > _imageCounter)
        {
            if (!elem.Attributes.ContainsKey("src")) // key comparison is not case sensitive
            {
                e.Parent.Children.Add(_clipboardImages[_imageCounter].Clone());
                e.Handled = true;
            }
            _imageCounter++;
        }
    }
}

因此,对于每个名称为"img"的HTML元素节点,我们检查"src"属性是否缺失。如果是这样,我们添加下一个存储的图像,并通过设置e.Handled = true;告诉事件源事件现在已经处理了(针对这个HTML节点)哪个图像是"下一个"图像由_imageCounter字段决定,该字段对每个访问的"img"元素递增。

ClipboardPaste()被调用时,_imageCounter字段必须被重置,所以我们这样做:

new public void ClipboardPaste()
{
    _imageCounter = 0;
    string current = C1.Silverlight.Clipboard.GetHtmlData();
    // ... as posted above
}
结论

如果你把上面发布的所有代码块复制/粘贴(没有双关语…)在一起,你应该最终得到一个没有副作用的解决方案(至少到目前为止作者还不知道),对更改是健壮的,几乎不做HTML处理。