I've got a panel for text formatting and I've got to get sub- and superscript work but it can't be done by inserting \sub or \super into RTF (RTF is what I need in the end).
This is how I would set superscript by inserting \sub:
public void SetSuperscript()
{
ITextSelection selectedText = _textbox.Document.Selection;
if (selectedText != null)
{
ITextCharacterFormat charFormatting = selectedText.CharacterFormat;
charFormatting.Superscript= FormatEffect.On;
selectedText.CharacterFormat = charFormatting;
}
}
Now I know I can do it like that as well:
public void SetSuperscript()
{
ITextSelection selectedText = _textbox.Document.Selection;
if (selectedText != null)
{
ITextCharacterFormat charFormatting = selectedText.CharacterFormat;
charFormatting.Size /= 2;
charFormatting.Position = charFormatting.Size;
selectedText.CharacterFormat = charFormatting;
}
}
and it works just as fine. The problem is when it comes to creating a subscript:
public void SetSubscript()
{
ITextSelection selectedText = _textbox.Document.Selection;
if (selectedText != null)
{
ITextCharacterFormat charFormatting = selectedText.CharacterFormat;
charFormatting.Size /= 2;
charFormatting.Position = -charFormatting.Size;
selectedText.CharacterFormat = charFormatting;
}
}
This code above throws an exception "Value does not fall within the expected range." and it's caused by this line:
charFormatting.Position = -charFormatting.Size;
I'm assigning there a negative value (which is allright according to the documentation) because I need the selected text to be lower than normal text by half of its height. What am I doing wrong?
Daniel
Related
I'm currently using the following code as a LineTransformer with an AvalonEdit TextEditor. I want to be able to highlight the current single search result with a selection, however the selection is barely visible because the formatting of the DocumentColorizingTransformer has precedence over showing highlighted text. How do I get the highlighted selection to show instead of or before the formatting?
public class ColorizeSearchResults : DocumentColorizingTransformer {
public ColorizeSearchResults() : base() {
SearchTerm = "";
MatchCase = false;
}
public string SearchTerm { get; set; }
public bool MatchCase { get; set; }
protected override void ColorizeLine(DocumentLine line) {
if (SearchTerm.Length == 0)
return;
int lineStartOffset = line.Offset;
string text = CurrentContext.Document.GetText(line);
int count = 0;
int start = 0;
int index;
while ((index = text.IndexOf(SearchTerm, start, MatchCase ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase)) >= 0) {
base.ChangeLinePart(
lineStartOffset + index,
lineStartOffset + index + SearchTerm.Length,
(VisualLineElement element) => {
element.TextRunProperties.SetForegroundBrush(Brushes.White);
element.TextRunProperties.SetBackgroundBrush(Brushes.Magenta);
});
start = index + 1;
count++;
}
}
}
Example of formatting showing over selection
See http://avalonedit.net/documentation/html/c06e9832-9ef0-4d65-ac2e-11f7ce9c7774.htm for AvalonEdit render flow.
The selection layer is rendered before text. So if the text has background, it overrides the selection background. Fortunately we can set the background to Brush.Transparent (or a mix of the Selection.Brush and your own color).
Solution: I've modified the SelectionColorizer code to reset selection background to transparent:
class SelectionColorizerWithBackground : ColorizingTransformer
{
ICSharpCode.AvalonEdit.Editing.TextArea _textArea;
public SelectionColorizerWithBackground(
ICSharpCode.AvalonEdit.Editing.TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
this._textArea = textArea;
}
protected override void Colorize(ITextRunConstructionContext context)
{
int lineStartOffset = context.VisualLine.FirstDocumentLine.Offset;
int lineEndOffset = context.VisualLine.LastDocumentLine.Offset +
context.VisualLine.LastDocumentLine.TotalLength;
foreach (var segment in _textArea.Selection.Segments)
{
int segmentStart = segment.StartOffset;
if (segmentStart >= lineEndOffset)
continue;
int segmentEnd = segment.EndOffset;
if (segmentEnd <= lineStartOffset)
continue;
int startColumn;
if (segmentStart < lineStartOffset)
startColumn = 0;
else
startColumn = context.VisualLine.ValidateVisualColumn(
segment.StartOffset, segment.StartVisualColumn,
_textArea.Selection.EnableVirtualSpace);
int endColumn;
if (segmentEnd > lineEndOffset)
endColumn =
_textArea.Selection.EnableVirtualSpace
? int.MaxValue
: context.VisualLine
.VisualLengthWithEndOfLineMarker;
else
endColumn = context.VisualLine.ValidateVisualColumn(
segment.EndOffset, segment.EndVisualColumn,
_textArea.Selection.EnableVirtualSpace);
ChangeVisualElements(
startColumn, endColumn,
element => {
element.TextRunProperties.SetBackgroundBrush(
System.Windows.Media.Brushes.Transparent);
if (_textArea.SelectionForeground != null)
{
element.TextRunProperties.SetForegroundBrush(
_textArea.SelectionForeground);
}
});
}
}
}
To use the code you are supposed to do the following:
var lineTransformers = textEditor.TextArea.TextView.LineTransformers;
// Remove the original SelectionColorizer.
// Note: if you have syntax highlighting you need to do something else
// to avoid clearing other colorizers. If too complicated you can skip
// this step but to suffer a 2x performance penalty.
lineTransformers.Clear();
lineTransformers.Add(new ColorizeSearchResults());
lineTransformers.Add(
new SelectionColorizerWithBackground(textEditor.TextArea));
After I've tried my solutions extensively, I'd like to add a few points:
While my other solution above appears to work, you'll have some subpixel artefacts when the rectangles are supposed to be tiled. If that is unacceptable you can implement an IBackgroundRenderer. (That happens to be my chosen solution.) If you want some code you may request here, but I doubt whether it will be useful.
BTW, since your question is about search result, most likely you can use https://github.com/icsharpcode/AvalonEdit/blob/697ff0d38c95c9e5a536fbc05ae2307ec9ef2a63/ICSharpCode.AvalonEdit/Search/SearchResultBackgroundRenderer.cs unmodified (or modify it if you don't want the rounded borders).
You may use element.BackgroundBrush = Brushes.Magenta; instead of element.TextRunProperties.SetBackgroundBrush(Brushes.Magenta);. AvalonEdit appears to draw the background with a rectangle with 3px radius.
There is also a RichTextColorizer starting from AvalonEdit 5.01. I don't know how to use it though because it is not referenced in other files. And the (most likely unwanted) rounded rectangles in the previous paragraph are likely to be present.
So here's my final product based almost entirely off of the existing AvalonEdit SearchResultBackgroundRenderer.
This works a little different than my post's colorizer as you have to modify the search results manually instead of it doing it for you. But that may also save some computation time.
If your search doesn't use Regex, then you can easily modify SearchResult to instead just pass in a start offset and length for the constructor.
/// <summary>A search result storing a match and text segment.</summary>
public class SearchResult : TextSegment {
/// <summary>The regex match for the search result.</summary>
public Match Match { get; }
/// <summary>Constructs the search result from the match.</summary>
public SearchResult(Match match) {
this.StartOffset = match.Index;
this.Length = match.Length;
this.Match = match;
}
}
/// <summary>Colorizes search results behind the selection.</summary>
public class ColorizeSearchResultsBackgroundRenderer : IBackgroundRenderer {
/// <summary>The search results to be modified.</summary>
TextSegmentCollection<SearchResult> currentResults = new TextSegmentCollection<SearchResult>();
/// <summary>Constructs the search result colorizer.</summary>
public ColorizeSearchResultsBackgroundRenderer() {
Background = new SolidColorBrush(Color.FromRgb(246, 185, 77));
Background.Freeze();
}
/// <summary>Gets the layer on which this background renderer should draw.</summary>
public KnownLayer Layer {
get {
// draw behind selection
return KnownLayer.Selection;
}
}
/// <summary>Causes the background renderer to draw.</summary>
public void Draw(TextView textView, DrawingContext drawingContext) {
if (textView == null)
throw new ArgumentNullException("textView");
if (drawingContext == null)
throw new ArgumentNullException("drawingContext");
if (currentResults == null || !textView.VisualLinesValid)
return;
var visualLines = textView.VisualLines;
if (visualLines.Count == 0)
return;
int viewStart = visualLines.First().FirstDocumentLine.Offset;
int viewEnd = visualLines.Last().LastDocumentLine.EndOffset;
foreach (SearchResult result in currentResults.FindOverlappingSegments(viewStart, viewEnd - viewStart)) {
BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
geoBuilder.AlignToWholePixels = true;
geoBuilder.BorderThickness = 0;
geoBuilder.CornerRadius = 0;
geoBuilder.AddSegment(textView, result);
Geometry geometry = geoBuilder.CreateGeometry();
if (geometry != null) {
drawingContext.DrawGeometry(Background, null, geometry);
}
}
}
/// <summary>Gets the search results for modification.</summary>
public TextSegmentCollection<SearchResult> CurrentResults {
get { return currentResults; }
}
/// <summary>Gets or sets the background brush for the search results.</summary>
public Brush Background { get; set; }
}
In order to make use of the background renderer:
var searchColorizor = new ColorizeSearchResultsBackgroundRenderer();
textEditor.TextArea.TextView.BackgroundRenderers.Add(searchColorizor);
It is needed to highlight by fiven colour a substring in the document RichEditBox. For this purpose I wrote a method:
private async Task ChangeTextColor(string text, Color color)
{
string textStr;
bool theEnd = false;
int startTextPos = 0;
myRichEdit.Document.GetText(TextGetOptions.None, out textStr);
while (theEnd == false)
{
myRichEdit.Document.GetRange(startTextPos, textStr.Length).GetText(TextGetOptions.None, out textStr);
var isFinded = myRichEdit.Document.GetRange(startTextPos, textStr.Length).FindText(text, textStr.Length, FindOptions.None);
if (isFinded != 0)
{
string textStr2;
textStr2 = myRichEdit.Document.Selection.Text;
var dialog = new MessageDialog(textStr2);
await dialog.ShowAsync();
myRichEdit.Document.Selection.CharacterFormat.BackgroundColor = color;
startTextPos = myRichEdit.Document.Selection.EndPosition;
myRichEdit.Document.ApplyDisplayUpdates();
}
else
{
theEnd = true;
}
}
}
In the debugger you can see that there is a substring and isFinded is equal with the number of signs (or symbols) in the found substring. It means the fragment is found and judging by the description of the method FindText should be highlighted but it isn't. In textStr2 an empty line returns and, correspondingly, the colour doesn't change. I cannot identify the reasons of the error.
The code you postted did not set the selection, so the myRichEdit.Document.Selection is null. You can use ITextRange.SetRange to set the selection. And you can use ITextRange.FindText method to find the string in the selection.
For example:
private void ChangeTextColor(string text, Color color)
{
string textStr;
myRichEdit.Document.GetText(TextGetOptions.None, out textStr);
var myRichEditLength = textStr.Length;
myRichEdit.Document.Selection.SetRange(0, myRichEditLength);
int i = 1;
while (i > 0)
{
i = myRichEdit.Document.Selection.FindText(text, myRichEditLength, FindOptions.Case);
ITextSelection selectedText = myRichEdit.Document.Selection;
if (selectedText != null)
{
selectedText.CharacterFormat.BackgroundColor = color;
}
}
}
Good day!
Sorry if I cant make it into code but here's my problem:
Im coding C#, using TextBox Control, Multiline = true.
Now, my problem is i need to know the user's input (per line) whether it already reaches maximum length PER LINE defined. If yes, it shouldn't be printed in the textbox (like MaxLength does).
Example of the input (Note: maximum length per line is 5):
Dog //3 characters
Cat //3 characters
Telephone //9 characters
Telephone shouldn't be printed coz it exceeded max length per line which is 5. It should be "Telep" only.
EDITED:
Acctually, I found my way to solve this. it was as simple as this!!!!
if (txtText.Lines[txtText.GetLineFromCharIndex(txtText.SelectionStart)].Length > Convert.ToInt32(txtMaxlen.Text) - 1 && e.KeyChar!=8)
{
e.Handled = true;
}
Just put that code in "OnKeyPress" event then VIOLA!
You can handle OnTextChanged event and then use Lines property to get array of lines. Then simply check whenever any line has more characters than you allow and if so - correct it and set as a Lines again. You have to also prevent event from rising when you do such correction, use the flag or unsubscribe/subscribe technique.
You can extract them from the lines of the Textbox like:
var myarr = myTextBox.Lines.Select(n => n.Remove(3)).ToArray();
Btw, you're most likely better off with a DataGridView Control.
textBox.OnTextChanged += (EventArgs e) => // invoked on every text change
{
var lines = myTextBox.Where(x.Length > 5); // Get the long lines
for (var i; i < lines.Count(); i++) // iterate over long lines
{
lines[i] = lines[i].Substring(0, 5); // replace the line with its substring(stripped) value
}
}
How about this, this should behave like the MaxLength property and handle the addition of the new char before it occurs, not after. When a key is pressed it checks the length of the line the cursor is currently on.
public class MaxPerLineTextBox : TextBox
{
public MaxPerLineTextBox()
{
base.Multiline = true;
}
public override bool Multiline
{
get
{
return true;
}
set
{
throw new InvalidOperationException("Readonly subclass");
}
}
public int? MaxPerLine { get; set; }
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (char.IsControl(e.KeyChar))
{
base.OnKeyPress(e);
return;
}
int maxPerLine;
if (this.MaxPerLine.HasValue)
{
maxPerLine = this.MaxPerLine.Value;
}
else
{
base.OnKeyPress(e);
return;
}
var count = 0;
var lineLength = this.Lines.Select(line =>
{
count += line.Length;
return new { line.Length, count };
})
.Last(c => c.count < this.SelectionStart).Length;
if (lineLength < maxPerLine)
{
base.OnKeyPress(e);
return;
}
e.Handled = true;
}
}
I've got a gantt chart (RangeBar) I've made with the MS Chart control; for some shorter series, the label gets displayed outside the bar; I'd prefer to set it so the label gets stays inside the bar and gets truncated (with ellipsis would be nice). Is there a way of doing this? I've been poking around in the properties of the chart and series for ages now with no success.
I think the property you need to set is BarLabelStyle
eg.
chart.Series["mySeries"]["BarLabelStyle"] = "Center";
See this Dundas page that explains that custom property which should be similar or same for the MS Chart control.
In the end I rolled my own using this (yes it's messy, it'll get tidied up when I have time):
private static void Chart_PostPaint(object sender, ChartPaintEventArgs e)
{
Chart c = ((Chart)sender);
foreach (Series s in c.Series)
{
string sVt = s.GetCustomProperty("PixelPointWidth");
IGanttable ig = (IGanttable)s.Tag;
double dblPixelWidth = c.ChartAreas[0].AxisY.ValueToPixelPosition(s.Points[0].YValues[1]) - c.ChartAreas[0].AxisY.ValueToPixelPosition(s.Points[0].YValues[0]);
s.Label = ig.Text.AutoEllipsis(s.Font, Convert.ToInt32(dblPixelWidth)-dblSeriesPaddingGuess);
}
}
public static string AutoEllipsis(this String s, Font f, int intPixelWidth)
{
if (s.Length == 0 || intPixelWidth == 0) return "";
var result = Regex.Split(s, "\r\n|\r|\n");
List<string> l = new List<string>();
foreach(string str in result)
{
int vt = TextRenderer.MeasureText(str, f).Width;
if (vt < intPixelWidth)
{ l.Add(str); }
else
{
string strTemp = str;
int i = str.Length;
while (TextRenderer.MeasureText(strTemp + "…", f).Width > intPixelWidth)
{
strTemp = str.Substring(0, --i);
if (i == 0) break;
}
l.Add(strTemp + "…");
}
}
return String.Join("\r\n", l);
}
This seems to work quite happily as long as it is the Post_Paint event (If you use the Paint event it will stop ToolTips from showing)
I have a Navigation-bar in my program that allows you to navigate the different sections in my TextBox, but the problem I have is that this doesn't work if the Text I am scrolling to is already visible on the screen.
Like in this example, if I try to jump from Section 1 to Section 3, it won't work as it's already visible.
But, in this example if I jump to Section 3, it works fine as it's not already visible.
The scrolling function I use is very simple:
if (nLine > 0 && nLine <= textBox.LineCount)
textBox.ScrollToLine(nLine - 1);
I hope that someone can shed some light on an alternative solution that allows me to scroll even if the text is already visible.
Edit: Added solution.
This is a code snippet from my project.
private static void ScrollToLineCallback(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var textBox = (TextBox)target;
int newLineValue;
if (Int32.TryParse(e.NewValue.ToString(), out newLineValue))
{
if (newLineValue > 0 && newLineValue <= textBox.LineCount) // Validate
{
textBox.ScrollToLine(newLineValue - 1); // Scroll to Line
// Check and see if we are at the line we want.
if (textBox.GetFirstVisibleLineIndex() <= newLineValue && textBox.GetLastVisibleLineIndex() >= newLineValue)
{
// If not lets move to the desired location
int newLineCorrectionValue = newLineValue - textBox.GetFirstVisibleLineIndex() - 2; // How much further do we need to scroll down?
for (int i = 0; i < newLineCorrectionValue; i++)
{
textBox.LineDown(); // Scroll down
}
}
}
}
}
You could use GetCharacterIndexFromLineIndex to get the index of the beginning of the desired line and then set the CaretIndex to that value.
Because I don't really know, what you are trying to achieve, another possibility is to use LineUp and LineDown in conjunction with GetFirstVisibleLineIndex and GetLastVisibleLineIndex.
private void TextBoxGotoLine(
TextBox textbox1,
int linenum)
{
var target_cpos = textbox1.GetCharacterIndexFromLineIndex(linenum);
var target_char_rect = textbox1.GetRectFromCharacterIndex(target_cpos);
var first_char_rect = textbox1.GetRectFromCharacterIndex(0);
textbox1.ScrollToVerticalOffset(
target_char_rect.Top -
first_char_rect.Top
);
}
I found out if Wrapping is enabled its more complications:
private void TextBoxGotoLine(TextBox textbox1, int linenum)
{
// int Linenum is the Absolute Line, not including
// effect of Textbox Wrapping.
if (textbox1.TextWrapping == TextWrapping.Wrap)
{
// If textbox Wrapping is on, we need to
// Correct the Linenum for Wrapping which adds extra lines
int cidx = 0;
bool found = false;
int ln = 0;
char[] tmp = textbox1.Text.ToCharArray();
for (cidx = 0; cidx < tmp.Length; cidx++)
{
if (tmp[cidx] == '\n')
{
ln++;
}
if (ln == linenum)
{
found = true;
break;
}
}
if (!found)
return;
linenum = textbox1.GetLineIndexFromCharacterIndex(cidx+1);
}
// Non-Wrapping TextBox
var target_cpos = textbox1.GetCharacterIndexFromLineIndex(linenum);
var target_char_rect = textbox1.GetRectFromCharacterIndex(target_cpos);
var first_char_rect = textbox1.GetRectFromCharacterIndex(0);
textbox1.ScrollToVerticalOffset(target_char_rect.Top - first_char_rect.Top);
}