Is there an equivalent to SetCursorPosition for a text stream? - c#

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"

Related

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);
}

reading separate lines of data in a grid using c#

Given a data text file which looks like
21,7,11
20,10,12
17,7,18
These represent height, temperature and carbon percentage.
I have read in the file as a .txt file using system.io. Is this correct? from here how would I calculate the maximum temperature?
{
string s;
System.IO.StreamReader inputFile = new System.IO.StreamReader(DataFile);
s = inputFile.ReadLine();
int noDataLines = int.Parse(s);
}
You need to read all the lines and compare each value to find out max temperature.
Something like below (untested code!) should be done. There are lot of assumptions in this code and you may have to change it to suit your case.
{
string s;
int maxValue=-1, temp=-1;
using(System.IO.StreamReader in = new System.IO.StreamReader(DataFile))
{
while (in.Peek() >= 0)
{
s = in.ReadLine();
if(int.tryParse(s.split(",")[1], out temp)
{
if(temp>maxValue)
maxValue = temp;
}
}
}
}
You will most likely want to create a two-dimensional list or array, and in this example I am using a list.
{
List<List<int>> intList = new List<int>(); // This creates a two dimensional list.
System.IO.StreamReader inputFile = new System.IO.StreamReader(DataFile);
string line = inputFile:ReadLine();
while (line != null) // Iterate over the lines in the document.
{
intList.Add( // Adding a new row to the list.
line.Split(',').Select(int.Parse).ToList()
// This separates the line by commas, and turns it into a list of integers.
);
line = inputFile:ReadLine(); // Move to the next row.
}
}
I will admit that this is certainly not a very concise method of doing it, but it is relatively straightforward.
To access it, do this:
int element = intList[1, 2]; // Accessing 2nd row, 3rd column.

Extracting digits from a string five at a time in C#

I am attempting to take numbers (characters 0-9) in from a file and store them in memory.
Lets say we have a string called "register" (and can only (must) hold 5 chars max) and the register string will take in numbers that are read from the file so for example:
File1.txt:
The house number is 10 and the price is 4000 and 3.
So the register would be filled with the following: "10400"
Some logic would then be performed against the string and then the first char would be removed from string and everything would shift 1 to the left and another char (number) from the file would be added e.g.:
04000
and then...
40003
Hopefully somebody could shed some light on this and provide some ways of achieving this :)
Well, if you want to lop the first character off a string and add on one at the end, you can just say:
string s = "10400";
string t = s.Substring(1) + "0";
This gives t = "04000". Repeating:
string u = t.Substring(1) + "3";
This gives u = "40003".
So, what more do you want? Figuring out the logic of what to add to the end is your job.
OK...
First, a FileStream and associated StreamReaders will allow you to read from the file in pretty much any format you desire. This will be important because your specific algorithm will determine the retrieval method.
Boiling it down, you want to read characters from the file, and when that character is a number, store it in the register, continuing in this manner until you have five number characters in the register. Then, you'll do some logic that results in the first number no longer being useful, so you truncate it and get the next value.
How about something along these lines?
var register = new StringBuilder();
using(var stream = File.Open("File1.txt"))
{
bool ended, fileEnded;
int buffer;
while(!ended)
{
while(register.Length < 5 && !fileEnded)
{
buffer = stream.ReadByte();
if(buffer == -1)
{
fileEnded = true;
break;
}
var myChar = (char)buffer;
if(Char.IsNumber(myChar))
StringBuilder.Append(myChar);
}
//at this point you have 5 characters in register (or have run out of file).
//perform your logic, then remove the front character
register.Remove(0,1);
//repeat the loop. You won't get any more new characters once you reach the end of file,
//but the main loop will keep running until you set ended to true
if(WereDone())
ended=true;
}
stream.Close();
}
You could also read the entire file into a string variable, then apply a Regex that will find number characters, concatenate those into a large buffer, then fill your Register from that. That is a better approach for a small file, but this one will work for any file size.
You can create an extension method to List like so:
static class Helper
{
public static void Push<T>(this List<T> list, T item)
{
if (list.Count == 5)
list.RemoveAt(0);
list.Add(item);
}
}
And then you can use it like:
List<char> queue = new List<char>(5);
queue.Push('1');
queue.Push('0');
queue.Push('4');
queue.Push('0');
queue.Push('0');
Subsequent Call to Push will remove the first char and add the last
queue.Push('1');
I would likely put a method for fetching the correct string into a value. See below for an example:
static string FetchRegister(string Source, int Max, int StartIndex)
{
string Register = string.Empty;
int RegisterIndex = 0;
for (int i = 0; i < Source.Length; i++)
{
if (char.IsNumber(Source[i]))
{
if (RegisterIndex >= StartIndex)
{
Register += Source[i].ToString();
if (Register.Length == Max)
{
return Register;
}
}
RegisterIndex += 1;
}
}
return Register;
}

Working on one word, dilemma using string or char array or stringbuilder?

I am working on a chess(not grid). This chess(not grid) has X rows. In each of rows we have Y blocks.
I would like to know what words can I find in each of the rows.
Also what is that's words start and stop indexes.
So my tactic for one row is:
take all items from row (-cat--dog----) and save it to string
operate on string's indexes using for(...){...}
Is it good idea or maybe I should convert that string to char array and then operate?
What method is faster to do that? What about StringBuilder?
#Oden thx for correction but I didn't mean grid but just a chess :)
Somewhere I wrote this question wrong. (Maybe I wrong formulated the question. It's hard to make this shape for me, so sorry.)
The main problem's question was simply included in main title of this topic. What method would be faster while processing over words for example mamma--mia (each element is ansi symbol, let say in string format)?
So I wonder if I were working on just a string it would be faster than first converting string toCharArray and work then. Or maybe using stringBuilder.
I simply ask what is faster: work or chars, strings or elements of string builder in my method.. :( What would be faster in big function/method -> that's the question.
The performance bottleneck you're facing is not in the string construction, but in finding the words you're looking for. If you have m words you're looking for, you might end up going through the string looking for words m times. That's not necessary! You might try and create a huge regular expression * matching all substrings you're looking for. The way they are constructed *, they only pass the string once (basically if you start with an 'a', they're in a state that tells them "this could be a beginning to all the words beginning with a", if the next char is a 'b', state says "this could either be the beginning of a word starting with ab or the beginning of a word starting with b).
http://www.regular-expressions.info/dotnet.html
http://en.wikipedia.org/wiki/Finite-state_machine
I would suggest
1) create an object to contain the points,
2) modularize the portions to parse the string (preferably in a class, but a method would do),
3) then use LINQ to obtain all of the rows the the values for the column you seek
An example is as follows:
void Main()
{
const string columnName = "ColumnYouSeek";
var dgv = GetDataGridView(columnName);
var items = GetItems(dgv, columnName);
// items now contains what you want
}
Create an object to hold your values
class ItemWithIndex
{
public string Text { get; set; }
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public override string ToString()
{
return string.Format(
"{0}: Starts at {1}, Ends at {2}",
Text, StartIndex, EndIndex);
}
}
Change to below to point to your DataGridView
public System.Windows.Forms.DataGridView GetDataGridView(string columnName)
{
var dgv = new System.Windows.Forms.DataGridView();
var column = new System.Windows.Forms.DataGridViewTextBoxColumn();
column.DataPropertyName = columnName;
column.Name = columnName;
dgv.Columns.Add(column);
dgv.Rows.Add("-cat--dog----");
dgv.Rows.Add("--elephant----mouse----");
return dgv;
}
Modularize to get Items
public IEnumerable<ItemWithIndex> GetItems(
System.Windows.Forms.DataGridView dgv, string columnName)
{
var rows = dgv.Rows.Cast<System.Windows.Forms.DataGridViewRow>();
var rowData = rows.Select(x => (x.Cells[columnName].Value ?? "").ToString());
return rowData.SelectMany(x => GetWords(x));
}
Modularize the portions to get the individual words
Note: This can be unit tested and refactored
public IEnumerable<ItemWithIndex> GetWords(string val)
{
var index = val.IndexOf("-");
while (index >= 0 && index + 1 < val.Length)
{
var startIndex = index + 1;
var endIndex = val.IndexOf("-", startIndex);
if (endIndex < 0)
endIndex = val.Length - 1; // end of string
var text = val.Substring(startIndex, endIndex - startIndex);
index = endIndex;
if (string.IsNullOrEmpty(text))
continue;
yield return new ItemWithIndex
{
Text = text,
StartIndex = startIndex,
EndIndex = endIndex,
};
}
}
Looks like you should be using either a two dimensional array of strings - string[,] or a jagged array (depending if each row has the same amount of blocks or not).
Another option is to use a generic list of lists - List<List<string>>.
Either option lets you query your data by index directly.

Retrieving an accurate string array from a RichTextBox control

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);
}
}
}

Categories