0

I have a winform app with the following functionality:

  1. Has a multiline textbox that contain one URL on each line - about 30 URLs (each URL is different but the webpage is the same (just the domain is different);
  2. I have another textbox in which I can write a command and a button that sends that command to an input field from the webpage.
  3. I have a WebBrowser controller ( I would like to do all the things in one controller )

The webpage consist of a textbox and a button which I want to be clicked after I insert a command in that textbox.

My code so far:

//get path for the text file to import the URLs to my textbox to see them
private void button1_Click(object sender, EventArgs e)
{
    OpenFileDialog fbd1 = new OpenFileDialog();
    fbd1.Title = "Open Dictionary(only .txt)";
    fbd1.Filter = "TXT files|*.txt";
    fbd1.InitialDirectory = @"M:\";
    if (fbd1.ShowDialog(this) == DialogResult.OK)
        path = fbd1.FileName;
}

//import the content of .txt to my textbox
private void button2_Click(object sender, EventArgs e)
{
    textBox1.Lines = File.ReadAllLines(path);
}

//click the button from webpage
private void button3_Click(object sender, EventArgs e)
{
    this.webBrowser1.Document.GetElementById("_act").InvokeMember("click");
}

//parse the value of the textbox and press the button from the webpage
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    newValue = textBox2.Text;
    HtmlDocument doc = this.webBrowser1.Document;
    doc.GetElementById("_cmd").SetAttribute("Value", newValue);     
}

Now, how can I add all those 30 URLs from my textbox in the same webcontroller so that I can send the same command to all of the textboxes from all the webpages and then press the button for all of them ?


//EDIT 1 So, I have adapted @Setsu method and I've created the following:

public IEnumerable<string> GetUrlList()
{
    string f = File.ReadAllText(path); ;
    List<string> lines = new List<string>();

    using (StreamReader r = new StreamReader(f))
    {
        string line;
        while ((line = r.ReadLine()) != null)
            lines.Add(line);
    }
    return lines;
}

Now, is this returning what it should return, in order to parse each URL ?

shA.t
  • 16,580
  • 5
  • 54
  • 111
Cajuu'
  • 1,154
  • 2
  • 19
  • 50
  • I have edited your title. Please see, "[Should questions include “tags” in their titles?](http://meta.stackexchange.com/questions/19190/)", where the consensus is "no, they should not". – John Saunders Mar 10 '15 at 21:02
  • You can't, because a single WebBrowser object can only navigate one page at a time. You will either have to have 30 WebBrowsers at once or do it one page at a time. Note that in your code you never navigate to any page with your one WebBrowser. Also note that your `DocumentCompleted` event is not implemented correctly; you need to check when the full page is completely done because this event fires multiple times due to frames (see this [answer](http://stackoverflow.com/a/2778696/1378739)). – Setsu Mar 10 '15 at 21:29
  • @Setsu - So, I can achieve this by doing what I need with one page at a time. So, I will have a `for(){}` loop which is going to take each link from the textbox, then do what I need, right ? And I am doing this in an `else{}` statement after: `if (e.Url.AbsolutePath != (sender as WebBrowser).Url.AbsolutePath) return; ` ? – Cajuu' Mar 10 '15 at 21:36
  • @Alexander I'll type up a more detailed answer for you. As a side note, the line of code you have there can stand on its own without the else, because if the condition were true the execution would just exit out of the event. This is good because otherwise your business logic will be encapsulated in a giant `else` statement. – Setsu Mar 10 '15 at 22:03
  • For your `GetUrlList` implementation your last line just needs to be `return lines;`. Do note that each url needs to be well formed; i.e. have the necessary protocol (http:// for example). – Setsu Mar 11 '15 at 22:11
  • They are well formated. Now, I can't understand something: in method: `private void webBrowserDocumentCompleted` the text that I enter in my `textBox2` won't be parsed, right ? Because the webcontroller is already executed. @Setsu – Cajuu' Mar 11 '15 at 23:03
  • @Alexander The value of `textBox2.Text` will be taken at the time when the `webBrowserDocumentCompleted` event fires. I would make sure that `textBox2` has the proper input before calling `btnDoWork`. – Setsu Mar 11 '15 at 23:07
  • What arguments shall I pass to `btnDoWork` ? And more, how will I be able to verify that commands completed successfully for all those URLs ? (something like getting a message in a `label` that will tell me `completed successfully` or `error at URL x`). Sorry for asking so many questions but I am still learning @Setsu – Cajuu' Mar 11 '15 at 23:29
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/72802/discussion-between-setsu-and-alexander). – Setsu Mar 11 '15 at 23:37

1 Answers1

1

If you want to keep using just 1 WebBrowser control, you'd have to sequentially navigate to each URL. Note, however, that the Navigate method of the WebBrowser class is asynchronous, so you can't just naively call it in a loop. Your best bet is to implement an async/await pattern detailed in this answer here.

Alternatively, you CAN have 30 WebBrowser controls and have each one navigate on its own; this is roughly equivalent to having 30 tabs open in modern browsers. Since each WebBrowser is doing identical work, you can just have 1 DocumentCompleted event written to handle a single WebBrowser, and then hook up the others to the same event. Do note that the WebBrowser control has a bug that will cause it to gradually leak memory, and the only way to solve this is to restart the application. Thus, I would recommend going with the async/await solution.

UPDATE:

Here's a brief code sample of how to do the 30 WebBrowsers way (untested as I don't have access to VS right now):

List<WebBrowser> myBrowsers = new List<WebBrowser>();

public void btnDoWork(object sender, EventArgs e)
{
    //This method starts navigation.
    //It will call a helper function that gives us a list 
    //of URLs to work with, and naively create as many
    //WebBrowsers as necessary to navigate all of them

    IEnumerable<string> urlList = GetUrlList();
    //note: be sure to sanitize the URLs in this method call

    foreach (string url in urlList)
    {
        WebBrowser browser = new WebBrowser();
        browser.DocumentCompleted += webBrowserDocumentCompleted;
        browser.Navigate(url);
        myBrowsers.Add(browser);
    }
}

private void webBrowserDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    //check that the full document is finished
    if (e.Url.AbsolutePath != (sender as WebBrowser).Url.AbsolutePath) 
        return;

    //get our browser reference
    WebBrowser browser = sender as WebBrowser;

    //get the string command from form TextBox
    string command = textBox2.Text;

    //enter the command string
    browser.Document.GetElementById("_cmd").SetAttribute("Value", command);

    //invoke click
    browser.Document.GetElementById("_act").InvokeMember("click");

    //detach the event handler from the browser
    //note: necessary to stop endlessly setting strings and clicking buttons
    browser.DocumentCompleted -= webBrowserDocumentCompleted;
    //attach second DocumentCompleted event handler to destroy browser
    browser.DocumentCompleted += webBrowserDestroyOnCompletion;
}

private void webBrowserDestroyOnCompletion(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    //check that the full document is finished
    if (e.Url.AbsolutePath != (sender as WebBrowser).Url.AbsolutePath) 
        return;

    //I just destroy the WebBrowser, but you might want to do something
    //with the newly navigated page

    WebBrowser browser = sender as WebBrowser;
    browser.Dispose();
    myBrowsers.Remove(browser);
}
Community
  • 1
  • 1
Setsu
  • 1,188
  • 13
  • 26
  • Thank you, @Setsu, for this explanation. I will try to do it with the second method as it seems a bit easier and I am not so experienced. Anyway, if you will have some time, and can build a snippet adapted to my code, just let me know. – Cajuu' Mar 10 '15 at 22:16
  • I have one more question: how can I parse the string from the applications textbox in webbrowser controller ? I am asking this because when I start the application and I have to input the string, the webbrowser controller is already loaded. – Cajuu' Mar 10 '15 at 22:29
  • @Alexander I'm not quite sure I understand what you're asking there. Are you talking about a `TextBox` control on the form application or an html textbox element in the web page? – Setsu Mar 10 '15 at 22:50
  • I am talking about a textbox from my form in which I am writting a command ( e.g: apple).After I enter that text in the textbox and press a button (also from my form), that text should be called in webpage textbox with `doc.GetElementById("_cmd").SetAttribute("Value", newValue); `. The problem is `newValue`. The textbox is initially empty when I first run my app so `newValue = null` which is not what I want. I want `newValue = textBox1.Text.` @Setsu – Cajuu' Mar 10 '15 at 22:55
  • I get: `webBrowserDocumentCompleted does not exist in the current context` and `GetUrlList() does not exist in the current context` – Cajuu' Mar 10 '15 at 23:38
  • @Alexander I'm not sure why you're getting that the `webBrowserDocumentCompleted` method doesn't exist, but the `GetUrlList` is a function I expect you to implement to get the list of URLs. – Setsu Mar 10 '15 at 23:40
  • is that method created as it should be ? – Cajuu' Mar 11 '15 at 21:39