Save Richtextbox text in TabControl to a file - c#

I have a code that will write me all the text from all RichTextBoxes that are in the TabPage. The problem is that it does not save any text.
string projectFile = projectPathFolder + #"\" + projectName + #"\" + projectName + ".project";
for (int i = 0; i < tabControl1.RowCount; i++)
{
RichTextBox richText = tabControl1.Controls[i] as RichTextBox;
using (var stream = new StreamWriter(File.Create(projectFile)))
{
stream.Write(scintilla.Text);
}
File.WriteAllText(projectFile, "// " + tabControl1.TabPages[i].Text + "\n\n" + richText.Text, Encoding.UTF8);
}

RowCount is the wrong property to use as it returns the number of rows, not tabs. E.g. you could have 6 tabs but RowCount may still only be 1 if the tabs are narrow and the control wide.
Firstly, are you trying to enumerate all pages in the TabControl? If that's the case, use TabCount in your for loop, not RowCount
for (int i = 0; i < tabControl1.TabCount; i++)
If you only want to use the current tab, then get rid of the loop and just use tabControl1.SelectedTab.
Secondly, the line trying to get the RichTextBox is never going to work. A TabControl can only host TabPage instances, and trying to access RichTextBox instances via the Controls property is simply not going to work. You will need to get the TabPage first and then either enumerate the controls on it to find the RichTextBox, or if there is only a single control per page then you could directly access it.
RichTextBox richText = tabControl1.TabPages[i].Controls[0] as RichTextBox;
I'm not at all sure why you have that using block to write content to projectFile - it's just going to get overwritten by the call to File.WriteAllText.
As an aside, joining paths by hand is bad practice - try using Path.Combine, e.g. Path.Combine(projectPathFolder, projectName, projectName + ".project");
Edit: One other point. You're using as RichTextBox, which generally means you accept the cast may not be valid and return null. However, you're not doing an explicit null check on that result. If you expect failure, then wrap the File.WriteAllText statement in a null check. If you don't expect failure, then make the cast explicit and let it crash at the point of the cast - better to fail early than later.
RichTextBox richText = (RichTextBox)tabControl1.TabPages[i].Controls[0];
Hope this helps.

Related

Selecting text and changing it's color on a line of a RichTextBox

Good Afternoon. I am new to stack overflow as a poster but have referenced it for years. I have been researching this problem of mine for about 2 weeks and while I've seen solutions that are close I still am left with an issue.
I am writing a C# gui that reads in an assembly code file and highlights different text items for further processing via another program. My form has a RichTextBox that the text is displayed in. In the case below I am trying to select the text at the location of the ‘;’ until the end of the line and change the text to color red. Here is the code that I am using.
Please note: The files that are read in by the program are of inconsistent length, not all lines are formatted the same so I cannot simply search for the ';' and operate on that.
On another post a member has given an extension method for AppendText which I have gotten to work perfectly except for the original text is still present along with my reformatted text. Here is the link to that site:
How to use multi color in richtextbox
// Loop that it all runs in
Foreach (var line in inArray)
{
// getting the index of the ‘;’ assembly comments
int cmntIndex = line.LastIndexOf(';');
// getting the index of where I am in the rtb at this time.
int rtbIndex = rtb.GetFirstCharIndexOfCurrentLine();
// just making sure I have a valid index
if (cmntIndex != -1)
{
// using rtb.select to only select the desired
// text but for some reason I get it all
rtb.Select(cmntIndex + rtbIndex, rtb.SelectionLength);
rtb.SelectionColor = Color.Red;
}
}
Below is the sample assembly code from a file in it's original form all the text is black:
;;TAG SOMETHING, SOMEONE START
ASSEMBLY CODE ; Assembly comments
ASSEMBLY CODE ; Assembly comments
ASSEMBLY CODE ; Assembly comments
;;TAG SOMETHING, SOMEONE FINISH
When rtb.GetFirstCharIndexOfCurrentLine() is called it returns a valid index of the RTB and I imagine that if I add the value returned by line.LastIndexOf(';') I will then be able to just select the text above that looks like ; Assembly comments and turn it red.
What does happen is that the entire line turns red.
When I use the AppendText method above I get
ASSEMBLY CODE (this is black) ; Assembly comments (this is red) (the rest is black) ASSEMBLY CODE ; Assembly comments
The black code is the exact same code as the recolored text. In this case I need to know how to clear the line in the RTB and/or overwrite the text there. All the options that I have tried result in deletion of those lines.
Anywho, I'm sure that was lengthy but I'm really stumped here and would greatly appreciate advice.
I hope I've understood you correctly.
This loops over each line in the richtextbox, works out which lines are the assembly comments, then makes everything red after the ";"
With FOREACH loop as requested
To use a foreach loop you simply need to keep track of the index manually like so:
// Index
int index = 0;
// Loop over each line
foreach (string line in richTextBox1.Lines)
{
// Ignore the non-assembly lines
if (line.Substring(0, 2) != ";;")
{
// Start position
int start = (richTextBox1.GetFirstCharIndexFromLine(index) + line.LastIndexOf(";") + 1);
// Length
int length = line.Substring(line.LastIndexOf(";"), (line.Length - (line.LastIndexOf(";")))).Length;
// Make the selection
richTextBox1.SelectionStart = start;
richTextBox1.SelectionLength = length;
// Change the colour
richTextBox1.SelectionColor = Color.Red;
}
// Increase index
index++;
}
With FOR loop
// Loop over each line
for(int i = 0; i < richTextBox1.Lines.Count(); i++)
{
// Current line text
string currentLine = richTextBox1.Lines[i];
// Ignore the non-assembly lines
if (currentLine.Substring(0, 2) != ";;")
{
// Start position
int start = (richTextBox1.GetFirstCharIndexFromLine(i) + currentLine.LastIndexOf(";") + 1);
// Length
int length = currentLine.Substring(currentLine.LastIndexOf(";"), (currentLine.Length - (currentLine.LastIndexOf(";")))).Length;
// Make the selection
richTextBox1.SelectionStart = start;
richTextBox1.SelectionLength = length;
// Change the colour
richTextBox1.SelectionColor = Color.Red;
}
}
Edit:
Re-reading your question I'm confused as to whether you wanted to make the ; red as well.
If you do remove the +1 from this line:
int start = (richTextBox1.GetFirstCharIndexFromLine(i) + currentLine.LastIndexOf(";") + 1);
Private Sub RichTextBox1_Click(sender As Object, e As EventArgs) Handles RichTextBox1.Click
Dim MyInt1 As Integer
Dim MyInt2 As Integer
' Reset your RTB back color to white at each click
RichTextBox1.SelectionBackColor = Color.White
' Define the nth first character number of the line you clicked
MyInt1 = RichTextBox1.GetFirstCharIndexOfCurrentLine()
' use that nth to find the line number in the RTB
MyInt2 = RichTextBox1.GetLineFromCharIndex(MyInt1)
'Select the line using an array property of RTB (RichTextBox1.Lines())
RichTextBox1.Select(MyInt1, RichTextBox1.Lines(MyInt2).Length)
' This line would be for font color change : RichTextBox1.SelectionColor = Color.Maroon
' This one changes back color :
RichTextBox1.SelectionBackColor = Color.Yellow
End Sub
' There are a few bugs inherent to the rtb.select method
' It bugs if a line wraps, or fails on an "http" line... probably more.
(I just noticed the default stackoverflow.com character colors on my above code are not correct for comment lines and others.)

Cannot reference dynamically referenced controls

I am using the instructions found here to access a few textboxes that are already in my Winform. For some reason I get the error:
Object reference not set to an instance of an object
I am quite sure that the code is correct but as soon as I try to access any property of the control I get that error. My code is below - can anybody spot what I am doing wrong?
TextBox textbox = this.Controls["txtLiveBlock" + ((i + 1) * (j + 1)).ToString()] as TextBox;
textbox.Text = "TESTING";
Note that my Textbox is called "txtLiveBlock1" and i = 0, j = 0. I have even tried sending the section txtLiveBlock" + ((i + 1) * (j + 1) to a MessageBox and I get "txtLiveBlock1" back.
If the control is inside another container control, like a panel or a TabPage, you would have to reference that container control:
TextBox textbox = tabPage1.Controls["txtLiveBlock" + ((i + 1) * (j + 1)).ToString()] as TextBox;
textbox.Text = "TESTING";
Break it down:
string name = "txtLiveBlock" + ((i + 1) * (j + 1)).ToString();
Control ctrl = this.Controls[name]; // returns null if the control is not found
TextBox textbox = ctrl as TextBox; // returns null if ctrl is not a TextBox
textbox.Text = "TESTING"; // if textbox is null, throws NullReferenceException
Step through this code: where does it break?
You are getting a NullReferenceException on the last line, which means that textbox is ending up as null. This could happen in a couple of ways:
this.Controls[name] returns null if the control is not in the list of controls on the form. Note that controls inside of other controls are not in this list - e.g. controls in a panel are in that panel's Controls list, not the form's.
ctrl as TextBox returns null if the ctrl is not actually a TextBox.
If you step through the code and mouse-over the variables as you go, you should be able to see what is happening.
Where are you calling this code? If it's in the constructor before InitializeComponent() gets called, then the Controls collection is empty at this point (although if it's empty, I would expect a KeyNotFoundException instead of returning a null value, but I'm not in a position to test it right now).

Outlook.Items Restrict() weird behavior

I just want to filter my Mails with the Restrict-Method like so:
restriction += "[ReceivedTime] < '" + ((DateTime)time).ToString("yyyy-MM-dd HH:mm") + "'";
var count = oFolder.Items.Restrict(restriction).Count;//Cast<object>().ToList();
for (int i = 0; i < count; i++)
{
var crntReceivedTime = ((OutLook.MailItem)oFolder.Items.Restrict(restriction).Cast<object>().ToList()[i]).ReceivedTime;
if (crntReceivedTime > time)
{
string t = "";
}
}
Theoretically the line string t = ""; should never be called, because I determined the Items to never have entries which ReceivedTime's value is bigger than time.
The problem is the line gets called, what means the restricted Items Collection contains entries which its shouldn't contain.
Did I do something wrong or is the Restrict()-method just failing?
Firstly, you are using multiple dot notation. You are calling Restrict (which is expensive even if it is called once) on each step of the loop. Call it once, cache the returned (restricted) Items collection, then loop over the items in that collection.
Secondly, what is the full restriction? You are using += to add an extra restriction on ReceivedTime. What is the actual value of the restriction variable?
Edit: I had no problem with the following script executed from OutlookSpy (I am its author - click Script button, paste the script, click Run):
restriction = " [ReceivedTime] < '2011-06-11 00:00' "
set Folder = Application.ActiveExplorer.CurrentFolder
set restrItems = Folder.Items.Restrict(restriction)
for each item in restrItems
if TypeName(item) = "MailItem" Then
Debug.Print item.ReceivedTime & " - " & item.Subject
End If
next

Getting element of enum in array

I need to figure out how to get an element on an enum in an array.
Basically, I have a grid of 9x9 buttons. I have two multi-dimensional arrays that houses these values. One houses their names (if the name is 43) it means 5 down, 4 across (because they start at 0). The name is also the same as the ELEMENT of itself in the array.
string[,] playingField = new string[9, 9];
enum CellType { Empty, Flag, Hidden, Bomb }
CellType[,] cells = new CellType[9, 9];
the names of the buttons are held in playingField.
the status of each cell is held in cells (if it is empty, has a bomb, etc.)
Credit to AbdElRaheim for giving the above. The reason I'm doing this is so I can get a button name (exactly the same as the element name) which will be the same in both arrays.
For example: I can do this:
string dim1 = Convert.ToString(btn.Name[0]);
string dim2 = Convert.ToString(btn.Name[1]);
if (cells[Convert.ToInt32(dim1), Convert.ToInt32(dim2)] == CellType.Bomb)
(please excuse my terrible converting. i'll fix that up later ;)) and what the above does is it allows me to see if a cell that you click has a bomb under it.
However, what I need to do now, is essentially reverse of this. In the above I know the element name that I want to compare it to, because the element name is the same as the button name. However, now what I need to do is FIND the element name (button name) by getting the element of all elements that are Bomb in cells.
I'm not sure how to do this, I tried:
foreach (CellType Bomb in cells)
{
but it doesn't do anything. I need to find all 'bomb' in 'cells' and return the element name. That way I can use that element name, convert it to string, and use my StringToButton method to create a reference to the button.
This is the way I'm currently doing it, for reference, and to help you understand a little better, but take note this is NOT the way I want to continue doing it. I want to do it the way I asked about above :)
foreach (string i in minedNodes)
{
Button buttonName = StringToButton(Convert.ToString(i));
buttonName.Image = new Bitmap(dir + "mine.png");
}
Thanks!
If you are looking for a way to traverse your cells array, you would do this:
int oi, ii;
for (oi = 0; oi <= cells.GetUpperBound(0); ++oi)
{
for (ii = 0; ii <= cells.GetUpperBound(1); ++ii)
{
System.Diagnostics.Debug.WriteLine(
"Checking: " + oi + "," + ii + " : " + cells[oi, ii].ToString()
);
}
}
You can then save a list of references to cells[oi, ii] contents which match your desired value.

Errata in Extreme Programming Adventures in C#?

I'm trying to work my way through Ron Jeffries's Extreme Programming Adventures in C#. I am stuck, however, in Chapter 3 because the code does not, and cannot, do what the author says it does.
Basically, the text says that I should be able to write some text in a word-wrap enabled text box. If I then move the cursor to an intermediate line and hit enter, the code should re-display the lines before the cursor, add a couple of lines and a set of HTML paragraph tags, then append the rest of the lines. The code doesn't match the text because it uses the textbox.lines property. Well, no matter how many word-wrapped lines there are in a text box, there's only ONE line in the Lines property until you hit a carriage return. So, the statement that the code should, "Copy the rest of the lines into the buffer" appears wrong to me.
I'd appreciate anybody having experience with the book telling me what I'm reading, or doing, wrong!
Thanks.
EoRaptor
Try emailing Ron Jeffries directly. I have the book - somewhere, but I don't remember it not working. His email address is ronjeffries at acm dot org and put [Ron] in the subject line.
(And for those wondering - his email info was right from his website Welcome page)
I've also just started this book and had exactly the same problem although the code you have included looks further along than where I am.
The 'subscript out of range' occurred for 2 reasons, first as Ron explains he was just testing and so returned a hard-coded value of 3 before he wrote the CursorLine() function, which means you I think at least 4? lines of text which as you say need to be pasted in, or maybe set the text to this value before running, also as you say they need to have carriage returns to make txtbox.Lines return an array of strings.
The second reason occurs even after CursorLine() has been implemented but only happens if the text box is empty as txtbox.Lines returns string[0] but I think Ron is implementing a 'User Story' which says that when text has been entered and user presses enter, so not sure if he fixes this later, but will probably find out!
The author's do state that they are learning C# and will show the development wart's and all, which is one of the reasons I have chosen to study this book as I think it is encouraging me to develop projects. I also try to do the code first before looking at his solutions to see if I'm thinking the same way, but maybe I know C# a little better than I give myself credit for, or I'm completly crap, but I've noticed a few things, first he says that overriding OnKeyDown() doesn't work, but I think he must have got confused and tried to do in Form, instead of deriving from TextBox and overriding there.
This was my code when reading the 'User Story':
int curPos = txtbox.SelectionStart;
string Wrd = Environment.NewLine + "<P></P>" + Environment.NewLine;
txtbox.SelectedText = Wrd;
int pl = Environment.NewLine.Length + 3; // "<P>" length is 3
// Put text cursor inbetween <P> tags
txtbox.SelectionStart = curPos + pl;
It works differently to Ron's code, but was just my interpretation of the 'User Story' and not sure how should act if text is selected or wether to split line if text cursor in the middle etc.
Also in 'My Adventures' in Extreme Programming Adventures in C#
txtbox.GetLineFromCharIndex(txtbox.SelectionStart)
gets the cursor line position and doesn't matter if no carriage returns or resized,
as far as I can tell, I done little test with:
txtbox.GetLineFromCharIndex(txtbox.TextLength)
which returns the total amount of lines, which will vary if you resize the text box.
Using C# I always look for solutions which already exsist and people may slate me for this but I think MS have created a great language with great components which do what you expect them to do, so don't have to re-create the wheel each time.
Although like I say it's early days in this book and perhaps these simple solutions aren't extensible enough and maybe Ron's taking that into account, although he did mention just get it working then worry about that later is more the XP way.
Warren.
print("using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace NotepadOne {
public class TextModel {
private String[] lines;
private int selectionStart;
private int cursorPosition;
public TextModel() {
}
public String[] Lines {
get {
return lines;
}
set {
lines = value;
}
}
public int SelectionStart {
get {
return selectionStart;
}
set {
selectionStart = value;
}
}
public int CursorPosition {
get {
return cursorPosition;
}
set {
cursorPosition = value;
}
}
public void InsertControlPText() {
lines[lines.Length - 1] += "ControlP";
}
public void InsertParagraphTags() {
int cursorLine = CursorLine();
String[] newlines = new String[lines.Length + 2];
for (int i = 0; i <= cursorLine; i++) {
newlines[i] = lines[i];
}
newlines[cursorLine + 1] = "";
newlines[cursorLine + 2] = "<P></P>";
for (int i = cursorLine + 1; i < lines.Length; i++) {
newlines[i + 2] = lines[i];
}
lines = newlines;
selectionStart = NewSelectionStart(cursorLine + 2);
}
private int CursorLine() {
int length = 0;
int lineNr = 0;
foreach (String s in lines) {
if (length <= SelectionStart && SelectionStart <= length + s.Length + 2) {
break;
length += s.Length + Environment.NewLine.Length;
lineNr++;
}
lineNr++;
}
return lineNr;
}
private int NewSelectionStart(int cursorLine) {
int length = 0;
for (int i = 0; i < cursorLine; i++) {
length += lines[i].Length + Environment.NewLine.Length;
}
return length + 3;
}
}
}
");
The InsertParagraphTags method is called by pressing the enter key in the textbox.
BTW, the break here is that there is a subscript out of range error if you try to hit enter at the end of the text. I'm sure I could figure out how to get around this but then my code won't look like his code; which is what I'm trying to learn.
Randy

Categories

Resources