我的任务是创建部分可编辑的RichTextBox
。我已经在Xaml中看到了为TextBlock
部分添加ReadOnly
元素的建议,但这会产生不良包装效果的不良视觉效果。 (它应该显示为一个连续文本块。)
我使用一些reverse string formatting修补了一个正在运行的原型来限制/允许编辑,并将其与动态创建inline Run
elements结合起来用于显示目的。使用字典存储文本的可编辑部分的当前值,我会根据任何Run
事件触发器相应地更新TextChanged
元素,并认为如果可编辑部分的文本被完全删除,它将会被替换回其默认值。
在字符串中:“Hi NAME,欢迎来到SPORT阵营。”,只有 NAME 和 SPORT 可以编辑。
╔═══════╦════════╗ ╔═══════╦════════╗
Default values: ║ Key ║ Value ║ Edited values: ║ Key ║ Value ║
╠═══════╬════════╣ ╠═══════╬════════╣
║ NAME ║ NAME ║ ║ NAME ║ John ║
║ SPORT ║ SPORT ║ ║ SPORT ║ Tennis ║
╚═══════╩════════╝ ╚═══════╩════════╝
"Hi NAME, welcome to SPORT camp." "Hi John, welcome to Tennis camp."
问题:
删除特定运行中的整个文本值会从RichTextBox Document
中删除该运行(以及以下运行)。即使我将它们全部添加回来,它们也不再在屏幕上正确显示。例如,使用上面设置中编辑过的字符串:
用户突出显示文本“John”并单击删除,而不是保存空值,应将其替换为默认文本 “NAME”。在内部发生这种情况。字典获取正确的值,Run.Text
具有值,Document
包含所有正确的Run
元素。但屏幕显示:
Sidenote :粘贴时也可以复制此运行元素丢失行为。突出显示“SPORT”并粘贴“网球”,并且包含“阵营的运行。”将丢失。
问题:
如果替换后,即使通过破坏性操作,如何保持每个Run
元素都可见?
代码:
我试图将代码删除到最小的例子,所以我删除了:
DependencyProperty
和关联的绑定要进行测试,请将该类放入WPF项目资源文件夹,修复命名空间,然后将控件添加到视图中。
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace WPFTest.Resources
{
public class MyRichTextBox : RichTextBox
{
public MyRichTextBox()
{
this.TextChanged += MyRichTextBox_TextChanged;
this.Background = Brushes.LightGray;
this.Parameters = new Dictionary<string, string>();
this.Parameters.Add("NAME", "NAME");
this.Parameters.Add("SPORT", "SPORT");
this.Format = "Hi {0}, welcome to {1} camp.";
this.Text = string.Format(this.Format, this.Parameters.Values.ToArray<string>());
this.Runs = new List<Run>()
{
new Run() { Background = Brushes.LightGray, Tag = "Hi " },
new Run() { Background = Brushes.Black, Foreground = Brushes.White, Tag = "NAME" },
new Run() { Background = Brushes.LightGray, Tag = ", welcome to " },
new Run() { Background = Brushes.Black, Foreground = Brushes.White, Tag = "SPORT" },
new Run() { Background = Brushes.LightGray, Tag = " camp." },
};
this.UpdateRuns();
}
public Dictionary<string, string> Parameters { get; set; }
public List<Run> Runs { get; set; }
public string Text { get; set; }
public string Format { get; set; }
private void MyRichTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
string richText = new TextRange(this.Document.Blocks.FirstBlock.ContentStart, this.Document.Blocks.FirstBlock.ContentEnd).Text;
string[] oldValues = this.Parameters.Values.ToArray<string>();
string[] newValues = null;
bool extracted = this.TryParseExact(richText, this.Format, out newValues);
if (extracted)
{
var changed = newValues.Select((x, i) => new { NewVal = x, Index = i }).Where(x => x.NewVal != oldValues[x.Index]).FirstOrDefault();
string key = this.Parameters.Keys.ElementAt(changed.Index);
this.Parameters[key] = string.IsNullOrWhiteSpace(newValues[changed.Index]) ? key : newValues[changed.Index];
this.Text = richText;
}
else
{
e.Handled = true;
}
this.UpdateRuns();
}
private void UpdateRuns()
{
this.TextChanged -= this.MyRichTextBox_TextChanged;
foreach (Run run in this.Runs)
{
string value = run.Tag.ToString();
if (this.Parameters.ContainsKey(value))
{
run.Text = this.Parameters[value];
}
else
{
run.Text = value;
}
}
Paragraph p = this.Document.Blocks.FirstBlock as Paragraph;
p.Inlines.Clear();
p.Inlines.AddRange(this.Runs);
this.TextChanged += this.MyRichTextBox_TextChanged;
}
public bool TryParseExact(string data, string format, out string[] values)
{
int tokenCount = 0;
format = Regex.Escape(format).Replace("\\{", "{");
format = string.Format("^{0}$", format);
while (true)
{
string token = string.Format("{{{0}}}", tokenCount);
if (!format.Contains(token))
{
break;
}
format = format.Replace(token, string.Format("(?'group{0}'.*)", tokenCount++));
}
RegexOptions options = RegexOptions.None;
Match match = new Regex(format, options).Match(data);
if (tokenCount != (match.Groups.Count - 1))
{
values = new string[] { };
return false;
}
else
{
values = new string[tokenCount];
for (int index = 0; index < tokenCount; index++)
{
values[index] = match.Groups[string.Format("group{0}", index)].Value;
}
return true;
}
}
}
}
答案 0 :(得分:2)
代码的问题在于,当您通过用户界面更改文本时,内部p.Inlines.Clear();
对象会被修改,创建,删除,并且所有疯狂的事情都会在幕后发生。内部结构非常复杂。例如,这是一个在无辜的单行private int DeleteContentFromSiblingTree(SplayTreeNode containingNode, TextPointer startPosition, TextPointer endPosition, bool newFirstIMEVisibleNode, out int charCount)
{
SplayTreeNode leftSubTree;
SplayTreeNode middleSubTree;
SplayTreeNode rightSubTree;
SplayTreeNode rootNode;
TextTreeNode previousNode;
ElementEdge previousEdge;
TextTreeNode nextNode;
ElementEdge nextEdge;
int symbolCount;
int symbolOffset;
// Early out in the no-op case. CutContent can't handle an empty content span.
if (startPosition.CompareTo(endPosition) == 0)
{
if (newFirstIMEVisibleNode)
{
UpdateContainerSymbolCount(containingNode, /* symbolCount */ 0, /* charCount */ -1);
}
charCount = 0;
return 0;
}
// Get the symbol offset now before the CutContent call invalidates startPosition.
symbolOffset = startPosition.GetSymbolOffset();
// Do the cut. middleSubTree is what we want to remove.
symbolCount = CutContent(startPosition, endPosition, out charCount, out leftSubTree, out middleSubTree, out rightSubTree);
// We need to remember the original previous/next node for the span
// we're about to drop, so any orphaned positions can find their way
// back.
if (middleSubTree != null)
{
if (leftSubTree != null)
{
previousNode = (TextTreeNode)leftSubTree.GetMaxSibling();
previousEdge = ElementEdge.AfterEnd;
}
else
{
previousNode = (TextTreeNode)containingNode;
previousEdge = ElementEdge.AfterStart;
}
if (rightSubTree != null)
{
nextNode = (TextTreeNode)rightSubTree.GetMinSibling();
nextEdge = ElementEdge.BeforeStart;
}
else
{
nextNode = (TextTreeNode)containingNode;
nextEdge = ElementEdge.BeforeEnd;
}
// Increment previous/nextNode reference counts. This may involve
// splitting a text node, so we use refs.
AdjustRefCountsForContentDelete(ref previousNode, previousEdge, ref nextNode, nextEdge, (TextTreeNode)middleSubTree);
// Make sure left/rightSubTree stay local roots, we might
// have inserted new elements in the AdjustRefCountsForContentDelete call.
if (leftSubTree != null)
{
leftSubTree.Splay();
}
if (rightSubTree != null)
{
rightSubTree.Splay();
}
// Similarly, middleSubtree might not be a local root any more,
// so splay it too.
middleSubTree.Splay();
// Note TextContainer now has no references to middleSubTree, if there are
// no orphaned positions this allocation won't be kept around.
Invariant.Assert(middleSubTree.ParentNode == null, "Assigning fixup node to parented child!");
middleSubTree.ParentNode = new TextTreeFixupNode(previousNode, previousEdge, nextNode, nextEdge);
}
// Put left/right sub trees back into the TextContainer.
rootNode = TextTreeNode.Join(leftSubTree, rightSubTree);
containingNode.ContainedNode = rootNode;
if (rootNode != null)
{
rootNode.ParentNode = containingNode;
}
if (symbolCount > 0)
{
int nextNodeCharDelta = 0;
if (newFirstIMEVisibleNode)
{
// The following node is the new first ime visible sibling.
// It just moved, and loses an edge character.
nextNodeCharDelta = -1;
}
UpdateContainerSymbolCount(containingNode, -symbolCount, -charCount + nextNodeCharDelta);
TextTreeText.RemoveText(_rootNode.RootTextBlock, symbolOffset, symbolCount);
NextGeneration(true /* deletedContent */);
// Notify the TextElement of a content change. Note that any full TextElements
// between startPosition and endPosition will be handled by CutTopLevelLogicalNodes,
// which will move them from this tree to their own private trees without changing
// their contents.
Invariant.Assert(startPosition.Parent == endPosition.Parent);
TextElement textElement = startPosition.Parent as TextElement;
if (textElement != null)
{
textElement.OnTextUpdated();
}
}
return symbolCount;
}
内深处调用的方法:
Run
如果您有兴趣,可以查看here的源代码。
解决方案是不要直接在FlowDocument
中使用您为比较目的创建的private void UpdateRuns()
{
TextChanged -= MyRichTextBox_TextChanged;
List<Run> runs = new List<Run>();
foreach (Run run in Runs)
{
Run newRun;
string value = run.Tag.ToString();
if (Parameters.ContainsKey(value))
{
newRun = new Run(Parameters[value]);
}
else
{
newRun = new Run(value);
}
newRun.Background = run.Background;
newRun.Foreground = run.Foreground;
runs.Add(newRun);
}
Paragraph p = Document.Blocks.FirstBlock as Paragraph;
p.Inlines.Clear();
p.Inlines.AddRange(runs);
TextChanged += MyRichTextBox_TextChanged;
}
个对象。在添加之前,请务必复制它:
class Base:
hello = 'A'
def greet(self):
print(self.hello)
def greet2(self):
print(Base.hello)
class Derived(Base):
hello = 'B'
d = Derived()
d.greet() # prints B
d.greet2() # prints A
答案 1 :(得分:1)
我建议移动代码以在UpdateRuns中创建运行
private void UpdateRuns()
{
this.TextChanged -= this.MyRichTextBox_TextChanged;
this.Runs = new List<Run>()
{
new Run() { Background = Brushes.LightGray, Tag = "Hi " },
new Run() { Background = Brushes.Black, Foreground = Brushes.White, Tag = "NAME" },
new Run() { Background = Brushes.LightGray, Tag = ", welcome to " },
new Run() { Background = Brushes.Black, Foreground = Brushes.White, Tag = "SPORT" },
new Run() { Background = Brushes.LightGray, Tag = " camp." },
};
foreach (Run run in this.Runs)