Retrieving an accurate string array from a RichTextBox control - c#

I have a RichTextBox control on my form. The control is setup in such a way that it will wrap to the next line after 32 lines of text are input. The problem I'm having is I want to be able to retreive an array of strings representing the lines in my control. I know there is a Lines property attached to the RichTextBox, but I am experiencing 2 issues with it:
1) I ONLY want an array of strings showing the lines that are visible on the screen only. Right now the Lines array returns every single line in the RichTextBox. I only want the lines visible on the screen returned.
2) The Lines property is not giving me a true representation of my lines. It counts a "line" as a line of text ended by a carriage return or \n. So in other words, if I type 64 characters and none of them are a carriage return, then it should return 2 lines (because there are 32 characters per line). Instead, it doesn't return any lines until I hit enter. Even then, it only returns 1 line, not 2. Its acting more like a Paragraph property, if there was such a thing.
Anyone know a way around these 2 issues?I am using C# btw

You have to do a few tricks to achieve this which have to do with querying for the position of the actual lines according to the character index. The following program shows one way of doing this. You might have to harden it a bit, but it should get you started:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
if (richTextBox1.Text == "")
return;
// Lines corresponding to the first and last characters:
int firstLine = richTextBox1.GetLineFromCharIndex(0);
int lastLine = richTextBox1.GetLineFromCharIndex(richTextBox1.Text.Length);
// Get array of lines:
List<string> lines = new List<string>();
for (int i = firstLine; i <= lastLine; i++)
{
int firstIndexFromLine = richTextBox1.GetFirstCharIndexFromLine(i);
int firstIndexFromNextLine = richTextBox1.GetFirstCharIndexFromLine(i + 1);
if (firstIndexFromNextLine == -1)
{
// Get character index of last character in this line:
Point pt = new Point(richTextBox1.ClientRectangle.Width, richTextBox1.GetPositionFromCharIndex(firstIndexFromLine).Y);
firstIndexFromNextLine = richTextBox1.GetCharIndexFromPosition(pt);
firstIndexFromNextLine += 1;
}
lines.Add(richTextBox1.Text.Substring(firstIndexFromLine, firstIndexFromNextLine - firstIndexFromLine));
}
// Print to richTextBox2 while debugging:
richTextBox2.Text = "";
foreach (string line in lines)
{
richTextBox2.AppendText(">> " + line + Environment.NewLine);
}
}
}

Related

How to get specific data from text file

A part of my program has the user save their record within a text document along with their name. The way the program works is that if the user submits their own name, their previous record would be brought back and reinserted into the game. For example, if my name was Justin, I would enter "Justin" into the textbox and the program looks through the textfile, and if it finds someone named Justin, then it would look at the next three lines of data and assign those lines to playerwins, computerwins, and ties respectively. However, any examples I could find either dealt with adding those specific numbers up. I was hoping someone here could point me in the right direction with how I am supposed to structure this code.
private void FileReader(string playername, int playerwins, int computerwins, int ties)
{
StreamReader outputfile;
outputfile = File.OpenText("Records.txt");
while (!outputfile.EndOfStream)
{
if (string.Compare(playername, outputfile.ReadLine()) == 0)
{
//ReadLine() and variable assigning code goes here
}
}
}
If the text file is small enough (and most of them are), I prefer to read and write all of the file's lines at once, rather than one at a time. This separates "file handling" from "score updating", rather than having them intertwined. Resources are also cleaned up automatically (with StreamReader, you have to remember to use 'using' or 'Dispose()' to ensure that resources like file handles are properly released after an error occurs).
For the code below, if playername is already in the file, then playername's scores are updated. Otherwise, playername and playername's scores are added to the end of the file. If the file does not already exist, a new file is created.
The last suggestion would be to rename the method, to something like UpdateOrInsertPlayerScores().
private void FileReader(string playername, int playerwins, int computerwins, int ties)
{
string filename = "Records.txt";
string[] lines;
// read entire text file (if any) into lines
if (File.Exists(filename))
{
lines = File.ReadAllLines(filename);
}
else
{
lines = new string[0];
}
// find playername's line (or -1 if not present)
int p = -1;
for (int i = 0; i < lines.Length; ++i)
{
if (lines[i] == playername)
{
p = i;
break;
}
}
// update (or insert) playername's scores in lines[]
if (p == -1)
{
// playername does not have scores yet; append 4 new lines representing playername's scores
List<string> newLines = new List<string>(); // copy lines[] to a List<> so we can add new lines to it
newLines.AddRange(lines);
newLines.Add(playername);
newLines.Add(playerwins.ToString());
newLines.Add(computerwins.ToString());
newLines.Add(ties.ToString());
lines = newLines.ToArray(); // copy expanded List<> back to lines[]
}
else
{
// update the 3 lines after playername's line with playername's updated scores
// verify that the 3 lines to be updated are present after the playername's line P
if ((p + 3) > (lines.Length - 1))
{
throw new Exception("Player scores file is not in the expected format.");
}
// update the 3 lines in place
lines[p + 1] = playerwins.ToString();
lines[p + 2] = computerwins.ToString();
lines[p + 3] = ties.ToString();
}
// re-write entire text file (with updated lines)
File.WriteAllLines(filename, lines);
}

c# Read lines from a file and replace with text from DataGridView Data

I am relatively new to c#, I am creating an windows application which would read all the lines from a text file. The user will input the string which needs to be replaced in Column[0] and the text with which it needs to be replaced in Column1 of the DataGridView control.
I have created two string arrays column0 and column1.
However, I am getting an error while replacing the string in line (column0, column1)
The following is my code:
string[] column0 = new string[dgvMapping.Rows.Count];
string[] column1 = new string[dgvMapping.Rows.Count];
int j = 0;
foreach(DataGridViewRow row in dgvMapping.Rows)
{
if (!string.IsNullOrEmpty(Convert.ToString(row.Cells[0].Value)))
{
column0[j] = Convert.ToString(row.Cells[0].Value);
column1[j] = Convert.ToString(row.Cells[1].Value);
j++;
}
}
var _data = string.Empty;
String[] arrayofLine = File.ReadAllLines(ofd.FileName);
using (StreamWriter sw = new StreamWriter(ofd.FileName + ".output"))
{
for (int i = 0; i < arrayofLine.Length; i++)
{
string line = arrayofLine[i];
line = line.Replace(column0[i], column1[i]);
sw.WriteLine(line);
}
}
I am using OpenFileDialog to select the file.
The Error While Executing:
You are looping around a file of unknown number of lines, and assuming that the count of lines in the grid is exactly the same as that of the file. Your code will only work if both the file and the gridView have the same number of lines.
One of the solutions, is to loop over the array of lines (as you have already did), and search for the GridViewRow in which the current line contains a key in your DGV. If this is the case, then replace all the occurences of the key by the value (obtained from the gridView) in that line, otherwise do nothing.
Check out the code below :
// Convert the row collection to a list, so that we could query it easily with Linq
List<DataGridViewRow> mySearchList = dataGridView1.Rows.Cast<DataGridViewRow>().ToList();
const int KEY_INDEX = 0; // Search index in the grid
const int VALUE_INDEX = 1; // Value (replace) index in the grid
for (int i = 0; i < arrayofLines.Length; i++)
{
string line = arrayofLines[i];
// Get data grid view Row where this line contains the key string
DataGridViewRow matchedRow = mySearchList.FirstOrDefault(obj => line.Contains(obj.Cells[KEY_INDEX].Value.ToString()));
// If this row exists, replace the key with the value (obtained from the grid)
if (matchedRow != null)
{
string key = matchedRow.Cells[KEY_INDEX].Value.ToString();
string value = matchedRow.Cells[VALUE_INDEX].Value.ToString();
line = line.Replace(key, value);
sw.WriteLine(line);
}
else
{
// Otherwise, do nothing
}
}
Stuartd is correct… there are more lines in the file than there are elements to search. I am not sure what the search is doing in a sense that it seems somewhat limited. The code appears to search for each item depending on what line it is. The searched value in column 0 and the replace value in column 1 of row 0… will only replace those values for the FIRST line in the file. The DataGridViews second row values will search/replace only the SECOND line and so on. This seems odd.
Example the two string arrays (column0 and column1) have sizes set to the number of rows in dgvMapping. Let’s say there are 5 rows in the grid, then the array sizes will be 5 strings. When you start the loop to write the strings, the loop starts at 0 and stops at the number of lines in the file. The code uses this i variable as an index into the two arrays. If there are more lines in the file, than there are rows in the grid… then you will get the error.
Again, this seems odd to do the search and replace this way. Assuming you want to search for EACH term in all the rows in column 0 and replace the found searched string with the replace string in column 1, then you will need to loop through EACH row of the grid for EACH line in the file. This will replace ALL the search/replace terms in the grid with ALL the lines in the file. If this is what you what to accomplish below is one way to achieve this, however…there are possibly better ways to accomplish this.
The code below reads the file into one big string. Then the code loops through ALL the grid rows to search/replace the strings in the big string. Hope this helps.
string bigString = File.ReadAllText(ofd.FileName);
try {
using (StreamWriter sw = new StreamWriter(ofd.FileName + ".output")) {
for (int k = 0; k < dgvMapping.Rows.Count; k++) {
if (dgvMapping.Rows[k].Cells[0].Value != null && dgvMapping.Rows[k].Cells[1].Value != null) {
string searchTerm = dgvMapping.Rows[k].Cells[0].Value.ToString();
string replaceTerm = dgvMapping.Rows[k].Cells[1].Value.ToString();
if (searchTerm != "") {
bigString = bigString.Replace(searchTerm, replaceTerm);
} else {
// one of the terms is empty
}
} else {
// one of the terms is null}
}
}
sw.WriteLine(bigString);
}
}
catch (Exception ex) {
MessageBox.Show("Write Erro: " + ex.Message);
}

Is there an equivalent to SetCursorPosition for a text stream?

when outputting to the console, you can set the specific location of the cursor and write to that (or use other nifty tricks like printing backspaces that will take you back.)
Is there a similar thing that can be done with a stream of text?
Scenario: I need to build a string with n pieces of, text where each might be on a different line and start position (or top and left padding).
Two strings might appear on the same line.
I could build a simple Dictionary<int, StringBuilder> and fidget with that, but I'm wondering if there's something like the console functionality for streams of text where you can write to a specific place (row and column).
Edit:
This is for a text only. No control.
The result might be a string with several new lines, and text appearing at different locations.
Example (where . will be white spaces):
..... txt3....... txt2
......................
................ txt1.
this will be the result of having txt1 at row 3 column (whatever), and txt2 and txt3 and row 1 with different colum values (where txt3 column < txt2 colmun)
While waiting for a better answer, here's my solution. Seems to work, been lightly tested, and can be simply pasted into linqpad and run.
void Main()
{
m_dict = new SortedDictionary<int, StringBuilder>();
AddTextAt(1,40, "first");
AddTextAt(2,40, "xx");
AddTextAt(0,10, "second");
AddTextAt(4,5, "third");
AddTextAt(1,15, "four");
GetStringFromDictionary().Dump();
}
// "global" variable
SortedDictionary<int, StringBuilder> m_dict;
/// <summary>
/// This will emulate writting to the console, where you can set the row/column and put your text there.
/// It's done by having Dictionary(int,StringBuilder) that will use to store our data, and eventually,
/// when we need the string iterate over it and build our final representation.
/// </summary>
private void AddTextAt(int row, int column, string text)
{
StringBuilder sb;
// NB: The following will initialize the string builder !!
// Dictionary doesn't have an entry for this row, add it and all the ones before it
if (!m_dict.TryGetValue(row, out sb))
{
int start = m_dict.Keys.Any() ? m_dict.Keys.Last() +1 : 0;
for (int i = start ; i <= row; i++)
{
m_dict.Add(i, null);
}
}
int leftPad = column + text.Length;
// If dictionary doesn't have a value for this row, just create a StringBuilder with as many
// columns as left padding, and then the text
if (sb == null)
{
sb = new StringBuilder(text.PadLeft(leftPad));
m_dict[row] = sb;
}
// If it does have a value:
else
{
// If the new string is to be to the "right" of the current text, append with proper padding
// (column - current string builder length) and the text
int currrentSbLength = sb.ToString().Length;
if (column >= currrentSbLength)
{
leftPad = column - currrentSbLength + text.Length;
sb.Append(text.PadLeft(leftPad));
}
// otherwise, text goes on the "left", create a new string builder with padding and text, and
// append the older one at the end (with proper padding?)
else
{
m_dict[row] = new StringBuilder( text.PadLeft(leftPad)
+ sb.ToString().Substring(leftPad) );
}
}
}
/// <summary>
/// Concatenates all the strings from the private dictionary, to get a representation of the final string.
/// </summary>
private string GetStringFromDictionary()
{
var sb = new StringBuilder();
foreach (var k in m_dict.Keys)
{
if (m_dict[k]!=null)
sb.AppendLine(m_dict[k].ToString());
else
sb.AppendLine();
}
return sb.ToString();
}
Output:
second
four first
xx
third
No. Text files don't really have concept of horizontal/vertical position, so you'd need to build some sort of positioning yourself.
For basic positioning tabs ("\t") may be enough, for anything more advanced you'd need to fill empty space with spaces.
It sounds like you have some sort of table layout - it may be easier to build data in cells first (List<List<string>> - list of rows consisting of columns of strings) and than format it with either String.Format("{0}\t{1}\t...", table[row][0],table[row][1],...) or manually adding necessary amount of spaces for each "cell"

Next textbox line

I have a multi-line textbox of sequences, which the game will play one after the other. For example, the textbox may contain this:
RGBY
YGBR
RGBB
I understand that to read the first line of a multi-line textbox, I must write this:
First sequence:
textBox1.Lines[0].Length //Reads first line only for sequence 1
But how can I make it read the next line in a general sense? n+1 where n is the previous line.
New sequence:
textBox1.Lines[0 + 1].Length //Go to next line for future sequences
Any help is appreciated. Thank you in advance!
You need to store the current index in a variable, a field or property in your class.
private int CurrentIndex { get; set; }
Now you can iterate all lines, for example in a button-click event handler where you want to advance to the next line until end:
if (CurrentIndex + 1 < textBox1.Lines.Length)
{
string currentLine = textBox1.Lines[++CurrentIndex];
}
for(int i=0; i < textBox1.Lines.Count(); i++)
{
var currentLine = textBox1.Lines[i];
// do what you want with current line
}

Using C#, how do I read a text file into a matrix of characters and then query that matrix? Is this even possible?

Example
If I had a text file with these lines:
The cat meowed.
The dog barked.
The cat ran up a tree.
I would want to end up with a matrix of rows and columns like this:
0 1 2 3 4 5 6 7 8 9
0| t-h-e- -c-a-t- -m-e-o-w-e-d-.- - - - - - - -
1| t-h-e- -d-o-g- -b-a-r-k-e-d-.- - - - - - - -
2| t-h-e- -c-a-t- -r-a-n- -u-p- -a- -t-r-e-e-.-
Then I would like to query this matrix to quickly determine information about the text file itself. For example, I would quickly be able to tell if everything in column "0" is a "t" (it is).
I realize that this might seem like a strange thing to do. I am trying to ultimately (among other things) determine if various text files are fixed-width delimited without any prior knowledge about the file. I also want to use this matrix to detect patterns.
The actual files that will go through this are quite large.
Thanks!
For example, I would quickly be able to tell if everything in column "0" is a "t" (it is).
int column = 0;
char charToCheck = 't';
bool b = File.ReadLines(filename)
.All(s => (s.Length > column ? s[column] : '\0') == charToCheck);
What you can do is read the first line of your text file and use it as a mask. Compare every next line to the mask and remove every character from the mask that is not the same as the character at the same position. After processing al lines you'll have a list of delimiters.
Btw, code is not very clean but it is a good starter I think.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace DynamicallyDetectFixedWithDelimiter
{
class Program
{
static void Main(string[] args)
{
var sr = new StreamReader(#"C:\Temp\test.txt");
// Get initial list of delimiters
char[] firstLine = sr.ReadLine().ToCharArray();
Dictionary<int, char> delimiters = new Dictionary<int, char>();
for (int i = 0; i < firstLine.Count(); i++)
{
delimiters.Add(i, firstLine[i]);
}
// Read subsequent lines, remove delimeters from
// the dictionary that are not present in subsequent lines
string line;
while ((line = sr.ReadLine()) != null && delimiters.Count() != 0)
{
var subsequentLine = line.ToCharArray();
var invalidDelimiters = new List<int>();
// Compare all chars in first and subsequent line
foreach (var delimiter in delimiters)
{
if (delimiter.Key >= subsequentLine.Count())
{
invalidDelimiters.Add(delimiter.Key);
continue;
}
// Remove delimiter when it differs from the
// character at the same position in a subsequent line
if (subsequentLine[delimiter.Key] != delimiter.Value)
{
invalidDelimiters.Add(delimiter.Key);
}
}
foreach (var invalidDelimiter in invalidDelimiters)
{
delimiters.Remove(invalidDelimiter);
}
}
foreach (var delimiter in delimiters)
{
Console.WriteLine(String.Format("Delimiter at {0} = {1}", delimiter.Key, delimiter.Value));
}
sr.Close();
}
}
}
"I am trying to ultimately (among other things) determine if various text files are fixed-width (...)"
If that's so, you could try this:
public bool isFixedWidth (string fileName)
{
string[] lines = File.ReadAllLines(fileName);
int length = lines[0].Length;
foreach (string s in lines)
{
if (s.length != Length)
{
return false;
}
}
return true;
}
Once you get that lines variable, you can access any character as though they were in a matrix. Like char c = lines[3][1];. However, there is no hard guarantee that all lines are the same length. You could pad them to be the same length as the longest one, if you so wanted.
Also,
"how would I query to get a list of all columns that contain a space character for ALL rows (for example)"
You could try this:
public bool CheckIfAllCharactersInAColumnAreTheSame (string[] lines, int colIndex)
{
char c = lines[0][colIndex];
try
{
foreach (string s in lines)
{
if (s[colIndex] != c)
{
return false;
}
}
return true;
}
catch (IndexOutOfRangeException ex)
{
return false;
}
}
Since it's not clear where you're have difficulty exactly, here are a few pointers.
Reading the file as strings, one per line:
string[] lines = File.ReadAllLines("filename.txt");
Obtaning a jagged array (a matrix) of characters from the lines (this step seems unnecessary since strings can be indexed just like character arrays):
char[][] charMatrix = lines.Select(l => l.ToCharArray()).ToArray();
Example query: whether every character in column 0 is a 't':
bool allTs = charMatrix.All(row => row[0] == 't');

Categories