I am creating a control in WPF that shows units using a System.Windows.Control.RichTextBox.
The problem is the RichTextBox control shows a plain text instead of a formatted text.
I guess the RichTextBox control has a bug and I don't know how to do it, because it works depending on the computer.
The XAML code is,
<RichTextBox x:FieldModifier="private"
x:Name="TxtItem1"
IsReadOnly="True"
IsHitTestVisible="False"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center" />
And part of the code behind is:
private static void UpdateDocument(RichTextBox control, DependencyPropertyChangedEventArgs e)
{
string content = e.NewValue as string;
control.Document = content.Html1ToFlowDocument();
}
The function Html1ToFlowDocument converts a string into a FlowDocument. The following image is in a computer that the code goes fine (Windows 7 64 bits):
And the next one does not work (Windows 7 64 bits):
Another approach was using a RTF text but I have the problem.
The code of the function Html1ToFlowDocument,
public static class Html1ToDocument
{
public static FlowDocument Html1ToFlowDocument(this string text)
{
var mcFlowDoc = new FlowDocument();
XmlDocument doc = new XmlDocument();
doc.LoadXml(string.Format("<P>{0}</P>", text));
XmlElement root = doc.GetElementsByTagName("P")[0] as XmlElement;
IEnumerable<Inline> children;
try
{
children = ParseChildren(root);
}
catch (Exception ex)
{
throw new FormatException("Unsupported text.", ex);
}
var paragraph = new Paragraph();
paragraph.Inlines.AddRange(children);
mcFlowDoc.Blocks.Add(paragraph);
return mcFlowDoc;
}
private static IEnumerable<Inline> ParseChildren(XmlElement root)
{
Span sitem;
List<Inline> children;
foreach (XmlNode element in root.ChildNodes)
{
Inline item = null;
if (element is XmlElement)
{
XmlElement xelement = (XmlElement)element;
switch (xelement.Name.ToUpper())
{
case "SUB":
children = ParseChildren(xelement).ToList();
if (children.Count == 1 && children.First() is Run)
{
item = children.First();
item.Typography.Variants = FontVariants.Subscript;
}
else
{
sitem = new Span();
sitem.Typography.Variants = FontVariants.Subscript;
sitem.Inlines.AddRange(children);
item = sitem;
}
break;
case "SUPER":
children = ParseChildren(xelement).ToList();
if (children.Count == 1 && children.First() is Run)
{
item = children.First();
item.Typography.Variants = FontVariants.Superscript;
}
else
{
sitem = new Span();
sitem.Typography.Variants = FontVariants.Superscript;
sitem.Inlines.AddRange(children);
item = sitem;
}
break;
}
yield return item;
}
else if (element is XmlText)
{
item = new Run(element.InnerText);
yield return item;
}
}
}
}
If most things on the surface between the two computers seem the same (i.e. OS version, locale, framework installed) then I'd wager it's an issue with fonts. You're essentially getting the same text in both cases, but you're hitting a wall with superscripts and subscripts. A quick little googling showed me that other people are having similar problems:
http://www.grumpydev.com/2009/11/05/wpf-richtextbox-subscript-and-superscript-without-font-restrictions/
superscripts are not coming in wpf richtext box
Looks like the font in question needs to be:
Open Type
Have both superscript and subscript variants
Here's some information on checking for fonts:
Detect whether a font supports variants (like superscript and subscript)
My recommendation might be to try out a different font to see if it fixes it, and if so, find out how you can ensure you'll get the fonts you need on both (meaning, all that you intend to deploy to) machines.
I have used a tricky solution. The characters of the controls in WPF are based on the Unicode Standard 6.3 and there is a table in this standard with special characters, specifically subscript and superscript. And the most amazing is that it works in a simple TextBox.
⁰ i ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ⁿ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₊ ₋ ₌ ₍ ₎ ₐ ₑ ₒ ₓ ₔ
You can download the specification here:
http://www.unicode.org/charts/PDF/U2070.pdf
And there are more special characters here: http://www.unicode.org/charts/
Related
So I have a WinForms application running on .NET 7.0 and I need to get a selection from a focused control (and this should work in any application that supports text selection) and replace it with specific text. The problem is that I can replace all the text in the field, but I can't replace just the part of the text that I need.
private void GetSelectedText()
{
try
{
var element = AutomationElement.FocusedElement;
if (element != null)
{
object pattern;
object valuePattern;
// Here I can get the selected text from almost any application.
if (element.TryGetCurrentPattern(TextPattern.Pattern, out pattern))
{
var tp = (TextPattern)pattern;
var sb = new StringBuilder();
foreach (var r in tp.GetSelection())
{
sb.AppendLine(r.GetText(-1));
}
// Debug info:
MessageBox.Show(sb.ToString());
}
// And this code sets value of the focused control to "aaaaaaa".
if (element.TryGetCurrentPattern(ValuePattern.Pattern, out valuePattern))
{
((ValuePattern)valuePattern).SetValue("aaaaaaa");
}
}
}
catch (Exception ex)
{
// Debug info:
Console.WriteLine(ex.Message, ex.StackTrace);
}
}
I've also tried WinAPI calls for copying and pasting the value but I couldn't figure out how to replace the text without using the clipboard or writing a huge amount of code.
I would be grateful if you could help me with this.
Text pattern doesn't provide any method to modify the text, and the value pattern also allows just setting the whole value. What you can do is modifying the selection through direct keyboard input. This is what I tried and works as expected:
var element = AutomationElement.FocusedElement;
if (element != null)
{
if (element.TryGetCurrentPattern(TextPattern.Pattern, out object pattern))
{
var tp = (TextPattern)pattern;
var selection = tp.GetSelection().FirstOrDefault();
if(selection != null)
{
SendKeys.SendWait("XXXXXX");
}
}
}
Fore more information, take a look at TextPattern overview
The TextPattern classes do not provide a means to insert or modify
text. However, depending on the control, this may be accomplished by
the UI Automation ValuePattern or through direct keyboard input. See
the TextPattern Insert Text Sample for an example.
I've seen some questions address this problem domain and the unnecessary complexity of handling underlines (mainly applying them, but I want to detect them), but none that I can recall suggesting as I am here that the default strategies for accomplishing this create illogical false negatives. Furthermore, most of the previous questions I've referred to have used a different control (e.g. TextBlock) and/or have obselete syntax.
The problem
(.NET Core 3.1) I would simply like to programatically detect if a WPF RichTextBox selection contains any TextDecorations, but debugging shows that the TextDecorationCollection is always empty, even when the selection is all underlined.
As you can see, TextDecorationCollection returns empty even when examining a fully underlined Inline (Run)
For context, this screenshot just shows the plain text representation of the FlowDocument
What I've tried
1
TextRange myrange = new TextRange(MainRtb.Selection.Start, MainRtb.Selection.End);
if (myrange.GetPropertyValue(Inline.TextDecorationsProperty).Equals(TextDecorations.Underline)) { }
2
TextRange myrange = new TextRange(MainRtb.Selection.Start, MainRtb.Selection.End);
var obj = myrange.GetPropertyValue(Inline.TextDecorationsProperty);
if (obj == DependencyProperty.UnsetValue) {
log.addLog("mix format");
}
if (obj is TextDecorationCollection) {
var objProper = obj as TextDecorationCollection;
if (objProper.Count > 0) {
log.addLog("all underlined");
} else {
log.addLog("none underlined");
}
}
3
foreach (Block block in MainRtb.Document.Blocks) {
Paragraph p = block as Paragraph;
if (p != null) {
foreach (Inline inline in p.Inlines) {
InlineUIContainer iuic = inline as InlineUIContainer;
if (iuic != null) {
Console.WriteLine("found underline");
}
}
}
}
Theory
This post https://social.msdn.microsoft.com/Forums/vstudio/en-US/3ac626cf-60aa-427f-80e9-794f3775a70e/how-to-tell-if-richtextbox-selection-is-underlined?forum=wpf suggests that
myrange.GetPropertyValue(Inline.TextDecorationsProperty)
doesn't work properly due to an issue inside the "GetPropertyValue()" method, but it's a very old post. I couldn't run Jim's solution exactly because he initialises an "IEnumerable" which now needs to be declared with a type of some kind - at least that's what VS2019 said.
Test Rtf File:
https://docs.google.com/document/d/1YQmGsPcH4hX2XsP7KBdFqTFg4XjrSv8I/edit?usp=sharing&ouid=111968029811979231347&rtpof=true&sd=true
Try the following method:
public static void GetDecorations(RichTextBox rtb)
{
TextDecorationCollection decors = rtb.Selection.GetPropertyValue(Inline.TextDecorationsProperty) as TextDecorationCollection;
if (decors == null || decors.Count == 0)
{
if (rtb.Selection.Start.Parent is Run run)
{
if (run.Parent is Span span)
{
decors = span.TextDecorations;
}
else if (run.Parent is Paragraph para)
{
decors = para.TextDecorations;
}
}
}
if (decors is TextDecorationCollection tdc)
{
// TODO: Processing decorations...
}
}
I suppose the problem you are discovered is related to the particular structure of the FlowDocument after loading your RTF document and it might be described as follow.
When the RTF document is loaded for the underlined text tBox. See TextPointer for more information on text position terminology like "insertion position" a Run inline is created for this text, but the Run.TextDecorations property doesn't contain the actual decorations for this text. Instead of that the decorations settings are stored in the parent Span object that contains this Run. In another words, these decorations property is inherited from parent to child.
Therefore, if no decorations property is set on the current Run object, then you should to check the TextDecorations property in the parent object.
Is it possible to highlight a part of a text without selecting this part of the text preferably with a different color in Textbox or Rich TextBox? In fact, I mean, a part of the text is highlighted by another color differing from the color assigned for text selection. To clarify, I have attached an image showing this behavior. (The image is from a website, not WPF).
The bold and dark green part is a text which is just highlighted, and the gray region is a selected part.
Using the RichTextBox element allows for more styling options which, to my knowledge, aren't available for the regular TextBox element.
Here is an approach that I have created:
// Generate example content
FlowDocument doc = new FlowDocument();
Run runStart = new Run("This is an example of ");
Run runHighlight = new Run("text highlighting in WPF");
Run runEnd = new Run(" using the RichTextBox element.");
// Apply highlight style
runHighlight.FontWeight = FontWeights.Bold;
runHighlight.Background = Brushes.LightGreen;
// Create paragraph
Paragraph paragraph = new Paragraph();
paragraph.Inlines.Add(runStart);
paragraph.Inlines.Add(runHighlight);
paragraph.Inlines.Add(runEnd);
// Add the paragraph to the FlowDocument
doc.Blocks.Add(paragraph);
// Apply to RichTextBox
YourRichTextBoxHere.Document = doc;
View Screenshot
I found this article to be helpful.
Highlight Searched Text in WPF ListView
While the article is about highlighting searched text in a ListView, I have easily adapted it in my own code to work with pretty much any control.
Starting with the control you pass in, it will recursively look for TextBlocks and will find the text you want, extract it as an inline, and will change it's Background / Foreground properties.
You can easily adapt the code to be a behavior if your want.
Here is an example:
private void HighlightText(object controlToHighlight, string textToHighlight)
{
if (controlToHighlight == null) return;
if (controlToHighlight is TextBlock tb)
{
var regex = new Regex("(" + textToHighlight + ")", RegexOptions.IgnoreCase);
if (textToHighlight.Length == 0)
{
var str = tb.Text;
tb.Inlines.Clear();
tb.Inlines.Add(str);
return;
}
var substrings = regex.Split(tb.Text);
tb.Inlines.Clear();
foreach (var item in substrings)
{
if (regex.Match(item).Success)
{
var run = new Run(item)
{
Background = (SolidColorBrush) new BrushConverter().ConvertFrom("#FFFFF45E")
};
tb.Inlines.Add(run);
}
else
{
tb.Inlines.Add(item);
}
}
}
else
{
if (!(controlToHighlight is DependencyObject dependencyObject)) return;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
HighlightText(VisualTreeHelper.GetChild(dependencyObject, i), textToHighlight);
}
}
}
I hope this is helpful!
This question already has answers here:
WPF - Making hyperlinks clickable
(5 answers)
Closed 8 years ago.
I have a parser XamlReader.Parse(xamlFile) and I need to parse the Hyperlink in it.
I have a TextBlock (it supports the Hyperlink) but no idea on how to make the word I want clickable.
You could play with the VisualTreeHelper to replace all the matching text with Hyperlinks.
Here is a sample:
void CreateLinks(FrameworkElement fe)
{
Uri URI = new Uri("http://google.com");
TextBlock tb = fe as TextBlock;
if (tb != null)
{
string[] tokens = tb.Text.Split();
tb.Inlines.Clear();
foreach (string token in tokens)
{
if (token == "Click")
{
Hyperlink link = new Hyperlink { NavigateUri = URI };
link.Inlines.Add("Click");
tb.Inlines.Add(link);
}
else
{
tb.Inlines.Add(token);
}
tb.Inlines.Add(" ");
}
tb.Inlines.Remove(tb.Inlines.Last());
}
else
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(fe); ++i)
{
CreateLinks(VisualTreeHelper.GetChild(fe, i) as FrameworkElement);
}
}
}
public MainWindow()
{
InitializeComponent();
Loaded += (s, a) =>
{
FrameworkElement root = XamlReader.Parse("<Grid xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"><TextBlock>Click Click Clock Clack Click</TextBlock></Grid>") as FrameworkElement;
CreateLinks(root);
grid.Children.Add(root);
};
}
Of course you may want to be more subtle for ensuring you keep the exact formatting of your text; with my quick and dirty implementation I'll lose consecutive spaces, and I don't handle the case where the pattern contains spaces.
So you can enhance it by using regex, but as far as WPF is concernend I think you have all the elements to make your own implementation.
Enjoy!
How can i focus a Inline in a RichTextBox?
I Create a FlowDocument from a Text-File and load it in my richTextBox1
and mark one Inline after an other accordingly to a Button_click (be recreating the FlowDocument)
with this code:
richTextBox1.SelectAll();
richTextBox1.Selection.Text = "";
string text = System.IO.File.ReadAllText(file);
int iZeile = 0;
string[] split = text.Split(new string[] {"\r\n"},StringSplitOptions.None);
foreach (string s in split)
{
if (iZeile != 27)
{
paragraph.Inlines.Add(s + "\r\n"); // adds line added without marking
}
else
{
Run run = new Run(split[27]); // adds line with marking
run.Background = Brushes.Yellow;
paragraph.Inlines.Add(run);
paragraph.Inlines.Add("\r\n");
}
iZeile++;
}
FlowDocument document = new FlowDocument(paragraph);
richTextBox1.Document = new FlowDocument();
richTextBox1.Document = document;
Keyboard.Focus(richTextBox1);
}
I know its not.. perfect.
the Issue
It works so far but the problem that occurs is me Market Inline doesn't comes intoView. Is there a easy way to bring this Inline intoView?
The straightforward solution seemed to be FrameworkContentElement.BringIntoView() but after putting it in the code below it initially had no effect. As it turns out this is one of these timing issues (I've seen similar problems in WinForms) that can be solved by processing the outstanding Windows Messages. WPF has no direct equivalent of DoEvents() but there exists a well known substitute.
I placed this in a ButtonClick, changes marked with //**:
Paragraph paragraph = new Paragraph();
Inline selected = null; //**
richTextBox1.SelectAll();
richTextBox1.Selection.Text = "";
string text = System.IO.File.ReadAllText(#"..\..\MainWindow.xaml.cs");
int iZeile = 0;
string[] split = text.Split(new string[] { "\r\n" }, StringSplitOptions.None);
foreach (string s in split)
{
if (iZeile != 27)
{
paragraph.Inlines.Add(s + "\r\n"); // adds line added without marking
}
else
{
Run run = new Run(split[27]); // adds line with marking
run.Background = Brushes.Yellow;
paragraph.Inlines.Add(run);
paragraph.Inlines.Add("\r\n");
selected = run; // ** remember this element
}
iZeile++;
}
FlowDocument document = new FlowDocument(paragraph);
richTextBox1.Document = new FlowDocument();
richTextBox1.Document = document;
Keyboard.Focus(richTextBox1);
DoEvents(); // ** this is required, probably a bug
selected.BringIntoView(); // **
And the helper method, from here:
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Background,
new Action(delegate { }));
}
you should try one of this methods
richTextBox.SelectionStart = richTextBox.Text.Length;
richTextBox.ScrollToCaret();
.
richTextBox.AppendText(text);
richTextBox.ScrollToEnd();
futher informations are here and here
Edit
ok after a bit of digging in the WPF RichTextBox i thing you cloud try richTextBox.ScrollToVerticalOffset(Offset)
to get the Offset maybe you could use this answer
EDIT 2
ok after some more research i found following Link where you can download this working example