Im trying to get into some webpages and get some information, using web browser so that it remembers my login details. things worked till here but for multiple urls web browser document load is not working properly as i want.
My intention was go to url->wait till it loads--> get required data into text--> new url and same process.
i used for loop to change url but when i run all the url's passed one by one not waiting till document loads and writes to text. please help me.
private void button1_Click_1(object sender, EventArgs e)
{
String text = File.ReadAllText("links.txt");
var result = Regex.Split(text, "\r\n|\r|\n");
foreach (string s in result)
{
listBox1.Items.Add(s);
}
for (int i = 0; i < listBox1.Items.Count; i++)
{
this.Text = Convert.ToString(i + 1) + "/" + Convert.ToString(listBox1.Items.Count);
textBox1.Text += listBox1.Items[i];
String url = textBox1.Text;
webBrowser2.ScriptErrorsSuppressed = true;
webBrowser2.DocumentCompleted += webBrowser2_DocumentCompleted;
webBrowser2.Navigate(url);
}
}
void webBrowser2_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
string sourceCode = webBrowser2.DocumentText;
try
{
/*someregax expressions to filter text */
StreamWriter sw = new StreamWriter("inks_info.txt", true);
sw.Write("url" + "~" + sourceCode + "\n");
sw.Close();
textBox1.Text = "";
}
catch
{
StreamWriter sw = new StreamWriter("inks_fail.txt", true);
sw.Write(textBox1.Text + "\n");
sw.Close();
textBox1.Text = "";
}
}
You have an event handler on the document load for each item, but you're not waiting for it to fire after the first navigation before you initiate the second navigation. Your for loop needs to be "more asynchronous". For example, placing items in a queue and requesting one at a time:
Queue<string> _items;
private void button1_Click_1(object sender, EventArgs e)
{
String text = File.ReadAllText("links.txt");
_items = new Queue<string>(Regex.Split(text, "\r\n|\r|\n"));
webBrowser2.ScriptErrorsSuppressed = true;
webBrowser2.DocumentCompleted += webBrowser2_DocumentCompleted;
RequestItem();
}
private void RequestItem()
{
if (_items.Any())
{
var url = _items.Dequeue(); // preprocess as required
webBrowser2.Navigate(url);
}
}
void webBrowser2_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// Handle result
RequestItem(); // Then request next item
}
Your code also looks like it's using UI elements (like a list box) as intermediate variables just for a logical purpose rather than display. You should separate out the logic (using regular variables, data structures such as lists and requesting data) from the display (showing the results in list boxes, updating text boxes, etc). It's not clear that you want to be using a WebBrowser even - it looks like you're just downloading text and should use WebClient or HttpClient. The code can then also be much cleaner using async/await:
foreach (var url in urls)
{
string text = await new WebClient().DownloadStringAsync(url);
// Handle text
}
Very Simple answer. The WebBorwser control sucks for this stuff but here is what you are looking for:
WHILE(webBrowser.ReadyState != WebBrowserReadyState.Ready)
{
Application.DoEvents()
}
Thats it.. It will not freeze your app or get you lost in code, it just waits till its don't navigating. You be most welcome.
Related
I'm new to c#.
I'm using the Nucleo board to send data through a serial port into my GUI. The data consists of pulse rate, number of steps and body temperature.
My code here works completely fine to display all the data into a single textbox, but I want to display each value in different textboxes.
This is what the incoming data looks like
S0E // where "S" is for steps and "E" is for the end
P5E // where "P" is for pulse rate and "E" is for the end
T22.5E // where "T" is for body temp. and "E" is for the end
Here is the code I am using:
private void showbtn_Click(object sender, EventArgs e) //Showbtn click
{
text.Clear(); //clears the text in the textbox
bool foundUser = false; // sets the boolean foundUser to false
int userindex = 0; // sets interger to zero
for (int i = 0; i < userlist.Count; i++)
{
if (userlist[i].name.Equals(Nametb.Text)) // if the user entered name equals to the name in the list
{
foundUser = true;
userindex = i;
}
}
if (foundUser == true)
{
string userText; // the following lines of code displays the user details in the textbox
userText = "name :" + userlist[userindex].name + "user :" + Environment.NewLine + "age:" + userlist[userindex].age +
Environment.NewLine + " gender:" + userlist[userindex].gender + Environment.NewLine + "height:" + userlist[userindex].height +
Environment.NewLine + "weight:" + userlist[userindex].weight + Environment.NewLine + "BMI :" + userlist[userindex].bmI;
text.Text = userText;
}
else
{
text.Text = "no user found"; // if the user not found displays as "no user Found"
}
t = comboBox1.Text.ToString();
sErial(t);
}
private void button2_Click(object sender, EventArgs e) // searches for available com ports
{
string[] ports = SerialPort.GetPortNames();
foreach (string port in ports)
{
comboBox1.Items.Add(port); // adds them to the combo box
}
}
string t;
private SerialPort SerialPort1;
void sErial(string Port_name)
{
SerialPort1 = new SerialPort(Port_name, 9600, Parity.None, 8, StopBits.One); //serial port properties
SerialPort1.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
SerialPort1.Open(); //opens serial port
}
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort serialPort1 = (SerialPort)sender;
string w = serialPort1.ReadLine(); // assigns the data from the serial port to a string
if (w != String.Empty)
{
if (abort == false)
{
Invoke(new Action(() => rt1.AppendText(w))); // displays the data in a textbox
}
}
}
If I have understood correctly, you receive data from an external device and you wish to split apart a string that contains multiple numeric data elements delimited by single characters, so you can display the results to different textboxes.
This is a typical problem to be solved by people who receive data from an embedded system and need to learn how to handle it on a host PC, but are new to programming in C#.
There are many different ways to do this. Since you say you can receive and display the entire string in a single textbox, I will focus only on how to split it apart and display the parts in three different richTextBoxes.
For a c# beginner, string.split and substring are pretty straightforward ways to break apart a string. Here is one simple example using string.split.
I made a complete example for you: you can see below that it works. First I created a form containing three rich textboxes called rt1, rt2, and rt3, along with an input textbox called tbInput and a button called btnSplit.
Then in the designer I double-clicked on the button to add an event handler for btnInput_click and added the code below to that handler. That's it!
private void btnSplit_Click(object sender, EventArgs e)
{
string blah = tbInput.Text;
tbInput.Clear();
var lst = blah.ToUpper().Split('E').ToList();
foreach (var item in lst)
if (item.Trim().StartsWith("S"))
rt1.AppendText($"Steps: {item.Remove(0, 1)} \n");
else if (item.Trim().StartsWith("T"))
rt2.AppendText($"Temperature: {item.Remove(0, 1)} \n");
else if (item.Trim().StartsWith("P"))
rt3.AppendText($"Pulse: {item.Remove(0, 1)} \n");
}
The code converts the input to upper case, splits the string on the letter "E" and assigns the results to a list, so given the starting string containing three "E"s, you will now have a list containing three strings:
item01 == S111
item02 == T98.6
item03 == P70
I then trim leading and trailing white space and assign the result to one of three RichTextBoxes based on the leading character. I use item.Remove(0,1) to remove the leading character before appending the result to the textbox. I use interpolation to embed the result in a string of my choosing.
I should probably point out that unless you are running this code on a thread other than the UI thread, you do not need to use an Action delegate and you do not need to Invoke anything. If you're on the main thread, just append the text to the textbox.
If you are planning to make a cross-thread call to a control running on the UI, an Action<> delegate is not the right way to do this. If that's what you're trying to do, look at MethodInvoker(). However, I strongly recommend that you not try to use multiple threads until you are quite advanced as a programmer and have read several books on the subject. They certainly are not needed for a simple project like this one :)
Does this help?
#craig.Feied
this was the part which I altered.
thanks again for your help.
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort serialPort1 = (SerialPort)sender;
var rt1 = new RichTextBox();
var rt2 = new RichTextBox();
var rt3 = new RichTextBox();
string blah = serialPort1.ReadLine();
var lst = blah.Split('E').ToList();
foreach (var item in lst)
if (item.Trim().StartsWith("S"))
rt1.AppendText($"Steps: {item.Remove(0, 1)} \n");
else if (item.Trim().StartsWith("T"))
rt2.AppendText($"Temperature: {item.Remove(0, 1)} \n");
else if (item.Trim().StartsWith("P"))
rt3.AppendText($"Pulse: {item.Remove(0, 1)} \n");
}
I have a Windows Desktop application that is used to do WebScraping on a website using WebBrowser.
I had to use WebBrowser because the website implements some Javascript function so that was the only way to get the html content of the pages.
The program has to parse about 1500 pages so I have implemented a task delay in order to avoid to overload the server ( and may be getting banned ).
The problem is that after 50-100 parsed pages, I get an out of memory error and the program gets closed.
This is the code:
private async void buttonProd_Click(object sender, EventArgs e)
{
const string C_Prod_UrlTemplate = "http://www.mysite.it";
var _searches = new List<Get_SiteSearchResult>();
using (ProdDataContext db = new ProdDataContext())
{
_searches = db.Get_SiteSearch("PROD").ToList();
foreach (var s in _searches)
{
WebBrowser wb1 = new WebBrowser();
wb1.ScriptErrorsSuppressed = true;
Uri uri = new Uri(String.Format(C_Prod_UrlTemplate,s.prod));
wb1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser_DocumentCompleted);
wb1.Url = uri;
await Task.Delay(90 * 1000);
}
}
}
private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
using (ProdDataContext db = new ProdDataContext())
{
WebBrowser wb = (WebBrowser)sender;
string s = wb.Document.Body.InnerHtml;
string fName = wb.CodSite + "_" + wb.PostId + ".txt";
File.WriteAllText(wb.FolderPath + #"LINKS\" + fName, s);
db.Set_LinkDownloaded(wb.CodSite, wb.PostId);
}
}
The error messa is generated on this command line in webBrowser_DocumentCompleted method:
string s = wb.Document.Body.InnerHtml;
Thanks to support
Instead of using a control (which is a rather complex construct that requires more memory than a simple object), you can simply fetch the string (the HTML code only) associated with an URL like this:
using(WebClient wc = new WebClient()) {
string s = wc.DownloadString(url);
// do stuff with content
}
Of course, you should ensure some error handling (maybe even a retrial mechanism) and put some delays to ensure you are not doing too much requests per time interval.
I have program that displays a "Loading" Winform when a button is pressed and disappears once the script needing to be run is complete.
When the button is pressed, the new form 'appears' however it displays none of the form information, such as the Logo and labels - only a blank/grey box. I've attempted changing the background colour and altering images however it is still displaying as blank form.
What I find to be most confusing is that this blank form displayed only appears blank when a specific CS. file is called within the button press; PDFMerge.CombineMultiblePDFs. If I try to display the Loading form within a different part of the program, e.g. when a different button is pressed, the form loads correctly as planned with all content.
Here is the blank form being displayed:
Here is the correct form being displayed on a different button or different form
Here is the code I am calling which displays the "blank" Winform.
loadingPDF.Show(); // Show the loading form
string fileDate = DateTime.Now.ToString("dd-MM-yy");
string fileTime = DateTime.Now.ToString("HH.mm.ss");
string outcomeFolder = outputFolder;
string outputFile = "Combined Folder " + fileDate + " # " + fileTime + ".pdf";
string outputFileName = Path.Combine(outcomeFolder, outputFile);
// combines the file name, output path selected and the yes / no for pagebreaks.
PDFMerge.CombineMultiplePDFs(sourceFiles, outputFileName);
loadingPDF.Hide(); // Hide the loading form
If I replace the PDFMerge.Combine with a different within CS file, the Loading form displays correctly, which leads me to believe the issue is laying with the PDFMerge and when it is being called. Below is the code used within the PDFMerge;
public class PDFMerge
{
public static void CombineMultiplePDFs(String[] fileNames, string outFile)
{
try
{
int pageOffset = 0;
int f = 0;
Document document = null;
PdfCopy writer = null;
while (f < fileNames.Length)
{
// Create a reader for a certain document
PdfReader reader = new PdfReader(fileNames[f]);
reader.ConsolidateNamedDestinations();
// Retrieve the total number of pages
int n = reader.NumberOfPages;
pageOffset += n;
if (f == 0)
{
// Creation of a document-object
document = new Document(reader.GetPageSizeWithRotation(1));
// Create a writer that listens to the document
writer = new PdfCopy(document, new FileStream(outFile, FileMode.Create));
// Open the document
document.Open();
}
// Add content
for (int i = 0; i < n;)
{
++i;
if (writer != null)
{
PdfImportedPage page = writer.GetImportedPage(reader, i);
writer.AddPage(page);
}
}
PRAcroForm form = reader.AcroForm;
if (form != null && writer != null)
{
//writer.CopyAcroForm(reader);
writer.Close();
}
f++;
}
// Close the document
if (document != null)
{
document.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
I don't see what could be causing the clash with the form display, perhaps the Form isn't loading on time but i don't see how it works with some features and not with others. Any advice regarding the issue would be greatly appreciated. Thank you
Update 1:
Additional code requested,
Here is the code used to LoadingPDF form. I used Winforms to create the content on the form:
public partial class LoadingPDF : Form
{
public LoadingPDF()
{
InitializeComponent();
}
private void LoadingPDF_Load(object sender, EventArgs e)
{
//
}
}
Creating instance of the loadingPDF form in the file selection form
// Declaring the 'loading' form when files are being combined.
LoadingPDF loadingPDF = new LoadingPDF();
Building on the comments, the PDFMerge.CombineMultiplePDFs() is cpu-locking your program, causing the thread to stop loading the form before it finishes. You can adapt your code like this:
public void ShowLoading()
{
loadingPDF.Shown += loadingPDF_Shown;
loadingPDF.Show(); // Show the loading form
}
public void loadingPDF_Shown(object sender, eventargs e)
{
string fileDate = DateTime.Now.ToString("dd-MM-yy");
string fileTime = DateTime.Now.ToString("HH.mm.ss");
string outcomeFolder = outputFolder;
string outputFile = "Combined Folder " + fileDate + " # " + fileTime + ".pdf";
// combines the file name, output path selected and the yes / no for pagebreaks.
PDFMerge.CombineMultiplePDFs(sourceFiles, outputFileName);
loadingPDF.Hide(); // Hide the loading form
}
Shown is the last event to trigger when a form is loaded. This should load your images before you start your cpu-intensive process.
An alternative would be to put your cpu-intensive process on another thread, to keep the UI thread clear. You can do that like this:
public void ShowLoading()
{
loadingPDF.Show(); // Show the loading form
System.ComponentModel.BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync(); //Added missed line
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//anything you want to do AFTER the cpu-intensive process is done
loadingPDF.Hide(); // Hide the loading form
}
public void worker_DoWork(object sender, DoWorkEventArgs e)
{
string fileDate = DateTime.Now.ToString("dd-MM-yy");
string fileTime = DateTime.Now.ToString("HH.mm.ss");
string outcomeFolder = outputFolder;
string outputFile = "Combined Folder " + fileDate + " # " + fileTime + ".pdf";
string outputFileName = Path.Combine(outcomeFolder, outputFile);
// combines the file name, output path selected and the yes / no for pagebreaks.
PDFMerge.CombineMultiplePDFs(sourceFiles, outputFileName);
}
Doing this with a background worker will keep your UI usable/clickable, and not make it freeze. Among other things, this allows for an animated loading form.
The program does not follow the logic, I need it to do the request according to the order of the items of the listbox
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
foreach (var listBoxItem in listBox1.Items)
{
for (int j = 0; j < listBox1.Items.Count; j++)
{
string lista = listBox1.Items[j].ToString();
string[] split = lista.Split(';');
num.Text = split[0];
v1.Text = split[1];
v2.Text = split[2];
c.Text = split[3];
WebClient client = new WebClient();
client.Proxy = null;
client.DownloadDataCompleted += new DownloadDataCompletedEventHandler(this.asyncWebRequest_DownloadDataCompleted);
client.DownloadDataAsync(new Uri("http://127.0.0.1/sv/" + num.Text));
j++;
}
}
}
private void asyncWebRequest_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
string #string = Encoding.Default.GetString(e.Result);
if (#string.Contains("uva"))
{
this.listBox2.Items.Add(numero.Text);
}
}
The program should make the download request of the string with EACH item in the listbox
example:
DownloadString http://127.0.0.1/sv/ + num.text and check if it contains a particular string
I need it to do the following
DownloadString http://127.0.0.1/sv/ + num.text
if
DownloadedString contains uva
then
listbox2.items.add(num.text)
else
next item from listbox1
There's so much wrong with this code, it's no wonder it's going wrong.
You're using Background Workers, don't, you should be using Tasks and async/await
You're looping over every item in the listbox twice, why?
You're spinning up multiple web clients with event subscriptions that are never disposed - probably gonna be leaking some memory here.
Here's how I'd do it:
public async void ButtonThatStartsEverything_Click(object sender, EventArgs e)
{
await DoTheDownloadStuff();
}
public async Task DoTheDownloadStuff()
{
var client = new WebClient();
foreach(var item in ListBox1.Items)
{
var expanded = item.Split(';');
var num = expanded[0];
var result = await client.DownloadDataAsyncTask(new Uri("http://127.0.0.1/sv/" + num));
if (result.Contains("uva"))
{
listBox2.Items.Add(num);
}
}
}
Please be aware that the code was written outside of Visual Studio, it may not be 100% accurate, and may not represent best practice overall e.g. You may want to download the data in parallel, which would require a change.
This code has all the basic stuff you need though.
lets say I have a GroupBox with several Labels. In these Labels, various IP-related information are displayed. One info is the external IP address of the machine.
string externalIP = "";
try
{
WebRequest request = WebRequest.Create("http://checkip.dyndns.org/");
request.Timeout = 3000;
System.Threading.Tasks.Task<System.Net.WebResponse> response = request.GetResponseAsync();
using (StreamReader stream = new StreamReader(response.Result.GetResponseStream()))
{
if (response.Result.ContentLength != -1)
{
externalIP = stream.ReadToEnd();
}
}
}
catch (Exception e)
{
externalIP = "Error.";
}
if (externalIP == "")
{
return "No service.";
}
else
{
return externalIP = (new Regex(#"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")).Matches(externalIP)[0].ToString();
}
This method is called from following code:
private void updateNetworkIP()
{
string ip4e = "External IPv4: " + getExternalIPv4();
lblIP4external.Text = ip4e;
//Get some more info here.
}
How do I execute the code after getExternalIPv4() even when it's not finished yet? It works when setting a TimeOut like I did above but sometimes the request just takes a little longer but still completes successfully. So I want to still be able to display the external IP but continue to execute the other methods for refreshing the GroupBox.
The BackgroundWorker will deliver what you are after. Sample code:
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(getExternalIPv4Back);
bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(writeLabel);
bg.RunWorkerAsync();
//The code below this point will be executed while the BackgroundWorker does its work
You have to define getExternalIPv4Back as a DoWork Event Method and include inside it the code to be executed in parallel; also writeLabel as a RunWorkerCompleted Event(required to edit the label without provoking muti-threading-related errors). That is:
private void getExternalIPv4Back(object sender, DoWorkEventArgs e)
{
IP = "External IPv4: " + getExternalIPv4(); //IP -> Globally defined variable
}
private void writeLabel(object sender, RunWorkerCompletedEventArgs e)
{
lblIP4external.Text = IP;
}