MODI MiSelectRects gets coordinates wrong - c#

I have a Windows Form app that, when executed, launches Firefox, grabs the process and handle of the window, and does a screen capture of Firefox, saves it to disk (temp.bmp) and calls ProcessGetWindow. I'm basically using MiSelectRects in MODI to capture the rectangle around the word that I'm looking for, and then I use AutoIT to mouse click on the word.
The problem is that my coordinates are off by about 10 pixels from the top.
Any ideas what might be wrong? Here's the function that does the processing. I have commented out the AutoIT processing, and I'm just debugging with a MessageBox to show me the actual coordinates. I then confirm with AutoIT's Window Info tool and it's definitely off... am I doing something wrong or is there something screwed up with MODI?
public void ProcessGetWindow(Bitmap image)
{
Document modiDoc = null;
MiDocSearch modiSearch = null;
IMiSelectableItem modiTextSel = null;
MiSelectRects modiSelectRects = null;
MiSelectRect modiSelectRect = null;
MiRects modiRects = null;
int intSelInfoPN;
string intSelInfoTop;
int intSelInfoBottom;
string intSelInfoLeft;
int intSelInfoRight;
// Load an existing image file.
modiDoc = new Document();
modiDoc.Create(#"C:\\temp.bmp");
// Perform OCR.
modiDoc.Images[0].OCR();
// Search for the selected word.
modiSearch = new MiDocSearch();
modiSearch.Initialize(modiDoc, "Click Me", 0, 0, false, false);
modiSearch.Search(null, ref modiTextSel);
try
{
modiSelectRects = modiTextSel.GetSelectRects();
}
catch (COMException)
{
MessageBox.Show("Me thinks that the OCR didn't work right!");
}
foreach (MiSelectRect mr in modiSelectRects)
{
//intSelInfoPN = mr.PageNumber.ToString();
intSelInfoTop = mr.Top.ToString();
//intSelInfoBottom = mr.Bottom;
intSelInfoLeft = mr.Left.ToString();
//intSelInfoRight = mr.Right;
/*AutoItX3 auto = new AutoItX3();
auto.AutoItSetOption("MouseCoordMode", 2);
auto.MouseClick("", intSelInfoLeft, intSelInfoTop, 1, 80);*/
MessageBox.Show("Coordinates: " + intSelInfoLeft + ", " + intSelInfoTop, "Coordinates", MessageBoxButtons.OK);
}
//string textResult = modiTextSel.Text;
//MessageBox.Show(textResult, "Search Results", MessageBoxButtons.OK);
// Close this dialog.
Application.Exit();
}

I am using the same program to find the location.
int centerwidth = (intSelInfoRight - intSelInfoLeft)/2;
centerwidth = intSelInfoLeft + centerwidth;
int centerheight = (intSelInfoBottom - intSelInfoTop)/2;
centerheight = centerheight + intSelInfoTop;
u can find the exact middle point of the text using it.
But this programs always gives the location of the 1st occurence of the word and not for the next occurences. Please let me know how to find the location of the text at all occurences.

I'm not familiar with the tools presented, but from what I read the GetSelectRects function returns a bounding rectangle, that is the smallest rectangle that contains the whole selection, in this case the word you searched for. I believe what happens is that you're clicking the corner of the bounding rectangle instead of in the middle, where the word is.
Calculate the co-ordinates for the center of the rectangle and try clicking that:
int height = mr.Bottom - mr.Top;
int width = mr.Right - mr.Left;
AutoItX3 auto = new AutoItX3();
auto.AutoItSetOption("MouseCoordMode", 2);
auto.MouseClick("", width/2, height/2, 1, 80);

MODI.Document modiDoc = null;
MODI.MiDocSearch modiSearch = null;
MODI.IMiSelectableItem modiTextSel = null;
MODI.MiSelectRects modiSelectRects = null;
MODI.MiSelectRect modiSelectRect = null;
MODI.MiRects modiRects = null;
int intSelInfoPN;
int intSelInfoTop;
int intSelInfoBottom;
int intSelInfoLeft;
int intSelInfoRight;
// Load an existing image file.
modiDoc = new MODI.Document();
modiDoc.Create(#"C:\Users\h117953\Desktop\OCR\1.jpg");
// Perform OCR.
//modiDoc.Images[0].OCR();
//MODI.Image image = (MODI.Image)modiDoc.Images[0];
modiDoc.OCR(MiLANGUAGES.miLANG_ENGLISH);
MODI.Image modiImage = (modiDoc.Images[0] as MODI.Image);
//string ocrtext = #"C:\Users\h117953\Desktop\OCR\Sample.txt";
//File.WriteAllText(ocrtext, modiImage.Layout.Text);
// Search for the selected word.
//int wordindex
modiSearch = new MODI.MiDocSearch();
//date to search
modiSearch.Initialize(modiDoc, "Deer", 0, 2, false, false);
modiSearch.Search(null, ref modiTextSel);
if (modiTextSel == null)
{
Response.Write("\nText not found \n");
}
else
{
Response.Write("\nText is found \n");
try
{
modiSelectRects = modiTextSel.GetSelectRects();
}
catch (Exception)
{
Response.Write("Me thinks that the OCR didn't work right!");
}
int centerwidth = 0;
int centerheight = 0;
foreach (MODI.MiSelectRect mr in modiSelectRects)
{
//intSelInfoPN = mr.PageNumber.ToString();
intSelInfoTop = mr.Top;
intSelInfoBottom = mr.Bottom;
intSelInfoLeft = mr.Left;
intSelInfoRight = mr.Right;
// MessageBox.Show("Coordinates: " + intSelInfoLeft + ", " + intSelInfoTop, "Coordinates", MessageBoxButtons.OK);
// MessageBox.Show("Coordinates: " + intSelInfoRight + ", " + intSelInfoBottom, "Coordinates", MessageBoxButtons.OK);
centerwidth = (intSelInfoRight - intSelInfoLeft) / 2;
centerwidth = intSelInfoLeft + centerwidth;
centerwidth = (intSelInfoBottom - intSelInfoTop) / 2;
centerheight = centerheight + intSelInfoTop;
//MessageBox.Show("Coordinates: " + centerwidth + ", " + centerheight, "Coordinates", MessageBoxButtons.OK);
Response.Write("the Widht and Height co-ordinates are (Width,Height)= ({0},{1})" + centerwidth + ","+ centerheight);
}

Related

Read lines from a file and write to multiple PDFs doesn't work

So I am trying to read a file line by line and write them to multiple PDFs, each PDF file should only contain 50 lines, if one PDF is full then another will be created until all lines are finished.
I almost finished it. But the problem is every time only the first PDF has contents. The others are all empty. Here is my code.
if ((System.IO.File.Exists(#"C:\Coil master 28-02-2019.csv")) == true)
{
int lineNumber = 1;
int PDFNumber = 0;
for (; PDFNumber < 4; PDFNumber++)
{
string path = Application.StartupPath;
var pgSize = new iTextSharp.text.Rectangle(2976, 4194);
Document pdfdoc = new Document(pgSize); // Setting the page size for the PDF
PdfWriter writer = PdfWriter.GetInstance(pdfdoc, new FileStream(path + "/MasterCoils/" + (1 + (50 * PDFNumber)) + "-" + (50 + (PDFNumber * 50)) + ".pdf", FileMode.Create)); //Using the PDF Writer class to generate the PDF
pdfdoc.Open(); // Opening the PDF to write the data from the textbox
foreach (string line in System.IO.File.ReadLines(#"C:\Coil master 28-02-2019.csv").Skip(1 + (50 * PDFNumber)).Take(50))
{
string[] inputArray = line.Split(delimiters); // split the input string by using the delimiter ','
COILID = inputArray[0];
TYPE = inputArray[1];
COLOR = inputArray[2];
WEIGHT = Int32.Parse(inputArray[3]);
GAUGE = inputArray[4];
WIDTH = inputArray[5];
PdfContentByte cb = writer.DirectContent;
// we tell the ContentByte we're ready to draw text
cb.BeginText();
// set up Font and Size for Content to be shown in PDF
BaseFont mybf = BaseFont.CreateFont(BaseFont.HELVETICA_BOLD, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
cb.SetFontAndSize(mybf, 15);
cb.SetTextMatrix(250 + (595 * i), (419 * (9 - j)) + 280);
cb.ShowText(COILID + "+" + TYPE + "+" + COLOR + "+" + WEIGHT + "+" + GAUGE + "+" + WIDTH);
// we tell the contentByte, we've finished drawing text
cb.EndText();
lineNumber++;
}
pdfdoc.Close();
}
}
If I delete the foreach statement, then all PDFs would have some contents. So I guess the problem is from the foreach, but I couldn't find exactly where is wrong. Please give me some help. Thanks!
I ran this code and it works exactly as expected, reads a csv and makes 4 new PDF's all containing data.
The only thing I really changed was the way the PDF's were being written to.
I am not an ITextSharp expert but I hope this helps you.
if (File.Exists(#"Coil master 28-02-2019.csv") == true)
{
int fileNumber = 0;
for (; fileNumber < 4; fileNumber++)
{
var pgSize = new Rectangle(2976, 4194);
var pdfdoc = new Document(pgSize); // Setting the page size for the PDF
pdfdoc.Open(); // Opening the PDF to write the data from the textbox
foreach (string line in File.ReadLines("Coil master 28-02-2019.csv").Skip(1 + (50 * fileNumber)).Take(50))
{
string[] inputArray = line.Split(','); // split the input string by using the delimiter ','
var COILID = inputArray[0];
var TYPE = inputArray[1];
var COLOR = inputArray[2];
var WEIGHT = Int32.Parse(inputArray[3]);
var GAUGE = inputArray[4];
var WIDTH = inputArray[5];
var p = new Paragraph(COILID + "+" + TYPE + "+" + COLOR + "+" + WEIGHT + "+" + GAUGE + "+" + WIDTH);
pdfdoc.Add(p);
}
pdfdoc.Close();
}
}

Splitting String from Settings

I'm trying to read out the contents off a Setting inside my Application. Below is the code i'm having troubles with:
private bool checkGrid()
{
string playlists = Spotify_Extender.Properties.Settings.Default.Playlists;
MessageBox.Show(playlists);
string[] split1;
if (playlists.Contains(";"))
{
MessageBox.Show("Multiple Links");
split1 = playlists.Split(';');
}
else
{
MessageBox.Show("One Link");
split1 = new string[1];
split1[0] = playlists;
}
MessageBox.Show("Array Length: " + split1.Length);
int lines = 0;
for (int i = 0; i < split1.Length; i++)
{
MessageBox.Show("Check #" + i + " - " + split1[i] + " - Length: " + split1[i].Length);
if (split1[i].Length >= 22)
{
MessageBox.Show(i + " - " + split1[1]);
lines++;
}
}
int rows = this.playlistGrid.Rows.Count;
MessageBox.Show(lines + "");
if (rows == lines)
return true;
return false;
}
The code should be easy to understand and it should work as far as i am aware, but it doesn't. I entered this in my Setting:
If i run the program now, my first MessageBox prints out exactly what i entered, the second one prints out "One Link" and the third prints "Array Length: 1". Now we get to the part i'm having troubles with. The next Message is this:
So the length of the text is 22 as displayed in the MessageBox, but down below this statement isn't true:
if (split1[i].Length >= 22)
I'm really confused by this and it also does this when i check this:
if (split1[i] != "")
Any help is appreciated, because i don't know what to do, since my code should be fine. Thanks for your time!
You should have split[i] and not split[1]

Unable to plot multiple lines in Fastline chart

I am trying to have a Fastline Chart (type chosen as I get lots of point to plot). I want to plot in the data I just got plus an average on the same chart. Should be easy but I keep getting an error "An unhandled exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll".
The function is below, if I comment out the second series called [AverageTime"] I compile and work with one line.
Please note the function is async (.net 4.5) which I use for "await Task.Delay(Int32.Parse(txtbx_timing_delay.Text));". Not sure if this has anything to do with it as I presume I have a thread I am waiting for.
Any ideas on how to get round the issue?
private async void btn_timing_send_Click(object sender, EventArgs e)
{
int recursive_counter = 0, num = 0, num2 = 0;
double total_time = 0, average_time = 0, min_time = 0, max_time = 0, ave_plot = 0;
num2 = Int32.Parse(txtbx_iterations.Text); // number of required iterations
/*
* Just a word on timings - Windows is bad at doing timings in lower Microseconds and below so we need to do a few things to try and get
* accurate timings.
*
* As the general rule, when testing comms with completion time in the range of several microseconds it's recommended to implement
* the loop running 10,000...100,000 iterations in order to increase the accuracy of the measurement
*/
string str1 = "";
Stream stm = tcpclnt.GetStream();
byte[] payload;
string ID = txtbx_timing_send_data.Text;
str1 = "No Connection";
ID = ID.ToCharArray().Aggregate("", (result, c) => result += ((!string.IsNullOrEmpty(result) && (result.Length + 1) % 3 == 0) ? " " : "") + c.ToString());//add space every two chars to make hex
payload = ID.Split().Select(s => Convert.ToByte(s, 16)).ToArray(); //split the bytes into the array
if (ckbx_plot.Checked)
{
chrt_timings.Series["ResponseTimes"].ChartType = SeriesChartType.FastLine; //set type
chrt_timings.Series["ResponseTimes"].Color = Color.Blue; //set colour
chrt_timings.Series["AverageTime"].ChartType = SeriesChartType.FastLine; //set type
// chrt_timings.Series["AverageTime"].Color = Color.Red; //set colour
// chrt_timings.Series["AverageTime"].BorderDashStyle = ChartDashStyle.Dash;
chrt_timings.Legends.Clear(); // We do not need a legend
chrt_timings.ChartAreas[0].AxisX.IsMarginVisible = false;
}
do
{
try
{
Stopwatch timer = Stopwatch.StartNew();
long frequency = Stopwatch.Frequency;
long nanosecPerTick = (1000L * 1000L * 1000L) / frequency;
long startTick = timer.ElapsedTicks; //start of timed section
stm.Write(payload, 0, payload.Length);
byte[] input = new byte[tcpclnt.ReceiveBufferSize];
int k = stm.Read(input, 0, tcpclnt.ReceiveBufferSize);
Array.Resize(ref input, k); //crop the array to the amount of items it read in
str1 = string.Join(" ", input.Select(b => string.Format("{0:X2} ", b))); //format as hex bytes
long stopTick = timer.ElapsedTicks; //end of timed section
var timestamp = Convert.ToDouble((stopTick - startTick) * nanosecPerTick) / 1000000;
timer.Reset();
rchtxbox_timings.SelectionColor = Color.LimeGreen;
rchtxbox_timings.AppendText(str1 + "\r");
rchtxbox_timings.SelectionColor = Color.Yellow;
rchtxbox_timings.AppendText(timestamp + "ms\r\r");
rchtxbox_timings.ScrollToCaret();
if (num == 0) min_time = timestamp;
if (num2 > 1)
{
total_time = total_time + timestamp;
if (max_time < timestamp) max_time = timestamp;
if (min_time > timestamp) min_time = timestamp;
}
if (chkBx_LogData.Checked)
{
using (StreamWriter sw = new StreamWriter(timing_filename, true))
{
str1 = str1.Replace(" ", ""); //take out the spaces
sw.WriteLine(str1 + "," + timestamp.ToString() + "\r");
}
}
//Plot graph if required
if (ckbx_plot.Checked)
{
ave_plot = timestamp / num;
if (ckbx_restrict_graph.Checked)
{
if (chrt_timings.Series["ResponseTimes"].Points.Count() >= Convert.ToInt16(txtbx_axispoints.Text)) chrt_timings.Series["ResponseTimes"].Points.RemoveAt(0);
chrt_timings.Series["ResponseTimes"].Points.Add(timestamp);
chrt_timings.Series["ResponseTimes"].ToolTip = timestamp.ToString();
// if (chrt_timings.Series["AverageTime"].Points.Count() >= Convert.ToInt16(txtbx_axispoints.Text)) chrt_timings.Series["Average Time"].Points.RemoveAt(0);
// chrt_timings.Series["AverageTime"].Points.Add(ave_plot);
chrt_timings.ResetAutoValues();
}
else
{
recursive_counter++;
chrt_timings.Series["ResponseTimes"].Points.AddXY(recursive_counter, timestamp);
chrt_timings.Series["ResponseTimes"].ToolTip = timestamp.ToString();
// chrt_timings.Series["AverageTime"].Points.AddXY(ave_plot, timestamp);
}
}
num = num + 1;
timestamp = 0;
await Task.Delay(Int32.Parse(txtbx_timing_delay.Text));
}
catch (Exception d)
{
rchTxtBx_output.AppendText("red..... " + d.StackTrace);
}
} while (num2 > num);
if (num2 > 1)
{
//write out min, max and ave times
average_time = total_time / num;
rchtxbox_timings.SelectionColor = Color.LightBlue;
rchtxbox_timings.AppendText("\rMinimum Time = " + min_time + "\r");
rchtxbox_timings.SelectionColor = Color.LightBlue;
rchtxbox_timings.AppendText("Maximum Time = " + max_time + "\r");
rchtxbox_timings.SelectionColor = Color.LightBlue;
rchtxbox_timings.AppendText("Average Time = " + average_time + "\r\r");
rchtxbox_timings.ScrollToCaret();
}
}
Thanks for help, after much head scratching I solved the issue. I missed one small point and that was I did not have the second series, Series["AverageTime"], declared as a member of the charting canvas.
To fix this I went to the Properties then click on collection and Add the member, AverageTime. Once that was done I could draw its points to the Chart Canvas.
So now I initialise the lines below
if (ckbx_plot.Checked)
{
chrt_timings.Series["ResponseTimes"].ChartType = SeriesChartType.FastLine; //set type
chrt_timings.Series["ResponseTimes"].Color = Color.Blue; //set colour
chrt_timings.Series["AverageTime"].ChartType = SeriesChartType.FastLine; //set type
chrt_timings.Series["AverageTime"].Color = Color.Red; //set colour
chrt_timings.Series["AverageTime"].BorderDashStyle = ChartDashStyle.Dash;
chrt_timings.Legends.Clear(); // We do not need a legend
chrt_timings.ChartAreas[0].AxisX.IsMarginVisible = false;
}
and now I add the data
if (ckbx_restrict_graph.Checked)
{ //shows just set number of points, add a new point remove an old point
if (chrt_timings.Series["ResponseTimes"].Points.Count() >= Convert.ToInt16(txtbx_axispoints.Text)) chrt_timings.Series["ResponseTimes"].Points.RemoveAt(0);
chrt_timings.Series["ResponseTimes"].Points.Add(timestamp);
chrt_timings.Series["ResponseTimes"].ToolTip = timestamp.ToString();
if (chrt_timings.Series["AverageTime"].Points.Count() >= Convert.ToInt16(txtbx_axispoints.Text)) chrt_timings.Series["Average Time"].Points.RemoveAt(0);
chrt_timings.Series["AverageTime"].Points.Add(ave_plot);
chrt_timings.ResetAutoValues();
}
else
{ //squash all points onto the same canvas
recursive_counter++;
chrt_timings.Series["ResponseTimes"].Points.AddXY(recursive_counter, timestamp);
chrt_timings.Series["ResponseTimes"].ToolTip = timestamp.ToString();
chrt_timings.Series["AverageTime"].Points.AddXY(ave_plot, timestamp);
}

C# losing Font Style in RichTextBox after deleting lines

I have a RichTextBox for a simple chat where I add lines programmatically.
I make the usernames bold and the messages in regular style.
After some lines I want to delete the first lines to keep the chat in a acceptably length. But when I do so I lose the text format and everything appears bold. What am I doing wrong and how can I fix this?
EDIT
I could solve the problem where I wasn't able to delete the first line.
I had to set the the ReadOnly property to false. Even though I was able to add new lines it prevented deleting lines. So the following code works to delete lines. Thanks to #TaW!
if (ChatText.Lines.Length >= 10)
{
int p = 0; int count = 0;
do
{
p = ChatText.Text.IndexOf("\n\r");
if (p >= 0)
{
ChatText.SelectionStart = p;
ChatText.SelectionLength = 2; // length of "\n\r"
ChatText.SelectedText = "\n";
count++;
}
}
while(p >= 0);
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = ChatText.Lines.Take(0).Select(x => x.Length + nll).Sum() - nll;
int pL = ChatText.Lines.Take(1).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
ChatText.SelectionStart = pS;
ChatText.SelectionLength = pL - pS;
ChatText.Cut();
}
//////////////////////////////////
// now add new lines
//////////////////////////////////
string[] chatstr;
// string text is given as method parameter
chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// go to the end of the text
ChatText.SelectionStart = ChatText.Text.Length;
ChatText.SelectionLength = 0;
// make text bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
// add username (chatstr[0]) and colon
ChatText.AppendText(chatstr[0] + ": ");
// make text regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
// add message (chatstr[1])
ChatText.AppendText(chatstr[1] + "\n");
// and finaly scroll down
ChatText.ScrollToCaret();
So deleting lines works and new lines are added as intended. Finaly!
solved :)
Never change the Text of a RichtTextBox if it contains any formatting.
Changing the Lines property (by Skip) is just another way to change the Text.
Instead only use the functions the RTB provides: Always start by selecting the portion you want to format, then apply one or more of the functions and/or set one or more of the properties..:
To delete portions use Cut.
Here is a function that will delete a number of entire lines:
void DeleteLines(RichTextBox rtb, int fromLine, int count)
{
int p1 = rtb.GetFirstCharIndexFromLine(fromLine);
int p2 = rtb.GetFirstCharIndexFromLine(fromLine + count);
rtb.SelectionStart = p1;
rtb.SelectionLength = p2 - p1;
bool readOnly = rtb.ReadOnly; // allow change even when the RTB is readonly
rtb.ReadOnly = false; ;
rtb.Cut();
rtb.ReadOnly = readOnly;
}
Trying to keept the formatting alive yourself is a tedious and error-prone waste of your time.
In addition to font properties you would also have to resore all other things you can set with the SelectedXXX properties, like colors, alignment, spacing etc etc..
To delete the first 3 lines use:
DeleteLines(yourRTB, 0, 3);
To restrict the text to 10 lines use:
DeleteLines(yourRTB, 0, yourRTB.Lines.Length - 10);
Note that the function above should have a few checks for valid input; I left them out as the checks somehow need a decision what to do, if count or fromLine if greater than Lines.Length or if fromLine is negative..
While we are at it, here is how to append a bold line:
yourRTB.SelectionStart = yourRTB.Text.Length;
yourRTB.SelectionLength = 0;
using (Font font = new Font(yourRTB.SelectionFont, FontStyle.Bold))
yourRTB.SelectionFont = font;
yourRTB.AppendText(yourNewLine + textOfNewLine);
Of course it really shold go into a reuseable function that the the bolding as a parameter..
Update:
since you are using WordWrap you may prefer this function. It deletes the actual lines, not the visible ones:
void DeleteLinesWW(RichTextBox rtb, int fromLine, int count)
{
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = rtb.Lines.Take(fromLine).Select(x => x.Length + nll).Sum() - nll;
int pL = rtb.Lines.Take(fromLine + count).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
rtb.SelectionStart = pS;
rtb.SelectionLength = pL - pS ;
bool readOnly = rtb.ReadOnly;
rtb.ReadOnly = false; // allow change even when the RTB is readonly
rtb.Cut();
rtb.ReadOnly = readOnly;
}
A word on NewLine: Do note that I have not used the Environment.NewLine constant as it not really a good idea. If you add multiline text to the RichTextBox in the designer and then look at it you will see that it uses simple '\n' new lines, no returns, no NL-CR, just '\n'. So this seems to be the generic way in a winforms RTB and I recommend using it..
The new function relies on all lines having a newline of the same length!
To make sure you can use this replacement function:
int RTBReplace(RichTextBox rtb, string oldText, string newText)
{
int p = 0; int count = 0;
do
{
p = richTextBox1.Text.IndexOf(oldText);
if (p >= 0)
{
richTextBox1.SelectionStart = p;
richTextBox1.SelectionLength = oldText.Length;
richTextBox1.SelectedText = newText;
count ++;
}
}
while (p >= 0);
return count;
}
Calling it like this:
RTBReplace(yourrichTextBox, "\r\n", "\n");
Update 2:
Here is an example how to add your chat lines:
private void button1_Click(object sender, EventArgs e)
{
string cLine = "Taw: Hello World"; // use your own lines!
var chatstr = cLine.Split(new string[] { ": " }, 2, StringSplitOptions.None);
AppendLineBold(yourrichTextBox, "\n" + chatstr[0], true);
AppendLineBold(yourrichTextBox, chatstr[1], false);
yourrichTextBox.ScrollToCaret();
}
void AppendLineBold(RichTextBox rtb, string text, bool bold)
{
rtb.SelectionStart = richTextBox1.Text.Length;
rtb.SelectionLength = 0;
using (Font font = new Font(rtb.SelectionFont,
bold ? FontStyle.Bold : FontStyle.Regular))
rtb.SelectionFont = font;
rtb.AppendText(text);
}
Update 3:
Looks like the ReadOnly property disallows the use of Cut. So we need to temporatily allow changes.
Funny: SelectedText can't be set either, but AppendText works fine..
To keep text formatting, you can also try the following (it's a little shorter and should also do the trick)
string text = "Username: hello this is a chat message";
// delete the first line when after 10 lines
if (ChatText.Lines.Length >= 10)
{
ChatText.SelectionStart = 0; // set SelectionStart to the beginning of chat text (RichTextBox)
ChatText.SelectionLength = ChatText.Text.IndexOf("\n", 0) + 1; // select the first line
ChatText.SelectedText = ""; // replace by an empty string
ChatText.SelectionStart = ChatText.Text.Length; // set SelectionStart to text end to make SelectionFont work for appended text
}
// split the string in chatstr[0] = username, chatstr[1] = message
string[] chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// make the username bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
ChatText.AppendText(chatstr[0] + ": ");
// make the message regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
ChatText.AppendText(chatstr[1] + Environment.NewLine);
ChatText.ScrollToCaret();

Automatically increment filename

Right now I have this code:
int number = 0;
DirectoryInfo di = new DirectoryInfo(scpath + #"Screenshots\");
if (di.Exists) {
} else {
di.Create();
}
int screenWidth = Screen.GetBounds(new Point(0, 0)).Width;
int screenHeight = Screen.GetBounds(new Point(0, 0)).Height;
Bitmap bmpScreenShot = new Bitmap(screenWidth, screenHeight);
Graphics gfx = Graphics.FromImage((Image)bmpScreenShot);
gfx.CopyFromScreen(0, 0, 0, 0, new Size(screenWidth, screenHeight));
bmpScreenShot.Save(di + "Screenshot_" + number, ImageFormat.Jpeg);
Program takes a screenshot (which works) and saves it. What I want to do is to have the program check and see if a screenshot exists ("Screenshot_*") and to create it if it doesn't. If it does, increment file name till it hits a number that hasn't been used at the end of "Screenshot_"
Not sure how to go about this given that it's more with files and incrementing. I'm thinking about a for loop but I'm playing with it now.
Getting the name of a file that does not exist sounds like a job for a method.
string IndexedFilename(string stub, string extension)
{
int ix = 0;
string filename = null;
do {
ix++;
filename = String.Format("{0}{1}.{2}", stub, ix, extension);
} while (File.Exists(filename));
return filename;
}
There is a race condition if you call this from multiple threads.
Assuming you have just one app and one thread in the app asking for filenames, then this ought to work.
The code to use the method looks like this:
string di = Path.Combine(scpath, "Screenshots");
if (!Directory.Exists(di) {
Directory.Create(di);
}
int screenWidth = Screen.GetBounds(new Point(0, 0)).Width;
int screenHeight = Screen.GetBounds(new Point(0, 0)).Height;
Bitmap bmpScreenShot = new Bitmap(screenWidth, screenHeight);
Graphics gfx = Graphics.FromImage((Image)bmpScreenShot);
gfx.CopyFromScreen(0, 0, 0, 0, new Size(screenWidth, screenHeight));
string filename = IndexedFilename(Path.Combine(di,"Shot_"),"jpg");
bmpScreenShot.Save(filename, ImageFormat.Jpeg);
Like #Quintin said, use datetime for filename:
string filename = Path.Combine(
di.FullName,
String.Format("{0}.jpg", DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss")));
bmpScreenShot.Save(filename, ImageFormat.Jpeg);
This is a possibility
string[] files = System.IO.Directory.GetFiles(scpath, "Screenshot_*.jpg");
string baseName = Path.Combine(scpath, "Screenshot_");
string filename;
int i = 0;
do {
filename = baseName + ++i + ".jpg";
} while (files.Contains(filename));
The advantage of this approach is that the file system is queried only once. If the file number gets large, consider adding the file names to a hash set to speed up the checks even more:
var files = new HashSet<string>(Directory.GetFiles(scpath, "Screenshot_*.jpg"));
Instead of using a number as a way to differentiate between screenshots use a timestamp:
string currentDT = string.Format("{0:D4}.{1:D2}.{2:D2}-{3:D2}.{4:D2}.{5:D2}",
DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day,
DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second)
bmpScreenShot.Save(di + "Screenshot_" + currentDT, ImageFormat.Jpeg);
I'd use GUID...
try{
bmpScreenShot.Save(di + "Screenshot_" + Guid.NewGuid().ToString(), ImageFormat.Jpeg);
}catch(Exception e)
{
//handle the problems here, for example if file already exists, try again
}
This should work well until you run out of unique GUIDs...
public static string MakeUniqueFileName(string file)
{
string dir = Path.GetDirectoryName(file);
string fn;
for (int i = 0; ; ++i)
{
fn = Path.Combine(dir, string.Format(file, i));
if (!File.Exists(fn))
return fn;
}
}
Use it like this:
string file = scpath + #"Screenshots\" + "Screenshot_{0}.png";
bmpScreenShot.Save(MakeUniqueFileName(file), ImageFormat.Jpeg);
This will create output_0.jpg output_1.jpg ... output_n.jpg:
int filecount = 0;
string path = Environment.CurrentDirectory;
for (int i = 0; File.Exists(path + #"\output_" + i + ".jpg"); i++)
{
filecount = i + 1;
}
File.Create(path + #"\output_" + filecount + ".jpg");
private static string GetUniqueFile(string path, string file, string ext)
{
int filecount = 0;
int i = 0;
for (i = 0; File.Exists(path + "\\" + file + "_" + i + "." + ext); i++)
{
filecount = i + 1;
}
return path + "\\" + file + "_" + i.ToString() + "." + ext;
}
In case the directory with the screenshots contains many images, it might be beneficial to find the next available filename using binary search. This way the File.Exists method will be called far fewer times than doing an incremental search.
/// <summary>
/// Performs a binary search in the Int32 range, and returns the first element
/// that satisfies a condition.
/// </summary>
public static TElement BinarySearchFirst<TElement>(
Func<int, TElement> selector,
Predicate<TElement> predicate,
int start = 1)
{
ArgumentNullException.ThrowIfNull(selector);
ArgumentNullException.ThrowIfNull(predicate);
if (start < 0) throw new ArgumentOutOfRangeException(nameof(start));
long lo = start;
long hi = 1;
(TElement Value, bool HasValue) maxFound = default;
// First stage, find an approximate upper limit of the search space.
while (hi < Int32.MaxValue)
{
hi = Math.Min(hi * 10, Int32.MaxValue);
if (hi < start) continue;
TElement item = selector((int)hi);
bool accepted = predicate(item);
if (accepted)
{
maxFound = (item, true);
hi--;
break;
}
lo = hi + 1;
}
// Second stage, perform binary search between lo and hi.
while (lo <= hi)
{
long mid = lo + ((hi - lo) >> 1);
TElement item = selector((int)mid);
bool accepted = predicate(item);
if (accepted)
{
maxFound = (item, true);
hi = mid - 1;
}
else
lo = mid + 1;
}
if (maxFound.HasValue) return maxFound.Value;
throw new InvalidOperationException("Element not found in the Int32 range.");
}
Usage example:
string pathFound = BinarySearchFirst(
i => Path.Combine(#"C:\Screenshots", $"Screenshot-{i}.png"),
path => !File.Exists(path));
In a folder with 200 screenshots, the above code will check for the existence of the files below:
Screenshot-10.png
Screenshot-100.png
Screenshot-1000.png
Screenshot-550.png
Screenshot-325.png
Screenshot-212.png
Screenshot-156.png
Screenshot-184.png
Screenshot-198.png
Screenshot-205.png
Screenshot-201.png
Screenshot-199.png
Screenshot-200.png
...before returning the value "C:\Screenshots\Screenshot-201.png" as the result.
Online demo.

Categories

Resources