C1RichTextBox自定义复制/粘贴行为 [英] C1RichTextBox with custom copy/paste behavior

查看:692
本文介绍了C1RichTextBox自定义复制/粘贴行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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




  1. 在剪贴板粘贴操作,怎么能我发现如果内容是从另一个在我的Silverlight应用程序,或从外部应用程序 C1RichTextBox 复制?从外部应用程序,只有文字不应该格式粘贴。

  2. 复制/粘贴大量内嵌图片从一个C1RichTextBox到另一个不起作用。在< IMG> 元素具有存储在其数据URL中的图像内容。如果图像变得太大(约1MB),当复制到剪贴板中的的src 属性将被丢弃。



该解决方案应该:




  • 不会对全局剪贴板或的编辑行为的副作用 C1RichTextBox

  • 反对更改 C1RichTextBox 实施稳健的。

  • 不必修改/解析/剪贴板分析HTML文档。


解决方案

我花了一段时间来弄清楚这一切(一个多很多......)出来,我很高兴与任何人谁也处理这些问题分享。



我使用的是派生类来解决问题。





<预类=郎-C#prettyprint-覆盖> 公共类C1RichTextBoxExt:C1RichTextBox
{



1。从纯文本



解决方案外部应用程序粘贴在理论上是简单的:从内部RichTextBox中被复制/剪切到剪贴板文本之后获得HTML的保持。粘贴时,在什么最后复制到剪贴板比较当前的HTML。由于ComponentOne的剪贴板是全球性的,内容的变化,如果复制/剪切在另一个应用程序完成,因此HTML会有所不同。



要记得上复制的HTML我们用一个静态成员 C1RichTextBoxExt 里面的:



<预类=郎-C#prettyprint-覆盖> 私有静态字符串_clipboardHtml;在 C1RichTextBox.ClipboardCopy()



坏消息是$ C>等方法不是虚拟的。好消息是:对于复制/剪切/粘贴的键盘快捷方式而调用这些方法可以被禁用,如在构造函数中:



<预类=郎-C#prettyprint-覆盖> 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 线了我们自己的版本:



<预类=朗C#prettyprint-覆盖> 保护覆盖无效的onkeydown(KeyEventArgs E)
{
如果((Keyboard.Modifiers == ModifierKeys.Control)及及(e.Key = = Key.C)){ClipboardCopy(); }
,否则如果((Keyboard.Modifiers == ModifierKeys.Control)及及(e.Key == Key.Insert)){ClipboardCopy(); }
,否则如果((Keyboard.Modifiers == ModifierKeys.Control)及及(e.Key == Key.V)){ClipboardPaste(); }
,否则如果((Keyboard.Modifiers == ModifierKeys.Control)及及(e.Key == Key.X)){ClipboardCut(); }
,否则如果((Keyboard.Modifiers == ModifierKeys.Shift)及及(e.Key == Key.Insert)){ClipboardPaste(); }
,否则如果((Keyboard.Modifiers == ModifierKeys.Shift)及及(e.Key == Key.Delete)){ClipboardCut(); }
,否则
{
//默认行为
base.OnKeyDown(E);
的回报;
}

e.Handled = TRUE; //基类应该不会触发KeyDown事件
}

要不会意外调用基类方法,我在覆盖(见下文,使用修改)。在 ClipboardCopy()方法只是调用基类,之后存储在剪贴板HTML。这里的一个小缺陷是使用 Dispatcher.BeginInvoke()因为 C1RichTextBox.ClipboardCopy()存储在选定的文本一个 Dispatcher.BeginInvoke()调用内部剪贴板。因此,内容将只提供调度员有机会运行由 C1RichTextBox 规定的动作后。



<预类= 郎咸平-C#prettyprint-覆盖> 新公共无效ClipboardCopy()
{
base.ClipboardCopy();

Dispatcher.BeginInvoke(()=>
{
_clipboardHtml = C1.Silverlight.Clipboard.GetHtmlData();
});
}



ClipboardCut 方法是非常相似的:



<预类=郎-C#prettyprint-覆盖> 新公共无效ClipboardCut()
{
base.ClipboardCut();

Dispatcher.BeginInvoke(()=>
{
_clipboardHtml = C1.Silverlight.Clipboard.GetHtmlData();
});
}



ClipboardPaste 方法现在可以检测是否粘贴外部数据。粘贴文本只是没有那么简单。我想出了这个主意替换当前剪贴板中的内容与剪贴板上的纯文本表示。粘贴完成后,剪贴板应恢复这样的内容可以再次在其它应用中被粘贴。这也必须是一个 Dispatcher.BeginInvoke(),因为基类方法 C1RichTextBox.ClipboardPaste()执行内完成在延迟作用以及粘贴操作。



<预类=郎-C#prettyprint-覆盖> 新公共无效ClipboardPaste()
{
//如果全局剪贴板中的文本存储在_clipboardText的文本相匹配它是
//假设在C1剪贴板HTML是仍然有效
//(没有其他复印由用户作出)。
串电流= C1.Silverlight.Clipboard.GetHtmlData();

如果(当前== _clipboardHtml)
{
//文本是相同的 - >让基类粘贴HTML
base.ClipboardPaste();
}
,否则
{
//让基类粘贴文本仅
字符串文本= C1.Silverlight.Clipboard.GetTextData();
C1.Silverlight.Clipboard.SetData(文本);

base.ClipboardPaste();

Dispatcher.BeginInvoke(()=>
{
//恢复剪贴板
C1.Silverlight.Clipboard.SetData(电流);
} );
}
}



2。复制/粘贴大量内嵌图片



这里的想法是相似的:记住图像复制时,将它们放回粘贴在



所以首先我们需要存储在那里它的图像文件中:



<预类=郎-C#prettyprint-覆盖> 私人静态列表< C1TextElement> _clipboardImages;
私有静态诠释_imageCounter;



(使用_imageCounter的进行说明以后...)



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



<预类=郎-C#prettyprint-覆盖> 新公共无效ClipboardCopy()
{
_clipboardImages = FindImages(精选);

base.ClipboardCopy();
// ...因为上面贴
}

和类似:



<预类=郎-C#prettyprint-覆盖> 新公共无效ClipboardCut()
{
_clipboardImages = FindImages(精选);

base.ClipboardCut();
// ...因为上面贴
}



方法找到图像是:



<预类=郎-C#prettyprint-覆盖> 私人列表<&的BitmapImage GT; FindImages(C1TextRange选择= NULL)
{
VAR的结果=新的List<&的BitmapImage GT;();
如果(选择== NULL)
{
//文档包含在文档级别的所有元素。
的foreach(在文件C1TextElement ELEM)
{
FindImagesRecursive(ELEM,结果);
}
}
,否则
{
//选择包含所有的(选择)的元素 - >无需递归搜索
的foreach(在selection.ContainedElements C1TextElement ELEM)
{
如果(ELEM是C1InlineUIContainer)
{
FindImage(如ELEM C1InlineUIContainer,结果) ;
}
}
}

返回结果;
}

私人无效FindImagesRecursive(C1TextElement ELEM,列表与LT; BitmapImage的>清单)
{
如果(ELEM是C1Paragraph)
{
VAR第=(C1Paragraph)ELEM;
的foreach(INL C1Inline在para.Inlines)
{
FindImagesRecursive(INL,清单);
}
}
,否则如果(ELEM是C1Span)
{
VAR跨度=(C1Span)ELEM;
的foreach(C1Inline孩子span.Inlines)
{
FindImagesRecursive(儿童,清单);
}
}
,否则如果(ELEM是C1InlineUIContainer)
{
FindImage(如ELEM C1InlineUIContainer,清单);
}
}

私人无效FindImage(C1InlineUIContainer容器,列表与LT; BitmapImage的>清单)
{
如果(container.Content是的BitmapImage)
{
list.Add(container.Content为BitmapImage的);
}
}



我不会去到有关上述方法的详细信息,如果你分析 C1RichTextBox.Document 的结构,它们是非常简单的。



现在,我们怎么恢复图片?我找到的最好的是使用的 C1RichTextBox.HtmlFilter ConvertingHtmlNode 事件。此事件的每一个的HTML节点转换成C1TextElement时间。我们同意它在构造函数中:



<预类=郎-C#prettyprint-覆盖> HtmlFilter.ConvertingHtmlNode + =新的EventHandler< ConvertingHtmlNodeEventArgs>( HtmlFilter_ConvertingHtmlNode);

和实现它是这样的:



<预类=郎-C#prettyprint-覆盖> 无效HtmlFilter_ConvertingHtmlNode(对象发件人,ConvertingHtmlNodeEventArgs E)
{
如果(e.HtmlNode是C1HtmlElement)
{
VAR ELEM = e.HtmlNode为C1HtmlElement;

如果(elem.Name.ToLower()==IMG&放大器;&安培; _clipboardImages = NULL&放大器;!&安培; _clipboardImages.Count> _imageCounter)
{
(!elem.Attributes.ContainsKey(SRC))//如果键比较是不区分大小写
{
e.Parent.Children.Add(_clipboardImages [_imageCounter] .Clone());
e.Handled = TRUE;
}
_imageCounter ++;
}
}
}



因此​​,对于每个HTML元素节点一个名为IMG我们检查src属性丢失。如果是这样,我们添加下一个存储的图像,而不是通过设置 e.Handled = TRUE告诉该事件处理现在(这个HTML节点)事件源;
上的图像是下一个图像是由递增每个访问IMG元素的 _imageCounter 字段确定。



_imageCounter 字段必须将其复位 ClipboardPaste()被调用,所以我们做的:



<预类=郎-C#prettyprint-覆盖> 新公共无效ClipboardPaste()
{
_imageCounter = 0;

串电流= C1.Silverlight.Clipboard.GetHtmlData();
// ...因为上面贴
}



结论



如果您复制/粘贴(没有双关语意...)上面的贴在一起的所有代码块,你应该结束了,有没有副作用(至少没有已知的解决方案笔者今天)的,反对的变化稳健和做几乎没有HTML处理。


When using C1RichTextBox in Silverlight 5 with IE 10, I am facing two major issues:

  1. During a clipboard paste operation, how can I detect if the content was copied from another C1RichTextBox in my Silverlight application or from an external application? From external applications, only text should be pasted without formatting.
  2. Copy/Pasting large inline images from one C1RichTextBox to another does not work. The <img> elements have the image content stored in their data URL. If the image becomes too large (approx 1MB), the src attribute is dropped when copied to the clipboard.

The solution should:

  • Not have side-effects on the global clipboard or on the editing behavior of the C1RichTextBox.
  • Be robust against changes to the C1RichTextBox implementation.
  • Not have to modify/parse/analyze the HTML document in the clipboard.

解决方案

It took me a while to figure all this (an a lot more...) out and I am happy to share with anyone who has to deal with these issues.

I am using a derived class to solve the issues

public class C1RichTextBoxExt : C1RichTextBox
{

1. Pasting from external application with text-only

The solution is simple in theory: Get a hold of the HTML after text from within the RichTextBox was copied/cut to the clipboard. When pasting, compare the current HTML in the clipboard with what was last copied. Because the clipboard in ComponentOne is global, the content changes if a Copy/Cut was done in another application and thus the HTML will be different.

To remember the last copied HTML, we use a static member inside C1RichTextBoxExt:

private static string _clipboardHtml;

The bad news is: The C1RichTextBox.ClipboardCopy() etc. methods are not virtual. The good news is: The keyboard shortcuts for Copy/Cut/Paste which call these methods can be disabled, e.g. in the constructor:

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);

Now that the methods C1RichTextBox.ClipboardCopy() etc. are no longer called we can wire up our own version by overriding 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
}

To not accidentally call the base class methods, I am overwriting them (see below, using new modifier). The ClipboardCopy() method just calls the base class and afterwards stores the clipboard HTML. A small pitfall here was to use Dispatcher.BeginInvoke() since the C1RichTextBox.ClipboardCopy() stores the selected text in the clipboard inside a Dispatcher.BeginInvoke() invocation. So the content will only be available after the dispatcher had a chance to run the action provided by C1RichTextBox.

new public void ClipboardCopy()
{
    base.ClipboardCopy();

    Dispatcher.BeginInvoke(() =>
    {
        _clipboardHtml = C1.Silverlight.Clipboard.GetHtmlData();
    });
}

The ClipboardCut method is very similar:

new public void ClipboardCut()
{
    base.ClipboardCut();

    Dispatcher.BeginInvoke(() =>
    {
        _clipboardHtml = C1.Silverlight.Clipboard.GetHtmlData();
    });
}

The ClipboardPaste method can now detect if pasting external data. Pasting text only is no so straightforward. I came up with the idea to replace the current clipboard content with the text-only representation of the clipboard. After pasting is done, the clipboard should be restored so the content can be pasted again in other applications. This also has to be done within a Dispatcher.BeginInvoke() since the base class method C1RichTextBox.ClipboardPaste() performs the paste operation in a delayed action as well.

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. Copy/Pasting large inline images

The idea here is similar: Remember the images when copied, put them back in during paste.

So first we need to store where which image is in the document:

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

(The use of _imageCounter will be explained later...)

Then, before Copy/Cut is executed, we search for all images:

new public void ClipboardCopy()
{
    _clipboardImages = FindImages(Selection);

    base.ClipboardCopy();
    // ... as posted above
}

and similar:

new public void ClipboardCut()
{
    _clipboardImages = FindImages(Selection);

    base.ClipboardCut();
    // ... as posted above
}

The methods to find the images are:

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);
    }
}

I won't go into details about the above methods, they are pretty straightforward if you analyze the structure of C1RichTextBox.Document.

Now, how do we restore the images? The best I found is to use the ConvertingHtmlNode event of the C1RichTextBox.HtmlFilter. This event is fired every time a HTML node is converted into a C1TextElement. We subscribe to it in the constructor:

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

and implement it like this:

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++;
        }
    }
}

So for each HTML element node with the name "img" we check if the "src" attribute is missing. If so, we add the next stored image instead and tell the event source that the event is now handled (for this HTML node) by setting e.Handled = true; Which image is the "next" image is determined by the _imageCounter field which is incremented for each visited "img" element.

The _imageCounter field must be reset when ClipboardPaste() is invoked, so we do:

new public void ClipboardPaste()
{
    _imageCounter = 0;

    string current = C1.Silverlight.Clipboard.GetHtmlData();
    // ... as posted above
}

Conclusion

If you copy/paste (no pun intended...) all code blocks posted above together, you should end up with a solution which has no side-effects (at least none known to the author as of today), is robust against changes and does almost no HTML processing.

这篇关于C1RichTextBox自定义复制/粘贴行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆