Indentation in WPF TextFormatter - wpf

I'm making a WPF text-editor using TextFormatter. I need to indent some paragraphs, so I'm using the Indent property from the TextParagraphProperties class.
This works great in this scenario:
Regular text without any
indentation.
This paragraph has a uniform
indentation so everything is
ok.
But I also need this:
John: This paragraph has a
diferent indentation
in the first line.
Joe: So I don't know how
to make this happen.
I found the ParagraphIndent and FirstLineInParagraph properties, but I don't know how they works, or if then would be usefull.
Thanks in advance!!
Jose

Jose --
Hopefully this code skeleton will help... I assume that you started from the Advanced Text Formatting project on MSDN, which, sadly is not very advanced. Here is a code fragment you can introduce to MainWindow.Xaml.cs:
TextParagraphProperties textParagraphProperties = new GenericTextParagraphProperties(TextAlignment.Left,
false);
TextRunCache textRunCache = new TextRunCache();
// By default, this is the first line of a paragrph
bool firstLine = true;
while (textStorePosition < _textStore.Length)
{
// Create a textline from the text store using the TextFormatter object.
TextLine myTextLine = formatter.FormatLine(
_textStore,
textStorePosition,
96 * 6,
// ProxyTextParagraphProperties will return a different value for the Indent property,
// depending on whether firstLine is true or not.
new ProxyTextParagraphProperties(textParagraphProperties, firstLine),
lastLineBreak,
textRunCache);
// Draw the formatted text into the drawing context.
Debug.WriteLine("linePosition:" + linePosition);
myTextLine.Draw(dc, linePosition, InvertAxes.None);
// Update the index position in the text store.
textStorePosition += myTextLine.Length;
// Update the line position coordinate for the displayed line.
linePosition.Y += myTextLine.Height;
// Figure out if the next line is the first line of a paragraph
var textRunSpans = myTextLine.GetTextRunSpans();
firstLine = (textRunSpans.Count > 0) && (textRunSpans[textRunSpans.Count - 1].Value is TextEndOfParagraph);
lastLineBreak = myTextLine.GetTextLineBreak();
}
You'll need to create the class ProxyTextParagraphProperties, which descends from TextParagraphProperties. Here's a snip of what I did to test this:
class ProxyTextParagraphProperties : TextParagraphProperties
{
private readonly TextParagraphProperties _paragraphProperties;
private readonly bool _firstLineInParagraph;
public ProxyTextParagraphProperties(TextParagraphProperties textParagraphProperties, bool firstLineInParagraph)
{
_paragraphProperties = textParagraphProperties;
_firstLineInParagraph = firstLineInParagraph;
}
public override FlowDirection FlowDirection
{
get { return _paragraphProperties.FlowDirection; }
}
// implement the same as above for all the other properties...
// But we need to handle this one specially...
public override double Indent
{
get
{
return _firstLineInParagraph ? _paragraphProperties.Indent : 0;
}
}
}

Related

Trying to reset the cursor position in a WPF Rich Text Box control

I have a WPF Rich Text Box control where I am replacing the text with new text of the same length and I simply want to get the original caret position before I replace the text, replace the text and then reset the caret position.
I think that the problem is that it uses a TextPointer that is tied to the RichTextBox and is reset when I clear the original text so that when reapply it the position has changed. What I actually want to do is to reset it to either the character position index in the original text or I am happy to deal with Lined and Columns.
I have scoured the internet for an answer to what should be a very simple problem and nothing seems to answer it. What I really want is an X/Y coordinate or anything that will help.
You can use GetOffsetToPosition() and GetPositionAtOffset() to save the relative position of the Caret and to restore it.
In other words, assuming that the RichTextBox initializes like this:
RichTextBox rtb;
int paragraphIndex = -1;
int indexInParagraph;
public MainWindow()
{
InitializeComponent();
rtb = new RichTextBox();
rtb.Document = new FlowDocument();
Paragraph para = new Paragraph(new Run("some text some text some text."));
rtb.Document.Blocks.Add(para);
// sets the caret at a specific (random) position in the paragraph:
rtb.CaretPosition = para.ContentStart.GetPositionAtOffset(5);
this.Content = rtb;
}
Note the three private fields in the class.
You should save the caret's paragraph index and caret index in the paragraph, before you replace the text:
public void SaveCaretState()
{
//enumerate and get the paragraph index
paragraphIndex = -1;
foreach (var p in rtb.Document.Blocks)
{
paragraphIndex++;
if (p == rtb.CaretPosition.Paragraph)
break;
}
//get index relative to the start of the paragraph:
indexInParagraph = rtb.CaretPosition.Paragraph.ElementStart.GetOffsetToPosition(rtb.CaretPosition);
}
and restore it whenever you liked:
public void RestoreCaretState(MouseEventArgs e)
{
// you might need to insure some conditions here (paragraph should exist and ...)
Paragraph para = rtb.Document.Blocks.ElementAt(paragraphIndex) as Paragraph;
rtb.CaretPosition = para.ElementStart.GetPositionAtOffset(indexInParagraph);
}
Please note that its a simple example and there might be other Blocks in RichTextBox.Document. However, the idea and implementation is not that much different.

GMAP.NET adding labels underneath markers

I have just started using gmap.net and I was looking for the functionality of adding labels under the markers. I see there's tooltips but I would like to have a constant label under my marker with a one word description.
I searched for docs or other answers but I cannot find anything which leads me to believe that it is not implemented. If someone can verify this I would appreciate it.
You need to create your own custom marker.
Based on the source of GMapMarker and the derived GMarkerGoogle I came up with this simplified example:
public class GmapMarkerWithLabel : GMapMarker, ISerializable
{
private Font font;
private GMarkerGoogle innerMarker;
public string Caption;
public GmapMarkerWithLabel(PointLatLng p, string caption, GMarkerGoogleType type)
: base(p)
{
font = new Font("Arial", 14);
innerMarker = new GMarkerGoogle(p, type);
Caption = caption;
}
public override void OnRender(Graphics g)
{
if (innerMarker != null)
{
innerMarker.OnRender(g);
}
g.DrawString(Caption, font, Brushes.Black, new PointF(0.0f, innerMarker.Size.Height));
}
public override void Dispose()
{
if(innerMarker != null)
{
innerMarker.Dispose();
innerMarker = null;
}
base.Dispose();
}
#region ISerializable Members
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
protected GmapMarkerWithLabel(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endregion
}
Usage (assuming a GMap instance named gm):
GMapOverlay markerOverlay = new GMapOverlay("markers");
gm.Overlays.Add(markerOverlay);
var labelMarker = new GmapMarkerWithLabel(new PointLatLng(53.3, 9), "caption text", GMarkerGoogleType.blue);
markerOverlay.Markers.Add(labelMarker)
I'll answer here because this is the first question that pops up when looking to display a text marker for the WPF GMAP.NET library. Displaying a text marker with the WPF version of the library is actually much easier than in WinForms, or at least than the accepted answer.
The GMapMarker in WPF has a Shape property of type UIElement, which means you can provide a System.Windows.Controls.TextBlock object to display a text marker :
Markers.Add(new GMapMarker(new PointLatLng(latitude, longitude))
{
Shape = new System.Windows.Controls.TextBlock(new System.Windows.Documents.Run("Label"))
});
Since the marker displays the top left portion of the shape at the given position, you can play with the GMapMarker.Offset property to adjust the text position according to its dimensions. For instance, if you want the text to be horizontally centered on the marker's position :
var textBlock = new TextBlock(new Run("Label"));
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
textBlock.Arrange(new Rect(textBlock.DesiredSize));
Markers.Add(new GMapMarker(new PointLatLng(request.Latitude, request.Longitude))
{
Offset = new Point(-textBlock.ActualWidth / 2, 0),
Shape = textBlock
});
The solution to get the TextBlock's dimensions was quickly taken from this question, so if you need a more accurate way of getting the block's dimensions to play with the offset I suggest you start from there.

How to highlight the word(s) with bold in text block for specified keyword using the attached property

The objective is to bold the word(s) of text in Textblock with matching input keyword.
For example: Stackoverflow is a very helpful, keep using Stackoverflow to sharpen your skills.
When the keyword is: Stackoverflow it should now display as
Stackoverflow is a very helpful, keep using Stackoverflow to sharpen your skills.
I tried to achieve the same objective using attached property. Below is the snap code of the same
public class HighLightKeyWord : DependencyObject
{
//This word is used to specify the word to highlight
public static readonly DependencyProperty BoldWordProperty = DependencyProperty.RegisterAttached("BoldWord", typeof(string), typeof(HighLightKeyWord),
new PropertyMetadata(string.Empty, OnBindingTextChanged));
public static string GetBoldWord(DependencyObject obj)
{
return (string)obj.GetValue(BoldWordProperty);
}
public static void SetBoldWord(DependencyObject obj, string value)
{
obj.SetValue(BoldWordProperty, value);
}
private static void OnBindingTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var _Key = e.NewValue as string;
var textblk = d as TextBlock;
string SourceText = textblk.Text;
SetBold(_Key, textblk, SourceText);
}
private static void SetBold(string key, TextBlock text, string SourceText)
{
text.Inlines.Clear();
var words = SourceText.Split(' ');
for (int i = 0; i < words.Length; i++)
{
var word = words[i];
var inline = new Run() { Text = word + ' ' };
if (String.Compare(word, key, StringComparison.CurrentCultureIgnoreCase) == 0)
{
inline.FontWeight = FontWeights.Bold;
}
text.Inlines.Add(inline);
}
}
}
// Bind object in Main
StackOverFlow stkovrflw = new StackOverFlow();
stkovrflw.Text = "Stackoverflow is a very helpful,keep using Stackoverflow to sharpen your skills.";
stkovrflw.KeyWord = "Stackoverflow";
this.DataContext = stkovrflw;
In Xaml I binded the value as
<TextBlock Text="{Binding Path=Text}" loc:HighLightKeyWord.BoldWord="{Binding Path=KeyWord}" />
Above code is working fine, however when I directly sets the HighlightText property in the xaml instead through data binding, Text property of the Text block is getting empty in the OnBindingTextChanged method and this method is called only once when dependency property is set .
I used this design based on Spellchecker concept so that other teamates can reuse my attached property in their projects.
Can anyone suggest how to fix the problem?
Whether you want to modify a TextBlock FontWeight attribute or another display property, I've written the following static method and found it very useful. (Note: The method illustrates highlighting text by modifying the Foreground property. The same principle can be used for any other TextBlock display attribute.)
static Brush DefaultHighlight = new SolidColorBrush(Colors.Red);
public static void SetTextAndHighlightSubstring(this TextBlock targetTextBlock, string sourceText, string subString, Brush highlight = null)
{
if (targetTextBlock == null || String.IsNullOrEmpty(sourceText) || String.IsNullOrEmpty(subString))
return;
targetTextBlock.Text = "";
var subStringPosition = sourceText.ToUpper().IndexOf(subString);
if (subStringPosition == -1)
{
targetTextBlock.Inlines.Add(new Run { Text = sourceText });
return;
}
var subStringLength = subString.Length;
var header = sourceText.Substring(0, subStringPosition);
subString = sourceText.Substring(subStringPosition, subStringLength);
var trailerLength = sourceText.Length - (subStringPosition + subStringLength);
var trailer = sourceText.Substring(subStringPosition + subStringLength, trailerLength);
targetTextBlock.Inlines.Add(new Run { Text = header });
targetTextBlock.Inlines.Add(new Run { Text = subString, Foreground = highlight ?? DefaultHighlight });
targetTextBlock.Inlines.Add(new Run { Text = trailer });
}
You can call this method using the TextBlock extension syntax like so:
TextBlockTitle.SetTextAndHighlightSubstring(_categoryItem.ItemText, _searchText);
In this example, the following display resulted:
I recently ran into the same problem with the broken text binding. I realize this is an old question, but figured I'd share my finding anyway.
Basically Text property's data-binding is severed the moment Inlines.Clear is called. The way I got around this problem was by adding an internal text dependency property that replicated text's binding so subsequent text changes would continue to trigger highlighting.
Try adding something like this before clearing text.Inlines.
protected static string GetInternalText(DependencyObject obj)
{
return (string)obj.GetValue(InternalTextProperty)
}
protected static void SetInternalText(DependencyObject obj, string value)
{
obj.SetValue(InternalTextProperty, value);
}
// Using a DependencyProperty as the backing store for InternalText. This enables animation, styling, binding, etc...
protected static readonly DependencyProperty InternalTextProperty =
DependencyProperty.RegisterAttached("InternalText", typeof(string),
typeof(HighlightableTextBlock), new PropertyMetadata(string.Empty, OnInternalTextChanged));
private static void SetBold(string key, TextBlock text, string SourceText)
{
//Add the following code to replicate text binding
if (textblock.GetBindingExpression(HighlightableTextBlock.InternalTextProperty) == null)
{
var textBinding = text.GetBindingExpression(TextBlock.TextProperty);
if (textBinding != null)
{
text.SetBinding(HighLightKeyWord.InternalTextProperty, textBinding.ParentBindingBase);
}
}
...
}
In InternalTextProperty's propertyChangeCallback you can have the same code as OnBindingTextChanged() or you could refactor them into another method.
For those who don't want to write their own code. I have created a lightweight nuget package that adds highlighting to regular TextBlock. You can leave most of your code intact and only have to add a few attached properties. By default the add-on supports highlighting, but you can also enable bold, italic and underline on the keyword.
https://www.nuget.org/packages/HighlightableTextBlock/
The source is here: https://github.com/kthsu/HighlightableTextBlock

WPF RichTextbox remove Foreground information from TextRange

sorry for my bad english... The default for a RichTextBox content is to inherit the Foreground color from the RichTextBox itself. That's nice, but if I set a specific Foreground color to some part of my text, that part does not inherit the Foreground anymore, obviously. How can I make my "colored" text inherit the Foreground again? I'm trying to do something like the "Automatic" color from Office Word but after I have set a specific color to a TextRange, I do not know how to unset it :/
TextRange.ClearAllProperties() does what I need, but also erases other properties like FontSize and FontFamily...
TextRange.ApplyPropertyValue(ForegroundProperty, DependencyProperty.UnsetValue) also does not do the trick...
You can also unset it by setting the property to null (this worked for me clearing out the background, for example removing highlighting)
TextRange.ApplyPropertyValue(TextElement.BackgroundProperty, null);
This seemed almost impossible to achieve since there is no "RemovePropertyValue" method. I also tried with span and got the same exception as you did so I made a method that collects all the Paragraphs within the TextRange and made a span for each separetly.. less than ideal, I know.. Anyway, it works for a small example but might be pretty hard to work with for something more complex.
private List<Span> m_spanList = new List<Span>();
private void c_setForegroundButton_Click(object sender, RoutedEventArgs e)
{
TextPointer textPointerStart = c_richTextBox1.Selection.Start;
TextPointer textPointerEnd = c_richTextBox1.Selection.End;
TextRange textRange = new TextRange(textPointerStart, textPointerEnd);
SetForeground(textRange);
}
private void c_clearForegroundButton_Click(object sender, RoutedEventArgs e)
{
foreach (Span span in m_spanList)
{
span.ClearValue(Span.ForegroundProperty);
}
}
public void SetForeground(TextRange textRange)
{
List<Paragraph> spannedParagraphs = new List<Paragraph>();
if (textRange.Start.Paragraph != null)
{
TextRange curRange = null;
Block cur = textRange.Start.Paragraph;
do
{
spannedParagraphs.Add(cur as Paragraph);
// Get next range
curRange = new TextRange(cur.ContentStart, cur.ContentEnd);
} while ((textRange.End.Paragraph == null || !curRange.Contains(textRange.End.Paragraph.ContentEnd)) && (cur = cur.NextBlock) != null);
}
if (spannedParagraphs.Count == 1)
{
Span span = new Span(c_richTextBox1.Selection.Start, c_richTextBox1.Selection.End);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else
{
for (int i = 0; i < spannedParagraphs.Count; i++)
{
if (i == spannedParagraphs.Count - 1)
{
Paragraph paragraph = spannedParagraphs[i];
// For some reason I get an exception here when I try this..
//m_span = new Span(paragraph.ElementStart, c_richTextBox1.Selection.End);
c_richTextBox1.Selection.Select(paragraph.ElementStart, c_richTextBox1.Selection.End);
Span span = new Span(c_richTextBox1.Selection.Start, c_richTextBox1.Selection.End);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else if (i == 0)
{
Paragraph paragraph = spannedParagraphs[i];
Span span = new Span(c_richTextBox1.Selection.Start, paragraph.ElementEnd);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else
{
Paragraph paragraph = spannedParagraphs[i];
Span span = new Span(paragraph.ElementStart, paragraph.ElementEnd);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
}
}
}
If you look at the code of method TextRange.ApplyPropertyValue in the .NET Reference Source, you'll see that in the end it calls DependencyObject.SetValue on a collection of Inlines and Blocks, and DependencyObject.SetValue treats the value DependencyProperty.UnsetValue specially by effectively clearing the local value for the property.
The problem is that they didn't think of that when implementing TextRange.ApplyPropertyValue: it checks the passed property value against the property type, and in case of a reference type, it makes sure the passed value is either null or inherits from the same class, thus preventing us from passing DependencyProperty.UnsetValue.
One solution I found to implement a way of clearing local values of a TextRange for dependency properties of a reference type is the following:
// We declare a marker brush to be detected later in the TextRange.
var markerBrush = new SolidColorBrush();
// First we ask the TextRange implementation to set our marker brush on its content.
// Using ApplyPropertyValue here takes care of splitting inlines when necessary to make
// sure that only the selected text gets affected.
range.ApplyPropertyValue(TextElement.ForegroundProperty, markerBrush);
// Now, we search the text range for every Inline that has our brush set as the foreground
// brush, and we clear the Foreground dependency property.
var position = range.Start;
while (position != null && range.Contains(position))
{
if (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
position.Parent is Inline inline &&
inline.ReadLocalValue(TextElement.ForegroundProperty) == _foregroundClearBrush)
inline.ClearValue(TextElement.ForegroundProperty);
position = position.GetNextContextPosition(LogicalDirection.Forward);
}

Remove images from RichTextBox FlowDocument

I have a WPF application on which the user can paste some data from Word inside a RichTextBox... but if that word data has an image, I need to remove it, how can I accomplish that?
Since the FlowDocument is xml, maybe doing some linq magic could do it, but I don't know how.
There is a tool called WordtoXAML Converter (http://wordtoxaml.codeplex.com). You can use that to convert your Word document contents to XAML, use regular expression matching to identify the images and then strip them out.
The following code will do what you want. While it can be a bit wasteful (it looks through the entire document instead of just the bit that was just pasted), it is the only way to do it, as sometimes the RichTextBox is inaccurate when it indicates the recently painted range:
public class MyTextBox : RichTextBox
{
public MyTextBox()
{
CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, Paste));
}
protected virtual void Paste(object sender, ExecutedRoutedEventArgs e)
{
Paste();
foreach (var image in FindImages())
{
if (image.SiblingInlines != null)
{
image.SiblingInlines.Remove(image);
}
}
}
IEnumerable<InlineUIContainer> FindImages()
{
var result = new List<InlineUIContainer>();
var blocks = Document.Blocks;
for (TextPointer position = blocks.FirstBlock.ElementStart; position != null && position.CompareTo(blocks.LastBlock.ElementEnd) != 1; position = position.GetNextContextPosition(LogicalDirection.Forward))
{
InlineUIContainer element = position.Parent as InlineUIContainer;
if (element != null && element.Child is Image)
{
result.Add(element);
}
}
return result;
}
}

Resources