I am trying to make a simple WYSIWYG editor. I found pretty difficult to format the rtb.
It is supposed to format basic things like bold, italic, coloring(and mixed).
What have I found and tried so far:
private void boldButton_Click(object sender, EventArgs e)
{
int start = rtb.SelectionStart;
int length = rtb.SelectionLength;
for (int i = start, max = start + length; i < max; ++i)
{
rtb.Select(i, 1);
rtb.SelectionFont = new Font(rtb.Font, rtb.SelectionFont.Style | FontStyle.Bold);
}
rtb.SelectionStart = start;
rtb.SelectionLength = length;
rtb.Focus();
}
rtb = richtextbox.
This works as expected, but is terribly slow.
I also found the idea about using and formatting directly the RTF, but the format seems too complicated and very easy to mistake it.
I hope it is a better solution.
Thank you.
The performance hit is probably down to the fact you're looping through each character instead of doing everything in one go:
var start = this.rtb.SelectionStart;
var length = this.rtb.SelectionLength;
this.rtb.Select(start, length);
this.rtb.SelectionFont = new Font(this.rtb.Font, this.rtb.SelectionFont.Style | FontStyle.Bold);
I've had the same problem myself. Interestingly, I found out that you can speed up formatting by an order of magnitude if you refrain from referencing to the control's properties when looping through. Instead, put the necessary control properties in separate variables before entering the loop. For instance, instead of continuously referencing e.g. richTextBox1.Length, replace with int len = richTextBox1.Length, and then refer to len inside the loop. Instead of referencing to richTextBox1.Text[index], replace with string text = richTextBox1.Text before the loop, and then instead text[index] inside the loop.
Related
I'm trying to dynamically position controls on a page, I've got the hang of "wrapping" the controls to the next line when the width is smaller than the total width of the controls.
The problem I'm having now, is getting the spacing correct.
I currently have the following;
public void AddControl(Control controlToAdd, int parentWidth, int allRowsHeight)
{
RowControls.Add(controlToAdd);
int seperationWidth = (parentWidth - RowControls.Sum(c => c.Width)) / (RowControls.Count + 1);
int count = 0;
foreach (Control c in RowControls)
{
int xLocation = (seperationWidth*(count+1));
for (int i = 0; i < count; i++)
{
xLocation += (RowControls[i].Width);
}
c.Location = new Point(xLocation, allRowsHeight);
count++;
}
}
This almost works, but as you can see from the screenshot, the controls are going a bit too far and I'm not quite sure why that is?
The "wrapping" check is basically a repetition of the seperationWidth line, it ensures of minimum spacing of 1, i.e if the spacing is less than 1, it "wraps" instead.
Perhaps there's a completely different and better way to do this? I do have access to DevExpress too if anyone is familiar with that.
EDIT:
Suspect my method has an issue with rounding. Not sure how I can get around it though;
In this instance, seperationWidth is 7 and the control widths are 128.
7+128+7+128+7+128+7+128+7=547.
Not sure how I can get around this issue though?
OK, so the issue here was that I was passing the Width property into the parentWidth parameter. What I actually needed to do was pass the ClientSize.Width property. This fixed the issue, buttons are now laid out correctly.
Hi well my problem is this
I have a RichTextBox but i wanna add a "pretty" space after the paragraph, i found on the internet many examples but all examples change all the lines and not only the paragraph.
private void FormatRTB(byte rule, int space, int x)
{
PARAFORMAT fmt = new PARAFORMAT();
fmt.cbSize = Marshal.SizeOf(fmt);
fmt.dwMask = PFM_LINESPACING;
fmt.dyLineSpacing = space;
fmt.bLineSpacingRule = rule;
richTextBox1.Select(x, 2);
SendMessage(new HandleRef(richTextBox1, richTextBox1.Handle),
EM_SETPARAFORMAT,
SCF_SELECTION,
ref fmt
);
}
Well i add this code and select ony the \n because after of "\n" start the paragraph and dosent works i dont if my logic is bad or i need to add more code
while (richTextBox1.Text.IndexOf("\n", k) > 0)
{
k = richTextBox1.Text.IndexOf("\n", k);
setLineFormat(2, 0, k);
k++;
}
.
I know that there is already an accepted answer, but maybe this will help other people.
If you really want to add spacing before or after a paragraph in RichTextBox, there is a very simple and "native" (ie. no hacking) solution using PFM_SPACEBEFORE or PFM_SPACEAFTER. The code is quite similar to the first one you present.
The complete solution with a custom control is posted on http://dominicweb.eu/en/blog/various/winforms-richtextbox-with-paragraph-spacing-csharp/
If you are sure that all the occurrences of "\n" are indeed a different paragraph, you can simply add spaces after it. You could use a simple loop as:
for (int i = 0; i < richTextBox1.Text.Length; i++)
{
if (richTextBox1.Text[i] == '\n')
richTextBox1.Text.Insert(i + 1, " ");
}
Often though paragraphs are marked with both '\n' and '\r' so you may look for the \r instead
I am using RichTextBox and TextBox for showing some information which is collected during several days. So there are a lot of strings inside it after couple days and I get OutOfMemory exception. I think this error occurs because of lots of data. Is there some properties or functions which allow to limit number of strings inside RichTextBox and TextBox? I need to truncate only old strings which are in the beggining of list. For instance, take a look at picture below:
Any ideas?
I created simple code which allows me to resolve this problem.
For TextBox:
if (limitLines>0 && simpleTextBox.LineCount > limitLines)
{
string tempText = "";
for (int i = simpleTextBox.LineCount-limitLines; i < simpleTextBox.LineCount; i++)
{
tempText += simpleTextBox.GetLineText(i);
}
simpleTextBox.Clear();
simpleTextBox.Text = tempText;
}
simpleTextBox.AppendText(data);
For RichTextBox:
TextRange tr = new TextRange(richTextBox.Document.ContentEnd, richTextBox.Document.ContentEnd);
tr.Text = text + "\r\n";
tr.ApplyPropertyValue(TextElement.ForegroundProperty, solidColorBrush);
if (limitLines > 0 && richTextBox.Document.Blocks.Count > limitLines)
{
for (int i = richTextBox.Document.Blocks.Count - limitLines; i < richTextBox.Document.Blocks.Count; i++)
richTextBox.Document.Blocks.Remove(richTextBox.Document.Blocks.FirstBlock);
}
I hope it helps to someone else!
I'm making a game, and I read dialogue text from an XML file. I'm trying to make a routine to add in newlines automatically as needed, so that it fits in the text box. It just won't work right, though. Here's the code as it currently is:
SpriteFont dialogueFont = font31Adelon;
int lastSpace = 0;
string builder = "";
string newestLine = "";
float maxWidth = 921.6f;
float stringLength = 0;
for (int i = 0; i <= speech.Length - 1; i++) //each char in the string
{
if (speech[i] == ' ') //record the index of the most recent space
{
lastSpace = i;
}
builder += speech[i];
newestLine += speech[i];
stringLength = dialogueFont.MeasureString(newestLine).X;
if (stringLength > maxWidth) //longer than allowed
{
builder = builder.Remove(lastSpace); //cut off from last space
builder += Environment.NewLine;
i = lastSpace; //start back from the cutoff
newestLine = "";
}
}
speech = builder;
My test string is "This is an example of a long speech that has to be broken up into multiple lines correctly. It is several lines long and doesn't really say anything of importance because it's just example text."
This is how speech ends up looking:
http://www.iaza.com/work/120627C/iaza11394935036400.png
The first line works because it happens to be a space that brings it over the limit, I think.
i = 81 and lastSpace = 80 is where the second line ends. builder looks like this before the .Remove command:
"This is an example of a long speech that\r\nhas to be broken up into multiple lines c"
and after it is run it looks like this:
"This is an example of a long speech that\r\nhas to be broken up into multiple line"
The third line goes over the size limit at i = 123 and lastSpace = 120. It looks like this before the .Remove:
"This is an example of a long speech that\r\nhas to be broken up into multiple line\r\ncorrectly. It is several lines long and doe"
and after:
"This is an example of a long speech that\r\nhas to be broken up into multiple line\r\ncorrectly. It is several lines long an"
As you can see, it cuts off an extra character, even though character 80, that space, is where it's supposed to start removing. From what I've read .Remove, when called with a single parameter, cuts out everything including and after the given index. It's cutting out i = 79 too, though! It seems like it should be easy enough to add or subtract from lastSpace to make up for this, but I either get "index out of bounds" errors, or I cut off even more characters. I've tried doing .Remove(lastSpace, i-lastSpace), and that doesn't work either. I've tried handling "ends with a space" cases differently than others, by adding or subtracting from lastSpace. I've tried breaking things up in different ways, and none of it has worked.
I'm so tired of looking at this, any help would be appreciated.
You add Environment.NewLine to your StringBuilder, you need to consider that when you specify the index where to start removing.
Environment.NewLine's value is System dependent. It can be "\r\n", "\n" or "\r" (or, only one of the first two according to MSDN).
In your case it's "\r\n", that means for removing one space, you added two other characters.
First, you need to declare a new variable:
int additionalChars = 0;
Then, when adding a line of text, you should change your code to something like this:
builder = builder.Remove(lastSpace + additionalChars); //cut off from last space
builder += Environment.NewLine;
additionalChars += Environment.NewLine.Length - 1;
The -1 is because you already removed a space (should make this code independent of the system's definition of Environment.NewLine).
UPDATE: You should also account for words that are longer than the line limit. You should break them anyway (couldn't help but have a try):
if (stringLength > maxWidth) //longer than allowed
{
// Cut off only if there was a space to cut off at
if (lastSpace >= 0) {
builder = builder.Remove(lastSpace + additionalChars); //cut off from last space
i = lastSpace; //start back from the cutoff
}
builder += Environment.NewLine;
// If there was no space that we cut off, there is also no need to subtract it here
additionalChars += Environment.NewLine.Length - (lastSpace >= 0 ? 1 : 0);
lastSpace = -1; // Means: No space found yet
newestLine = "";
}
As an alternative approach, you could break your sentence up into an array using .split and then fill your box until there isn't space for the next work, then add the newline and start on the next line.
Can you do something like the following code. The advantage is two-fold. Firstly, it skips to the next space to measure and decide whether to add the whole word or not, rather than going letter by letter. Secondly, it only calls MeasureString once for each word in the string, rather than for every letter added to the string. It uses StringBuilder with Append when the word will fit, or AppendLine when it won't fit and a new-line needs to be added.
int lastSpace = 0;
int nextSpace = s.IndexOf(' ', lastSpace + 1);
float width = 0;
float totalWidth = 0;
float maxWidth = 200;
while (nextSpace >= 0)
{
string piece = s.Substring(lastSpace, nextSpace - lastSpace);
width = g.MeasureString(piece, this.Font).Width;
if (totalWidth + width < maxWidth)
{
sb.Append(piece);
totalWidth += width;
}
else
{
sb.AppendLine(piece);
totalWidth = 0;
}
lastSpace = nextSpace;
nextSpace = s.IndexOf(' ', lastSpace + 1);
}
MessageBox.Show(sb.ToString());
The incredibly awesome AvalonEdit WPF TextEditor control seems to lack an important feature, or at least i can't figure it out. Given an offset and a length, highlight that portion in the TextDocument with a HighlightColor. Simple, right?
Apprentely not. I've RTFM, and the documentation on "Syntax Highlighting" confused me even more. Someone else asked the same question in the SharpDevelop forums, and i'm afraid i can't make sense of Herr Grunwald's answer.
Here's my attempt, using the DocumentHighlighter class (of course it doesn't work):
textEditor1.Text = "1234567890";
HighlightingColor c = new HighlightingColor() { FontWeight = FontWeights.ExtraBold };
DocumentHighlighter dh = new DocumentHighlighter(textEditor1.Document, new HighlightingRuleSet());
HighlightedLine hl = dh.HighlightLine(1);
hl.Sections.Add(new HighlightedSection() { Color = c, Offset = 1, Length = 3 });
Thank you for helping!
Did you see this in this article - it seems to be exactly what are you asking for:
public class ColorizeAvalonEdit : DocumentColorizingTransformer
{
protected override void ColorizeLine(DocumentLine line)
{
int lineStartOffset = line.Offset;
string text = CurrentContext.Document.GetText(line);
int start = 0;
int index;
while ((index = text.IndexOf("AvalonEdit", start)) >= 0) {
base.ChangeLinePart(
lineStartOffset + index, // startOffset
lineStartOffset + index + 10, // endOffset
(VisualLineElement element) => {
// This lambda gets called once for every VisualLineElement
// between the specified offsets.
Typeface tf = element.TextRunProperties.Typeface;
// Replace the typeface with a modified version of
// the same typeface
element.TextRunProperties.SetTypeface(new Typeface(
tf.FontFamily,
FontStyles.Italic,
FontWeights.Bold,
tf.Stretch
));
});
start = index + 1; // search for next occurrence
} } }
It highlights word AvalonEdit with bold.
Some background info:
AvalonEdit is a code editor, not a rich text editor. There is no such thing as "highlight a portion of the document" - the document only stores plain text.
Highlighting is computed on-demand, only for the lines currently in view. If you want custom highlighting, you need to add a step to the highlighting computation - this is what the ColorizeAvalonEdit class in the example posted by mzabsky is doing.
You need to create a custom ColorizingTransformer to do that. The above example is actually highlighting a specific word. Still, you can change it a little bit to to colorize or highlight a portion.
I used Avalon TextEditor for my Console+ project (which is in a very primitive stage at the moment)
public class OffsetColorizer : DocumentColorizingTransformer
{
public int StartOffset { get; set; }
public int EndOffset { get; set; }
protected override void ColorizeLine(DocumentLine line)
{
if (line.Length == 0)
return;
if (line.Offset < StartOffset || line.Offset > EndOffset)
return;
int start = line.Offset > StartOffset ? line.Offset : StartOffset;
int end = EndOffset > line.EndOffset ? line.EndOffset : EndOffset;
ChangeLinePart(start, end, element => element.TextRunProperties.SetForegroundBrush(Brushes.Red));
}
}
And you can add the colorizer to the editor by adding it to LineTransformers collection.
tbxConsole.TextArea.TextView.LineTransformers.Add(_offsetColorizer);
I know this is a pretty old question, but I thought I would share my solution. I am not sure if this solution has been implemented into AvalonEdit, since this question was originally answered, but I find the OffsetColorizer class doesn't actually select the line: it just changes the line's background colour.
My solution is as follows:
textEditor.SelectionStart = offset;
textEditor.SelectionLength = length;
However, this can be extended further like so:
public void SelectText(int offset, int length)
{
//Get the line number based off the offset.
var line = textEditor.Document.GetLineByOffset(offset);
var lineNumber = line.LineNumber;
//Select the text.
textEditor.SelectionStart = offset;
textEditor.SelectionLength = length;
//Scroll the textEditor to the selected line.
var visualTop = textEditor.TextArea.TextView.GetVisualTopByDocumentLine(lineNumber);
textEditor.ScrollToVerticalOffset(visualTop);
}
I find that this solution works better is that rather than just colouring the line, it actually selects it: meaning it can be copied using Ctrl+C.
I Hope this helps people in the future.