【问题标题】:C1RichTextBox with custom copy/paste behavior具有自定义复制/粘贴行为的 C1RichTextBox
【发布时间】:2015-10-22 10:26:33
【问题描述】:

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

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

解决方案应该:

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

【问题讨论】:

    标签: c# richtextbox silverlight-5.0 copy-paste componentone


    【解决方案1】:

    我花了一段时间才弄清楚这一切(还有很多……),我很高兴与任何需要处理这些问题的人分享。

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

    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的使用后面会解释……)

    然后,在执行 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”元素递增。

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

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

    结论

    如果您复制/粘贴(没有双关语......)上面发布的所有代码块,您最终应该得到一个没有副作用的解决方案(至少截至今天作者不知道),是对变化具有鲁棒性,几乎不进行 HTML 处理。

    【讨论】:

      猜你喜欢
      • 2012-09-05
      • 2015-10-06
      • 2010-09-09
      • 2018-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-23
      • 2019-10-14
      相关资源
      最近更新 更多