- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我将 FlowDocument 与包含(或作为基类)一些自定义 block 的 BlockUIContainer 和 InlineUIContainer 元素一起使用 - SVG、数学公式等。由于使用 Selection.Load(stream, DataFormats.XamlPackage) 将无法工作,因为序列化将删除 *UIContainers 的内容,除非 Child 属性是 Microsoft 引用源中可用的图像:
private static void WriteStartXamlElement(...)
{
...
if ((inlineUIContainer == null || !(inlineUIContainer.Child is Image)) &&
(blockUIContainer == null || !(blockUIContainer.Child is Image)))
{
...
elementTypeStandardized = TextSchema.GetStandardElementType(elementType, /*reduceElement:*/true);
}
...
}
在这种情况下,唯一的选择是使用 XamlWriter.Save 和 XamlReader.Load,它们可以完美地工作,序列化和反序列化 FlowDocument 的所有必需属性和对象,但必须手动实现复制+粘贴作为默认实现Copy+Paste 的使用 Selection.Load/Save。
复制/粘贴非常重要,因为它还用于处理 RichTextBox 控件内或控件之间的元素拖动 - 这是无需自定义拖动代码即可操作对象的唯一方法。
这就是为什么我希望使用 FlowDocument 序列化来实现复制/粘贴,但不幸的是它存在一些问题:
显然对象无法从一个文档中移除并添加到另一个文档(我最近发现了一个死胡同):“InlineCollection”元素无法插入到树中,因为它已经是树的子元素。
[TextElementCollection.cs]
public void InsertAfter(TextElementType previousSibling, TextElementType newItem)
{
...
if (previousSibling.Parent != this.Parent)
throw new InvalidOperationException(System.Windows.SR.Get("TextElementCollection_PreviousSiblingDoesNotBelongToThisCollection", new object[1]
{
(object) previousSibling.GetType().Name
}));
...
}
我考虑在所有需要移动到另一个文档的元素中使用反射来设置 FrameworkContentElement._parent,但这是不得已而为之的肮脏解决方案:
理论上,我只能复制所需的对象:(可选)在选择开始时使用文本部分运行,中间的所有段落和内联以及(可能)在结束时部分运行,将它们封装在自定义中使用 XamlReader/XamlWriter 进行类和序列化/反序列化。
这是自定义 RichTextBox 控件实现,其中包含部分工作的自定义复制/粘贴代码:
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
namespace FlowMathTest
{
public class CustomRichTextBoxTag: DependencyObject
{
public static readonly DependencyProperty SelectionStartProperty = DependencyProperty.Register(
"SelectionStart",
typeof(int),
typeof(CustomRichTextBoxTag));
public int SelectionStart
{
get { return (int)GetValue(SelectionStartProperty); }
set { SetValue(SelectionStartProperty, value); }
}
public static readonly DependencyProperty SelectionEndProperty = DependencyProperty.Register(
"SelectionEnd",
typeof(int),
typeof(CustomRichTextBoxTag));
public int SelectionEnd
{
get { return (int)GetValue(SelectionEndProperty); }
set { SetValue(SelectionEndProperty, value); }
}
}
public class CustomRichTextBox: RichTextBox
{
public CustomRichTextBox()
{
DataObject.AddCopyingHandler(this, OnCopy);
DataObject.AddPastingHandler(this, OnPaste);
}
protected override void OnSelectionChanged(RoutedEventArgs e)
{
base.OnSelectionChanged(e);
var tag = Document.Tag as CustomRichTextBoxTag;
if(tag == null)
{
tag = new CustomRichTextBoxTag();
Document.Tag = tag;
}
tag.SelectionStart = Document.ContentStart.GetOffsetToPosition(Selection.Start);
tag.SelectionEnd = Document.ContentStart.GetOffsetToPosition(Selection.End);
}
private void OnCopy(object sender, DataObjectCopyingEventArgs e)
{
if(e.DataObject != null)
{
e.Handled = true;
var ms = new MemoryStream();
XamlWriter.Save(Document, ms);
e.DataObject.SetData(DataFormats.Xaml, ms);
}
}
private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
var xamlData = e.DataObject.GetData(DataFormats.Xaml) as MemoryStream;
if(xamlData != null)
{
xamlData.Position = 0;
var fd = XamlReader.Load(xamlData) as FlowDocument;
if(fd != null)
{
var tag = fd.Tag as CustomRichTextBoxTag;
if(tag != null)
{
InsertAt(Document, Selection.Start, Selection.End, fd, fd.ContentStart.GetPositionAtOffset(tag.SelectionStart), fd.ContentStart.GetPositionAtOffset(tag.SelectionEnd));
e.Handled = true;
}
}
}
}
public static void InsertAt(FlowDocument destDocument, TextPointer destStart, TextPointer destEnd, FlowDocument sourceDocument, TextPointer sourceStart, TextPointer sourceEnd)
{
var destRange = new TextRange(destStart, destEnd);
destRange.Text = string.Empty;
// insert partial text of the first run in the selection
if(sourceStart.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
var sourceRange = new TextRange(sourceStart, sourceStart.GetNextContextPosition(LogicalDirection.Forward));
destStart.InsertTextInRun(sourceRange.Text);
sourceStart = sourceStart.GetNextContextPosition(LogicalDirection.Forward);
destStart = destStart.GetNextContextPosition(LogicalDirection.Forward);
}
var field = typeof(FrameworkContentElement).GetField("_parent", BindingFlags.NonPublic | BindingFlags.Instance);
while(sourceStart != null && sourceStart.CompareTo(sourceEnd) <= 0 && sourceStart.Paragraph != null)
{
var sourceInline = sourceStart.Parent as Inline;
if(sourceInline != null)
{
sourceStart.Paragraph.Inlines.Remove(sourceInline);
if(destStart.Parent is Inline)
{
field.SetValue(sourceInline, null);
destStart.Paragraph.Inlines.InsertAfter(destStart.Parent as Inline, sourceInline);
}
else
{
var p = new Paragraph();
destDocument.Blocks.InsertAfter(destStart.Paragraph, p);
p.Inlines.Add(sourceInline);
}
sourceStart = sourceStart.GetNextContextPosition(LogicalDirection.Forward);
}
else
{
var sourceBlock = sourceStart.Parent as Block;
field.SetValue(sourceBlock, null);
destDocument.Blocks.InsertAfter(destStart.Paragraph, sourceBlock);
sourceStart = sourceStart.GetNextContextPosition(LogicalDirection.Forward);
}
}
}
}
}
问题 - 是否存在使用 XamlReader 和 XamlWriter 为 FlowDocument 自定义复制+粘贴代码的现有解决方案?如何
修复上面的代码,使其不会提示不同的 FlowDocument 对象或
解决此限制?
编辑:作为一项实验,我实现了 2),以便可以将对象从一个 FlowDocument 移动到另一个。上面的代码已更新 - 所有对“字段”变量的引用。
最佳答案
看来赏金期快到期了,我突破了如何实现上述问题,所以在这里分享。
首先,TextRange.Save 有一个“preserveTextElements”参数,可用于序列化 InlineUIContainer 和 BlockUIContainer 元素。此外,这两个控件都不是密封的,因此可以用作自定义 TextElement 实现的基类。
考虑到以上几点:
我创建了一个继承自 InlineUIContainer 的 InlineMedia 元素,它使用 XamlReader 和 XamlWriter 将其子项“手动”序列化为“ChildSource”依赖属性,并从默认序列化程序中隐藏原始“子项”
我将 CustomRichTextBox 的上述实现更改为使用 range.Save(ms, DataFormats.Xaml, true) 复制选择。
正如您所注意到的,不需要特殊的粘贴处理,因为在剪贴板中交换原始 Xaml 后 Xaml 被很好地反序列化,这意味着从所有 CustomRichtextBox 控件拖 Action 为副本工作,而粘贴甚至可以工作到普通的 RichTextBox 中。
唯一的限制是,对于所有 InlineMedia 控件,需要在序列化整个文档之前通过序列化它的子项来更新 ChildSource 属性,我发现没有办法自动执行此操作(在保存元素之前 Hook 到 TextRange.Save)。
我可以接受,但没有这个问题的更好的解决方案仍然会得到赏金!
InlineMedia 元素代码:
public class InlineMedia: InlineUIContainer
{
public InlineMedia()
{
}
public InlineMedia(UIElement childUIElement) : base(childUIElement)
{
UpdateChildSource();
}
public InlineMedia(UIElement childUIElement, TextPointer insertPosition)
: base(childUIElement, insertPosition)
{
UpdateChildSource();
}
public static readonly DependencyProperty ChildSourceProperty = DependencyProperty.Register
(
"ChildSource",
typeof(string),
typeof(InlineMedia),
new FrameworkPropertyMetadata(null, OnChildSourceChanged));
public string ChildSource
{
get
{
return (string)GetValue(ChildSourceProperty);
}
set
{
SetValue(ChildSourceProperty, value);
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new UIElement Child
{
get
{
return base.Child;
}
set
{
base.Child = value;
UpdateChildSource();
}
}
public void UpdateChildSource()
{
IsInternalChildSourceChange = true;
try
{
ChildSource = Save();
}
finally
{
IsInternalChildSourceChange = false;
}
}
public string Save()
{
if(Child == null)
{
return null;
}
using(var stream = new MemoryStream())
{
XamlWriter.Save(Child, stream);
stream.Position = 0;
using(var reader = new StreamReader(stream, Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
}
public void Load(string sourceData)
{
if(string.IsNullOrEmpty(sourceData))
{
base.Child = null;
}
else
{
using(var stream = new MemoryStream(Encoding.UTF8.GetBytes(sourceData)))
{
var child = XamlReader.Load(stream);
base.Child = (UIElement)child;
}
}
}
private static void OnChildSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var img = (InlineMedia) sender;
if(img != null && !img.IsInternalChildSourceChange)
{
img.Load((string)e.NewValue);
}
}
protected bool IsInternalChildSourceChange { get; private set; }
}
CustomRichTextBox控件代码:
public class CustomRichTextBox: RichTextBox
{
public CustomRichTextBox()
{
DataObject.AddCopyingHandler(this, OnCopy);
}
private void OnCopy(object sender, DataObjectCopyingEventArgs e)
{
if(e.DataObject != null)
{
UpdateDocument();
var range = new TextRange(Selection.Start, Selection.End);
using(var ms = new MemoryStream())
{
range.Save(ms, DataFormats.Xaml, true);
ms.Position = 0;
using(var reader = new StreamReader(ms, Encoding.UTF8))
{
var xaml = reader.ReadToEnd();
e.DataObject.SetData(DataFormats.Xaml, xaml);
}
}
e.Handled = true;
}
}
public void UpdateDocument()
{
ObjectHelper.ExecuteRecursive<InlineMedia>(Document, i => i.UpdateChildSource(), FlowDocumentVisitors);
}
private static readonly Func<object, object>[] FlowDocumentVisitors =
{
x => (x is FlowDocument) ? ((FlowDocument) x).Blocks : null,
x => (x is Section) ? ((Section) x).Blocks : null,
x => (x is BlockUIContainer) ? ((BlockUIContainer) x).Child : null,
x => (x is InlineUIContainer) ? ((InlineUIContainer) x).Child : null,
x => (x is Span) ? ((Span) x).Inlines : null,
x => (x is Paragraph) ? ((Paragraph) x).Inlines : null,
x => (x is Table) ? ((Table) x).RowGroups : null,
x => (x is Table) ? ((Table) x).Columns : null,
x => (x is Table) ? ((Table) x).RowGroups.SelectMany(rg => rg.Rows) : null,
x => (x is Table) ? ((Table) x).RowGroups.SelectMany(rg => rg.Rows).SelectMany(r => r.Cells) : null,
x => (x is TableCell) ? ((TableCell) x).Blocks : null,
x => (x is TableCell) ? ((TableCell) x).BorderBrush : null,
x => (x is List) ? ((List) x).ListItems : null,
x => (x is ListItem) ? ((ListItem) x).Blocks : null
};
}
最后是 ObjectHelper 类 - 访问者助手:
public static class ObjectHelper
{
public static void ExecuteRecursive(object item, Action<object> execute, params Func<object, object>[] childSelectors)
{
ExecuteRecursive<object, object>(item, null, (c, i) => execute(i), childSelectors);
}
public static void ExecuteRecursive<TObject>(object item, Action<TObject> execute, params Func<object, object>[] childSelectors)
{
ExecuteRecursive<object, TObject>(item, null, (c, i) => execute(i), childSelectors);
}
public static void ExecuteRecursive<TContext, TObject>(object item, TContext context, Action<TContext, TObject> execute, params Func<object, object>[] childSelectors)
{
ExecuteRecursive(item, context, (c, i) =>
{
if(i is TObject)
{
execute(c, (TObject)i);
}
}, childSelectors);
}
public static void ExecuteRecursive<TContext>(object item, TContext context, Action<TContext, object> execute, params Func<object, object>[] childSelectors)
{
execute(context, item);
if(item is IEnumerable)
{
foreach(var subItem in item as IEnumerable)
{
ExecuteRecursive(subItem, context, execute, childSelectors);
}
}
if(childSelectors != null)
{
foreach(var subItem in childSelectors.Select(x => x(item)).Where(x => x != null))
{
ExecuteRecursive(subItem, context, execute, childSelectors);
}
}
}
}
关于c# - 使用 XamlReader 和 XamlWriter 时将一个 FlowDocument 的内容插入另一个,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25662382/
我正在为我的按钮使用 onClick 功能。我的按钮代码如下所示: Button 1 Button 2 我的 JS 函数如下所示: function fadeNext(selectedId, spee
首先,我想提一下,我理解每个人在不提供至少一些试验或错误的情况下提出问题的感受,但这纯粹是一种知识需求,话虽如此,我会去提前问。 我一直无法弄清楚如何将保存在 MySQL 表中的 600-1000 个
我想做的事情有点令人困惑,而且我英语不太好,所以我先把代码贴在这里,这样你就可以很容易地理解: 以下是表单内容: Testing for Stackoverflow Option1
我学习 SDL 二维编程已有一段时间了,现在我想创建一个结合使用 SDL 和 OpenGL 的程序。我是这样设置的: SDL_Init(SDL_INIT_VIDEO); window = SDL_Cr
我创建了 2 个 data-* 标签。数据类别和数据标签。单击 href 标签后,我想复制该数据类别和数据标签以形成输入。我的代码是:
我想用 CSS 换行。我正在使用内容。 td:before { content: "Test\A Test2"; } 它不工作。如何正确
这个问题已经有答案了: Java Class that implements Map and keeps insertion order? (8 个回答) 已关闭 6 年前。 我有一个 HashMap
我正在尝试使用 JMeter 执行端到端测试。测试涉及写入SFTP文件夹并从另一个SFTP文件夹读取写入操作生成的文件。 我能够使用 JMeter SSH SFTP 插件连接到 SFTP 文件夹,并能
您好,我有带有标准服务器端 Servlet 的 GWT 客户端。 我可以从 GWT 客户端上传文件并在服务器端读取其内容 我可以将其作为字符串发送回客户端 但是 我有 GWT FormPanel与操作
我在 Plone 4.3.9 中创建了一个自定义类型的灵巧性,称为 PersonalPage,必须只允许在特定文件夹中使用 成员文件夹/用户文件夹 . 在他的 FTI 中,默认情况下 False .
在新(更新)版本的应用程序中更改小部件布局的最佳做法是什么?当新版本提供更新、更好的小部件时,如何处理现有小部件? 最佳答案 我认为您必须向用户显示一个弹出窗口,说明“此版本中的新功能”并要求他们重新
在我的应用程序中,我使用支持 View 寻呼机和 PagerTabStrip。进入查看寻呼机我有一些 fragment ,进入其中一个我正在使用支持卡片 View 。运行应用程序后,所有卡片 View
我有以下布局文件。基本上我有谷歌地图,在左上角我有一个 TextView,我需要在其中每 15 秒保持一次计数器以刷新 map 。布局很好。
我使用如下结构: HashMap > > OverallMap 如果我这样做: OverallMap . clear ( ) clear() 丢弃的所有内容(HashMap 对象、Integer 对
我在数据库中有 1000 张图像。在页面加载时,我随机显示 60 张图片,当用户滚动时,我通过 AJAX 请求添加 20 张图片。 第一种方法 我所做的是将所有图像加载到一个容器中,然后隐藏所有图像并
我正在使用 woocommerce 创建一个网上商店。 我想在每个产品上添加一个包含产品信息的表格,例如颜色、交货时间等等。 但是当我添加这张表时。本产品消失后的所有内容。 我的表的代码: td {
This question already has an answer here: What does an empty value for the CSS property content do?
因此,我正在与我的 friend 一起为 Google Chrome 开发一个扩展程序,对于大多数功能(即日历、设置等),我们打开一个模式,这样我们就不必重定向到另一个页面。当您在内容之外单击时,我们
我将可变高度的 CSS 框设置为在更大的 div 中向左浮动。现在我想添加一个标题,其中文本在框的左侧垂直显示(旋转 90 度),如下面的链接所示(抱歉还不能发布图片)。 http://imagesh
相关页面位于 www.codykrauskopf.com/circus 如果您查看我页面的右侧,在半透明容器和浏览器窗口边缘之间有一个间隙。我看了看,出于某种原因,wrap、main、content
我是一名优秀的程序员,十分优秀!