Get a WPF RichTextBox to draw a horizontal line over text - c#

Say I have a given TextRange range that happens to have this text in it ----------------- (On its own line.)
I want to draw a real line whenever I see that text (instead of just 15 dashes).
But, I need to leave the dashes there for when I save it (and when other, plain text viewers load it).
I found how I can draw a line in the RichTextBox:
var line = new Line {X1 = 10, X2 = 200, Y1 = 5, Y2 = 5,
var paragraph = (Paragraph) MyRichTextBox.Document.Blocks.FirstBlock;
paragraph.Inlines.Add(line);
But this just draw after the last Inline in the paragraph.
So, my question is:
How can I draw so that my UIElement does not have text wrapping on (so that I can cover the dashes)?
Is this possible with the WPF RichTextBox?

Could you use "TextDecorations.Strikethrough" for this.
TextRange range = new TextRange(RichTextBox.Selection.Start, RichTextBox.Selection.End);
TextDecorationCollection tdc = (TextDecorationCollection)RichTextBox.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
if (!tdc.Equals(TextDecorations.Strikethrough))
{
tdc = TextDecorations.Strikethrough;
}
range.ApplyPropertyValue(Inline.TextDecorationsProperty, tdc);

I think you have to remove the Inline from within the paragraph and replace it with a Line element. In this case, you will have to replace Line elements with "-----" on save.
private void FindHRules()
{
foreach (Paragraph block in rtf.Document.Blocks.OfType<Paragraph>())
{
var inlines = block.Inlines.ToList();
for(int i = 0; i<inlines.Count; i++)
{
var inline = inlines[i];
TextRange r = new TextRange(inline.ContentStart, inline.ContentEnd);
if (r.Text.StartsWith("--"))
{
Line l = new Line { Stretch = Stretch.Fill, Stroke = Brushes.DarkBlue, X2 = 1 };
block.Inlines.InsertAfter(inline, new InlineUIContainer(l));
block.Inlines.Remove(inline);
}
}
}
}
I tested this with a RTF doc that had the "-----" lines in stand-alone paragraphs (<enter>) and line breaks (<shift-enter>) within other paragraphs.

Related

How to add data table with legend keys to a MS Chart in C#?

There are 2 lists called listversion & MIN_list. Using values of these list I have created a line chart. Everything is work fine. But I am wondering whether it is possible to add a data table with legend keys in to the chart like MS Excel.
chart.Series.Clear();
chart.ChartAreas[0].AxisX.Title = "Version";
chart.ChartAreas[0].AxisX.TitleFont = new System.Drawing.Font("Arial", 12, FontStyle.Regular);
chart.ChartAreas[0].AxisY.Title = "Time";
chart.ChartAreas[0].AxisY.TitleFont = new System.Drawing.Font("Arial", 12, FontStyle.Regular);
Series MIN = chart.Series.Add("Minimum");
MIN.Points.DataBindXY(listVersion, MIN_list[j]);
MIN.ChartType = SeriesChartType.Line;
MIN.Color = Color.Red;
MIN.BorderWidth = 3;
I am looking forward to something like this
If it is possible How can I do it ?
Thank you.
Yes you can do that:
Here are the steps I took:
First we disable the original Legend as it can't be manipulated the way we need to..:
chart1.Legends[0].Enabled = false;
Now we create a new one and a shortcut reference to it:
chart1.Legends.Add(new Legend("customLegend"));
Legend L = chart1.Legends[1];
Next we do some positioning:
L.DockedToChartArea = chart1.ChartAreas[0].Name; // the ca it refers to
L.Docking = Docking.Bottom;
L.IsDockedInsideChartArea = false;
L.Alignment = StringAlignment.Center;
Now we want to fill in one line for headers and one line per series.
I use a common function for both and pass in a flag to indicate whether the headers (x-values) or the cell data (y-values) should be filled in. Here is how I call the function:
addValuesToLegend(L, chart1.Series[0], false);
foreach (Series s in chart1.Series) addValuesToLegend(L, s, true);
Note that for this to work we need a few preparations in our Series:
We need to set the Series.Colors explicitly or else we can't refer to them.
I have added a format string to the Tag of each series; but maybe you find a better solution that avoids hard-coding the format for the header..
So here is the function that does all the filling and some styling:
void addValuesToLegend(Legend L, Series S, bool addYValues)
{
// create a new row for the legend
LegendItem newItem = new LegendItem();
// if the series has a markerstyle we show it:
newItem.MarkerStyle = S.MarkerStyle ;
newItem.MarkerColor = S.Color;
newItem.MarkerSize *= 2; // bump up the size
if (S.MarkerStyle == MarkerStyle.None)
{
// no markerstyle so we just show a colored rectangle
// you could add code to show a line for other chart types..
newItem.ImageStyle = LegendImageStyle.Rectangle;
newItem.BorderColor = Color.Transparent;
newItem.Color = S.Color;
}
else newItem.ImageStyle = LegendImageStyle.Marker;
// the rowheader shows the marker or the series color
newItem.Cells.Add(LegendCellType.SeriesSymbol, "", ContentAlignment.MiddleCenter);
// add series name
newItem.Cells.Add(LegendCellType.Text, addYValues ? S.Name : "",
ContentAlignment.MiddleLeft);
// combine the 1st two cells:
newItem.Cells[1].CellSpan = 2;
// we hide the first cell of the header row
if (!addYValues)
{
newItem.ImageStyle = LegendImageStyle.Line;
newItem.Color = Color.Transparent;
newItem.Cells[0].Tag = "*"; // we mark the 1st cell for not painting it
}
// now we loop over the points:
foreach (DataPoint dp in S.Points)
{
// we format the y-value
string t = dp.YValues[0].ToString(S.Tag.ToString());
// or maybe the x-value. it is a datatime so we need to convert it!
// note the escaping to work around my european locale!
if (!addYValues) t = DateTime.FromOADate(dp.XValue).ToString("M\\/d\\/yyyy");
newItem.Cells.Add(LegendCellType.Text, t, ContentAlignment.MiddleCenter);
}
// we can create some white space around the data:
foreach (var cell in newItem.Cells) cell.Margins = new Margins(25, 20, 25, 20);
// finally add the row of cells:
L.CustomItems.Add(newItem);
}
To draw the borders around the cells of our legend table we need to code the PrePaint event:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
LegendCell cell = e.ChartElement as LegendCell;
if (cell != null && cell.Tag == null)
{
RectangleF r = e.ChartGraphics.GetAbsoluteRectangle(e.Position.ToRectangleF());
e.ChartGraphics.Graphics.DrawRectangle(Pens.DimGray,Rectangle.Round(r));
// Let's hide the left border when there is a cell span!
if (cell.CellSpan != 1)
e.ChartGraphics.Graphics.DrawLine(Pens.White,
r.Left, r.Top+1, r.Left, r.Bottom-1);
}
}
You can add more styling although I'm not sure if you can match the example perfectly..

Rectangle's width increasing more than it should while trying to resize the rectangle

I don't think the title is on the spot. But please check the pictures below for a better demonstration.
I have a rectangle which fits text in it. What I'm trying to do is that, if the rectangle's height is bigger than the screen, then the rectangle should resize.
Resizing goes as follows: set the height to screen height, increase width, try to fit text, repeat.
Here is the method that parses my text, it returns the string that would fit in the rectangle given the width.
private String parseText(String text, int Width)
{
String line = String.Empty;
String returnString = String.Empty;
String[] wordArray = text.Split(' ');
int i = 1;
foreach (String word in wordArray)
{
if (Font.MeasureString(line + word).Length() > Width)
{
returnString = returnString + line + '\n';
line = String.Empty;
i++;
}
line = line + word + ' ';
}
this.Size = new Size(Size.Width, (int)Font.MeasureString(returnString + line).Y);
return returnString + line;
}
This part works really well. However, if the text exceeds the height, the text is drawn outside of the rectangle.
So I added this:
public string Text
{
get { return text; }
set
{
temp = parseText(value.Replace("\n", ""), Size.Width);
while(Size.Height > Viewport.Height)
{
Size = new Size(Size.Width + 5, Viewport.Height);
temp = parseText(value, Size.Width);
}
text = temp;
}
}
This is my problem:
Part 1: Text is ok. Does not hit the screen's edge
Part 2: Right after hitting the screen edge by adding a bunch of 'hello'
Part 3: Adding one more "hello" properly fixes the issue.
What is happening in Part 2? How does it get resized like that? Why is it fixed in part 3?
Note: Regardless of what I add here: Size = new Size(Size.Width + 5, Viewport.Height); either +5, or 5% of the screen, or 20% or 200% it still gets resized to the same amount.
Comment for more information. Thanks
Fixed by adding: value.Replace("\n", "") in temp = parseText(value, Size.Width);
I was doing that before the while loop, but not inside it. Therefore, the text was getting a bunch of new lines, and when it got called again, the new lines disappeared before the while loop.
It should look like this: temp = parseText(value.Replace("\n", ""), Size.Width - (int)LeftMargin);

Highlighting a line of text in a richtextbox

So I am working on an program that offers the user a 3-D visualization of data structures and sort algorithms. What I would like to do is have a richtextbox on the UI that shows the code for the particular algorithm that is being performed. And then I would like to have each particular line of the code to be highlighted as it is being executed. I just wanted to start with visualizing a stack since it is easier to deal with as I learn and work through this project. Right now I have a text file of c++ push and pop functions and I am saving the text into a list. I am then writing the text to the richtextbox. All of this is working but I don't know how to highlight a line and then highlight the next line. For example when I click "push" I would like it to highlight "list[stackTop] = newItem;" then draw the 3d cube (already done), then highlight the "stackTop++" line. Then the user can do it again or whatever else they want.
class CppFunctionsArray
{
List<string> ReadFunctions = new List<string>();
int Position = 0;
//Reads input from selected file and stores into ReadFunctions Array;
public void ReadInput(string fileName)
{
using (StreamReader r = new StreamReader(fileName))
{
string line;
while ((line = r.ReadLine()) != null)
{
ReadFunctions.Add(line);
}
}
}
//Writes lines to a RichTextBox.
public void WriteToRichTextBox(RichTextBox rtb, int startIndex, int endIndex, int lineNumber)
{
Position = 0;
for (int i = startIndex; i < endIndex; i++)
{
rtb.AppendText(ReadFunctions[i]);
rtb.AppendText(Environment.NewLine);
rtb.Font = new Font("times new roman", 12, FontStyle.Bold);
//Temporary
if (lineNumber == Position)
rtb.SelectionBackColor = Color.Red;
Position++;
}
}
These are not topics they are teaching me college. I am just teaching myself here. So if I am approaching this totally wrong, I am open to anything here.
Here is my event handler for "stackPush" button.
//Adds cube on top of the previous.
private void StackPush_Click(object sender, EventArgs e)
{
CppFunctionsArray ArrayOfFunctions = new CppFunctionsArray();
CodeTextBox.Clear();
ArrayOfFunctions.ReadInput("StackFunctions.txt");
//The 4 represents the line Number to highlight. TODO FIX THIS.
ArrayOfFunctions.WriteToRichTextBox(CodeTextBox, 1, 12,4);
//Draws a new cube of 1 unit length.
cube = new Visual();
//Adds cube to list;
cubeList.Add(cube);
cube.y = position;
position++;
}
If you are looking for an extension method to clear the background color from all lines of a RichTextBox, then color a specific line, the following should suffice:
public static void HighlightLine(this RichTextBox richTextBox, int index, Color color)
{
richTextBox.SelectAll();
richTextBox.SelectionBackColor = richTextBox.BackColor;
var lines = richTextBox.Lines;
if (index < 0 || index >= lines.Length)
return;
var start = richTextBox.GetFirstCharIndexFromLine(index); // Get the 1st char index of the appended text
var length = lines[index].Length;
richTextBox.Select(start, length); // Select from there to the end
richTextBox.SelectionBackColor = color;
}

Converting Path & StrokeWidth to Geometry

I have a bit of code that takes a number of Points and creates multiple LineSegments to build up a Path.
System.Windows.Shapes.Path pathSegment = new System.Windows.Shapes.Path();
PathFigure pathFig = new PathFigure();
PathGeometry pathGeo = new PathGeometry();
pathFig.StartPoint = new Point(pointData[0].X, pointData[0].Y);
for (int loop = 1; loop < pointData.Count; loop++)
{
LineSegment ls = new LineSegment();
ls.Point = new Point(pointData[loop].X, pointData[loop].Y);
pathFig.Segments.Add(ls);
}
pathGeo.Figures.Add(pathFig);
pathSegment.Data = pathGeo;
pathSegment.Stroke = brush;
pathSegment.StrokeThickness = 22;
This creates my line with a width of 22px (roughly). Now if you look at the actual Data for this you can only see the LineSegement elements, which essentially gives you an output like this, where the real line is in black and the actual displayed line is in grey (excuse the dodgy mspaint sketch):
Now I want to perform a StrokeContains on the Geometry to see if a specified Point is within the entire pathSegment above (grey area). What it actually does though is check against the LineSegments (the black line).
Is there a better way to build up the Path? or is there a way of converting the pathSegment, including the StrokeWidth to a new Path?
It should work if you use the proper Pen Thickness in the StrokeContains call:
Point point = ...
Pen pen = new Pen { Thickness = pathSegment.StrokeThickness };
bool contains = pathSegment.Data.StrokeContains(pen, point);
Alternatively you could simply do a hit test on the Path:
bool contains = pathSegment.InputHitTest(point) != null;

Line between 2 controls doesn't redraw

I have b Buttons created dynamically by code and I want to draw a Line between the first node and the last in the same StackPanel. The problem is that whenever I call this function it doesn't make the line from the first to the b Button but instead it move the line to the right.
My code for the Line draw function:
public void CreateALine()
{
redLine = new Line();
redLine.X1 = nodul_nou[0].Margin.Left ;
redLine.Y1 = nodul_nou[b].Margin.Top - 40;
redLine.X2 = nodul_nou[b].Margin.Left ;
redLine.Y2 = nodul_nou[b].Margin.Top - 40;
SolidColorBrush redBrush = new SolidColorBrush();
redBrush.Color = Colors.Black;
redLine.StrokeThickness = 4;
redLine.Stroke = redBrush;
workplace.Children.Add(redLine);
}
Note: nodul_nou[b] is the Button I am speak about, It does not draw a line between the first Button, nodul_nou[0], and the b Button, nodul_nou[b].

Categories

Resources