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);
}
Related
I want the the timeToString values change based on the count changing.So when the count of seconds decrement the second.Count.ToString("59") should decrement to "58".In the debug the PondoreCount function is working as expected, however I can't see the value decrementing in the console.
public class PonodoreTimer : Clock
{
private Clock _seconds;
private Clock _minutes;
public PonodoreTimer()
{
_seconds = new Clock();
_minutes = new Clock();
_seconds.Count = 59;
_minutes.Count = 24;
// string timeToString = _minutes.Count.ToString("24") + ":" + _seconds.Count.ToString("59");
}
public string PondoreTimerInString()
{
string timeToString = _minutes.Count.ToString("24") + ":" + _seconds.Count.ToString("59");
return timeToString;
}
public void PonodoreCount()
{
//string timeToString = _minutes.Count + ":" + _seconds.Count;
//Console.WriteLine(timeToString);
while (_minutes.Count != 0)
{
_seconds.Decrement();
if(_seconds.Count == 0)
{
_minutes.Decrement();
_seconds.Count = 59;
}
}
}
}
The whole thing can be replaced by:
var ts = TimeSpan.FromMinutes(25);
var one = TimeSpan.FromSeconds(1);
while(ts > TimeSpan.Zero){
ts = ts - one;
Console.WriteLine($#"{ts:mm\:ss}");
}
24:59, 24:58 ...
If you want it to count down from 25:00, put the WriteLine before the subtract
Change this string
string timeToString = _minutes.Count.ToString("24") + ":" + _seconds.Count.ToString("59");
to this
string timeToString = _minutes.Count.ToString() + ":" + _seconds.Count.ToString();
or you always get "24" and "59"...
I have this problem where I run my code and it gives me empty text boxs and I'm not sure why. I have debug it and found that number = double.Parse(txtTableAvgTemp.Text); and poolCost = double.Parse(txtTableDollars.Text); are both returning NULL. The code is to work out the heating cost of the pool in question as per size.
const double poolLengthMin = 5; // min pool length
const double poolLengthMax =50; // max pool length
const double poolWidthMin = 2; // min pool width
const double poolWidthMax = 20; // max pool width
const double poolDepthMin = 2; // min pool depth
const double poolDepthMax = 4; // max pool depth
// variable used in btnCalculate_Click
float poolLength;
float poolWidth;
float poolDepth;
float SurfaceArea = 0;
float Volume = 0;
const int poolSize = 0;
const int smallPool = 500000 ;
const int mediumPool = 1500000;
const double poolTemp = 1.5;
double poolDegreesMin = 5;
double poolDegreesMax = 25;
double number;
double costToHeatPool;
double heatingVolume;
double poolDegrees = 5;
/* validation statements for pool
* length, width and depth
*/
bool ValidPoolLength(double poolLength)
{
if (poolLength >= poolLengthMin && poolLength <= poolLengthMax)
{
return true;
}
else
{
return false;
}
}
bool ValidPoolWidth(double poolWidth)
{
if (poolWidth >= poolWidthMin && poolWidth <= poolWidthMax)
{
return true;
}
else
{
return false;
}
}
bool ValidPoolDepth(double poolDepth)
{
if(poolDepth >= poolDepthMin && poolDepth <= poolDepthMax)
{
return true;
}
else
{
return false;
}
}
// end of validation statements
private void lblCategory_Click(object sender, EventArgs e)
{
}
private void btnCalculate_Click(object sender, EventArgs e)
{ // convert variable to float from double from string
poolLength = float.Parse(txtLength.Text);
poolWidth = float.Parse(txtWidth.Text);
poolDepth = float.Parse(txtAvgDepth.Text);
//clear string
txtVolume.Clear();
txtSurfaceArea.Clear();
txtTableDollars.Clear();
// error massages for pool length
//pool width and pool depth
if (!(ValidPoolLength(poolLength)))
{
MessageBox.Show("Length measurement is invalid \r\n Please enter a value between : " + poolLengthMin + " and " + poolLengthMax, "Pool Lenght Invalid" , MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
return;
}
if (!(ValidPoolWidth(poolWidth)))
{
MessageBox.Show("Width measurment is invalid \r\n Please enter a value between : " + poolWidthMin + " and " + poolWidthMax, "Pool Width Invalid", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
return;
}
if (!(ValidPoolDepth(poolDepth)))
{
MessageBox.Show("Pool Depth is invalid \r\n Please enter a value between : " + poolDepthMin + " and " + poolDepthMax, "Pool Depth Invalid", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
return;
}
// caclulate surface area and show in txtSurfaceArea
SurfaceArea = poolLength * poolWidth;
txtSurfaceArea.Text += SurfaceArea;
//calculate pool volume and show in txtVolume
Volume = poolLength * poolWidth * poolDepth * 1000;
txtVolume.Text += Volume;
//calculate size of pool Small, Medium or large
//and show in lblcategory
Volume = float.Parse(txtVolume.Text);
if (poolSize <= smallPool && smallPool >= Volume)
{
lblCategory.Text = "Pool Category: Small";
}
else if (poolSize <= mediumPool && mediumPool >= Volume)
{
lblCategory.Text = "Pool Category: Medium";
}
else
{
lblCategory.Text = "Pool Category: Large";
}
//cost to heat the pool to 25 degrees
while (poolTemp >= poolDegrees && poolTemp < poolDegrees)
{
number = poolDegrees + poolTemp;
txtTableAvgTemp.Text += number.ToString() + "\r\n";
poolDegrees += poolTemp;
//variable for costing out heating of pool
double poolCost = costToHeatPool;
heatingVolume = float.Parse(txtVolume.Text);
costToHeatPool = float.Parse(txtVolume.Text);
poolCost = double.Parse(txtTableDollars.Text);//empty ?
number = double.Parse(txtTableAvgTemp.Text);//empty ?
poolCost = (Math.Truncate(poolCost));
//formula for costing of heating to pool
costToHeatPool = (25 - number) * heatingVolume / 32500;
txtTableDollars.Text += poolCost + "\r\n";
}
}
I have given you my whole code as I'm not sure where I have gone wrong. I did try poolDegreesMin and poolDegreesMax.
I just see some problems with the parsing itselve. You parse the text of txtTableAvgTemp.Text.
This text is extended for each while loop with a new line of temperature:
txtTableAvgTemp.Text += number.ToString() + "\r\n";
This will never parse.
Further on, on the first cycle the text is empty ("", which is not null). This will also throw a FormatException.
Initialize the text box first with a 0. And don't add new lines to a text box if you want to parse from them later. You should use temporary double fields to store the last values. Then there is no need for parsing the whole time.
// init with 0
txtTableDollars.Text = 0;
txtTableAvgTemp.Text = 0;
double currentCost = 0;
// do them outside, no need to do it on every loop
float heatingVolume = float.Parse(txtVolume.Text);
// working while
while (poolDegrees >= 3.5 && poolDegrees < 23)
{
poolDegrees += poolTemp;
double costToHeatPool = (25 - poolDegrees) * heatingVolume / 32500;
currentCost += costToHeatPool;
txtTableAvgTemp.Text += poolDegrees + System.Environment.NewLine;
txtTableDollars.Text += currentCost + System.Environment.NewLine;
}
Additional point: I do not know exactly, but this while loop here looks realy suspicious
while (poolTemp >= poolDegrees && poolTemp < poolDegrees) {/*...*/}
For me this looks like a while(false). Is this intended? this loop should never be executed...
Well, in the beginning of your code there is line of code:
txtTableDollars.Clear();
is clears textbox, so later when you're using that text, obviously it is empty:
poolCost = double.Parse(txtTableDollars.Text);
And later in the same loop - you're assigning it like
txtTableDollars.Text += poolCost + "\r\n";
so if your loop will be executed more than once - you will get some strange result (or exception) when parsing it again.
It looks like you should divide your computation from visualisation. Parse your input once and use numbers for computation, don't reparse text values again.
I am using the following mp4 streaming script for streaming mp4 videos on html5 player.
private void RangeDownload(string fullpath,HttpContext context)
{
long size,start,end,length,fp=0;
using (StreamReader reader = new StreamReader(fullpath))
{
size = reader.BaseStream.Length;
start = 0;
end = size - 1;
length = size;
context.Response.AddHeader("Accept-Ranges", "0-" + size);
if (!String.IsNullOrEmpty(context.Request.ServerVariables["HTTP_RANGE"]))
{
long anotherStart = start;
long anotherEnd = end;
string[] arr_split = context.Request.ServerVariables["HTTP_RANGE"].Split(new char[] { Convert.ToChar("=") });
string range = arr_split[1];
// Make sure the client hasn't sent us a multibyte range
if (range.IndexOf(",") > -1)
{
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
throw new HttpException(416, "Requested Range Not Satisfiable");
}
if (range.StartsWith("-"))
{
// The n-number of the last bytes is requested
anotherStart = size - Convert.ToInt64(range.Substring(1));
}
else
{
arr_split = range.Split(new char[] { Convert.ToChar("-") });
anotherStart = Convert.ToInt64(arr_split[0]);
long temp = 0;
anotherEnd = (arr_split.Length > 1 && Int64.TryParse(arr_split[1].ToString(), out temp)) ? Convert.ToInt64(arr_split[1]) : size;
}
anotherEnd = (anotherEnd > end) ? end : anotherEnd;
if (anotherStart > anotherEnd || anotherStart > size - 1 || anotherEnd >= size)
{
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
throw new HttpException(416, "Requested Range Not Satisfiable");
}
start = anotherStart;
end = anotherEnd;
length = end - start + 1; // Calculate new content length
fp = reader.BaseStream.Seek(start, SeekOrigin.Begin);
context.Response.StatusCode = 206;
}
}
// Notify the client the byte range we'll be outputting
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
context.Response.AddHeader("Content-Length", length.ToString());
// Start buffered download
context.Response.WriteFile(fullpath, fp, length);
context.Response.End();
}
However it works fine. Now i have sceneario where i need to restrict bandwidth of stream for paid and free users. Paid users have twice more bandwidth speed (kbps) then free user.
For this i integrated bandwith throttling script mentioned here http://www.codeproject.com/Articles/18243/Bandwidth-throttling
so i replace the following code in above function
length = end - start + 1; // Calculate new content length
fp = reader.BaseStream.Seek(start, SeekOrigin.Begin);
with
long speed = 1024000; // 1024kbps
length = end - start + 1; // Calculate new content length
Stream throttle = new ThrottledStream(reader.BaseStream, speed);
fp = throttle.Seek(start, SeekOrigin.Begin);
but it doesn't work.
Can any one help me how to restrict stream on above example.
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.
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);
}