首页 文章

C1RichTextBox具有自定义复制/粘贴行为

提问于
浏览
1

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

  • 在剪贴板粘贴操作期间,如何检测内容是从Silverlight应用程序中的另一个 C1RichTextBox 还是从外部应用程序复制的?从外部应用程序,只应粘贴文本而不进行格式化 .

  • 从一个C1RichTextBox复制/粘贴大型内嵌图像不起作用 . <img> 元素将图像内容存储在其数据URL中 . 如果图像变得太大(大约1MB),则复制到剪贴板时会删除 src 属性 .

解决方案应该:

  • 没有全局剪贴板或 C1RichTextBox 的编辑行为的副作用 .

  • C1RichTextBox 实施的更改具有可靠性 .

  • 无需修改/解析/分析剪贴板中的HTML文档 .

1 回答

  • 3

    我花了一段时间来计算所有这些(更多......),我很高兴与任何必须处理这些问题的人分享 .

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

    public class C1RichTextBoxExt : C1RichTextBox
    {
    

    1.从外部应用程序粘贴纯文本

    解决方案理论上很简单:在RichTextBox中的文本被复制/剪切到剪贴板后,获取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的使用将在后面解释......)

    然后,在执行复制/剪切之前,我们搜索所有图像:

    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节点)_2777395_图像由"next"字段确定,该字段针对每个访问过的"img"元素递增 .

    调用 ClipboardPaste() 时必须重置 _imageCounter 字段,因此我们执行以下操作:

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

    结论

    如果你复制/粘贴(没有双关语......)所有代码块一起发布在上面,你应该得到一个没有副作用的解决方案(至少今天作者都不知道),对变化是强有力的几乎没有HTML处理 .

相关问题