具有自定义 copy/paste 行为的 C1RichTextBox
C1RichTextBox with custom copy/paste behavior
在 Silverlight 5 和 IE 10 中使用 C1RichTextBox 时,我面临两个主要问题:
- 在剪贴板粘贴操作期间,如何检测内容是从我的 Silverlight 应用程序中的另一个
C1RichTextBox
还是从外部应用程序复制的?从外部应用程序中,只应粘贴文本而不设置格式。
- Copy/Pasting 从一个 C1RichTextBox 到另一个的大内联图像不起作用。
<img>
元素将图像内容存储在其数据 URL 中。如果图像变得太大(大约 1MB),src
属性在复制到剪贴板时会被删除。
解决方案应该:
- 对全局剪贴板或
C1RichTextBox
的编辑行为没有副作用。
- 对
C1RichTextBox
实施的更改保持稳健。
- 不必modify/parse/analyze剪贴板中的HTML文件。
我花了一段时间才弄清楚所有这些(还有很多...),我很乐意与任何必须处理这些问题的人分享。
我正在使用派生的 class 来解决问题
public class C1RichTextBoxExt : C1RichTextBox
{
1。使用纯文本从外部应用程序粘贴
解决方案在理论上很简单:在 RichTextBox 中的文本 copied/cut 到剪贴板后获取 HTML。粘贴时,将剪贴板中的当前 HTML 与上次复制的内容进行比较。因为 ComponentOne 中的剪贴板是全局的,所以如果 Copy/Cut 在另一个应用程序中完成,内容会发生变化,因此 HTML 会有所不同。
为了记住最后复制的HTML,我们在C1RichTextBoxExt
:
里面使用了一个静态成员
private static string _clipboardHtml;
坏消息是:C1RichTextBox.ClipboardCopy()
等方法不是虚拟的。好消息是:可以禁用调用这些方法的 Copy/Cut/Paste 的键盘快捷键,例如在构造函数中:
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
}
为了不意外地调用基础 class 方法,我覆盖了它们(见下文,使用 new
修饰符)。 ClipboardCopy()
方法只是调用基 class 然后存储剪贴板 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()
内完成,因为基础 class 方法 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。 Copy/Pasting 大内联图片
这里的想法是相似的:复制时记住图像,粘贴时将它们放回原处。
所以首先我们需要存储图像在文档中的位置:
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.HtmlFilter
的 ConvertingHtmlNode
事件。每次将 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 节点)
哪个图像是 "next" 图像由 _imageCounter
字段确定,每个访问的 "img" 元素递增。
调用ClipboardPaste()
时必须重置_imageCounter
字段,所以我们这样做:
new public void ClipboardPaste()
{
_imageCounter = 0;
string current = C1.Silverlight.Clipboard.GetHtmlData();
// ... as posted above
}
结论
如果您 copy/paste(没有双关语意...)将上面的所有代码块一起发布,您最终应该得到一个没有副作用的解决方案(至少 none 已知截至今天的作者),对变化很稳健,几乎不做 HTML 处理。
在 Silverlight 5 和 IE 10 中使用 C1RichTextBox 时,我面临两个主要问题:
- 在剪贴板粘贴操作期间,如何检测内容是从我的 Silverlight 应用程序中的另一个
C1RichTextBox
还是从外部应用程序复制的?从外部应用程序中,只应粘贴文本而不设置格式。 - Copy/Pasting 从一个 C1RichTextBox 到另一个的大内联图像不起作用。
<img>
元素将图像内容存储在其数据 URL 中。如果图像变得太大(大约 1MB),src
属性在复制到剪贴板时会被删除。
解决方案应该:
- 对全局剪贴板或
C1RichTextBox
的编辑行为没有副作用。 - 对
C1RichTextBox
实施的更改保持稳健。 - 不必modify/parse/analyze剪贴板中的HTML文件。
我花了一段时间才弄清楚所有这些(还有很多...),我很乐意与任何必须处理这些问题的人分享。
我正在使用派生的 class 来解决问题
public class C1RichTextBoxExt : C1RichTextBox
{
1。使用纯文本从外部应用程序粘贴
解决方案在理论上很简单:在 RichTextBox 中的文本 copied/cut 到剪贴板后获取 HTML。粘贴时,将剪贴板中的当前 HTML 与上次复制的内容进行比较。因为 ComponentOne 中的剪贴板是全局的,所以如果 Copy/Cut 在另一个应用程序中完成,内容会发生变化,因此 HTML 会有所不同。
为了记住最后复制的HTML,我们在C1RichTextBoxExt
:
private static string _clipboardHtml;
坏消息是:C1RichTextBox.ClipboardCopy()
等方法不是虚拟的。好消息是:可以禁用调用这些方法的 Copy/Cut/Paste 的键盘快捷键,例如在构造函数中:
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
}
为了不意外地调用基础 class 方法,我覆盖了它们(见下文,使用 new
修饰符)。 ClipboardCopy()
方法只是调用基 class 然后存储剪贴板 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()
内完成,因为基础 class 方法 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。 Copy/Pasting 大内联图片
这里的想法是相似的:复制时记住图像,粘贴时将它们放回原处。
所以首先我们需要存储图像在文档中的位置:
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.HtmlFilter
的 ConvertingHtmlNode
事件。每次将 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 节点)
哪个图像是 "next" 图像由 _imageCounter
字段确定,每个访问的 "img" 元素递增。
调用ClipboardPaste()
时必须重置_imageCounter
字段,所以我们这样做:
new public void ClipboardPaste()
{
_imageCounter = 0;
string current = C1.Silverlight.Clipboard.GetHtmlData();
// ... as posted above
}
结论
如果您 copy/paste(没有双关语意...)将上面的所有代码块一起发布,您最终应该得到一个没有副作用的解决方案(至少 none 已知截至今天的作者),对变化很稳健,几乎不做 HTML 处理。