克里斯,
我在这里向您传递了一个解决问题的可能实现,但请看一下这里的 cmets,在一切按预期工作之前我必须面对和修复。
这是在 webBrowser 的页面上执行某些活动的方法示例(请注意,在我的情况下,webBrowser 是表单的一部分):
internal ActionResponse CheckMessages() //Action Response is a custom class of mine to store some data coming from pages
{
//go to messages
HtmlDocument doc = WbLink.Document; //wbLink is a referring link to a webBrowser istance
HtmlElement ele = doc.GetElementById("message_alert_box");
if (ele == null)
return new ActionResponse(false);
object obj = ele.DomElement;
System.Reflection.MethodInfo mi = obj.GetType().GetMethod("click");
mi.Invoke(obj, new object[0]);
semaphoreForDocCompletedEvent = WaitForDocumentCompleted(); //This is a simil-waitOne statement (1)
if (!semaphoreForDocCompletedEvent)
throw new Exception("sequencing of Document Completed events is failed.");
//get the list
doc = WbLink.Document;
ele = doc.GetElementById("mailz");
if (!ele.WaitForAvailability("mailz", Program.BrowsingSystem.Document, 10000)) //This is a simil-waitOne statement (2)
ele = doc.GetElementById("mailz");
ele = doc.GetElementById("mailz");
//this contains a tbody
HtmlElement tbody = ele.FirstChild;
//count how many elemetns are espionage reports, these elements are inline then counting double with their wrappers on top of them.
int spioCases = 0;
foreach (HtmlElement trs in tbody.Children)
{
if (trs.GetAttribute("id").ToLower().Contains("spio"))
spioCases++;
}
int nMessages = tbody.Children.Count - 2 - spioCases;
//create an array of messages to store data
GameMessage[] archive = new GameMessage[nMessages];
for (int counterOfOpenMessages = 0; counterOfOpenMessages < nMessages; counterOfOpenMessages++)
{
//open first element
WbLink.ScriptErrorsSuppressed = true;
ele = doc.GetElementById("mailz");
//this contains a tbody
tbody = ele.FirstChild;
HtmlElement mess1 = tbody.Children[1];
int idMess1 = int.Parse(mess1.GetAttribute("id").Substring(0, mess1.GetAttribute("id").Length - 2));
//check if subsequent element is not a spio report, in case it is then the element has not to be opened.
HtmlElement mess1Sibling = mess1.NextSibling;
if (mess1Sibling.GetAttribute("id").ToLower().Contains("spio"))
{
//this is a wrapper for spio report
ReadSpioEntry(archive, counterOfOpenMessages, mess1, mess1Sibling);
//delete first in line
DeleteFirstMessageItem(doc, ref ele, ref obj, ref mi, ref tbody);
semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6); //This is a simil-waitOne statement (3)
}
else
{
//It' s anormal message
OpenMessageEntry(ref obj, ref mi, tbody, idMess1); //This opens a modal dialog over the page, and it is not generating a DocumentCompleted Event in the webBrowser
//actually opening a message generates 2 documetn completed events without any navigating event issued
//Application.DoEvents();
semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6);
//read element
ReadMessageEntry(archive, counterOfOpenMessages);
//close current message
CloseMessageEntry(ref ele, ref obj, ref mi); //this closes a modal dialog therefore is not generating a documentCompleted after!
semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6);
//delete first in line
DeleteFirstMessageItem(doc, ref ele, ref obj, ref mi, ref tbody); //this closes a modal dialog therefore is not generating a documentCompleted after!
semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6);
}
}
return new ActionResponse(true, archive);
}
在实践中,此方法获取 MMORPG 的页面并读取其他玩家发送到帐户的消息,并通过方法 ReadMessageEntry 将它们存储在 ActionResponse 类中。
除了真正依赖于大小写的代码的实现和逻辑(并且对您没有用处)之外,还有一些有趣的元素可能会对您的案例产生很好的影响。
我在代码中放了一些 cmets 并突出显示了 3 个重要点[带有符号 (1)、(2) 和 (3)]
算法是:
1) 到达一个页面
2) 从 webBrowser 获取底层 Document
3) 找到一个元素以点击进入消息页面[完成方式:HtmlElement ele = doc.GetElementById("message_alert_box");]
4) 通过 MethodInfo 实例和反射调用触发单击它的事件[这会调用另一个页面,因此 DocumentCompleted 迟早会到达]
5) 等待调用完成的文档,然后继续[完成:semaphoreForDocCompletedEvent = WaitForDocumentCompleted(); at point (1)]
6) 页面更改后从 webBrowser 获取新的 Document
7) 在页面上找到一个特定的锚点,该锚点定义了我要阅读的消息的位置
8) 确保页面中存在这样的 TAG(因为可能存在一些 AJAX 延迟我想要阅读的内容准备就绪)[完成:ele.WaitForAvailability("mailz", Program.BrowsingSystem.Document, 10000) 即第 (2) 点]
9) 为读取每条消息执行整个循环,这意味着打开一个位于同一页面上的模式对话框表单,因此不会生成 DocumentCompleted,准备好时读取它,然后关闭它,然后重新循环。对于这种特殊情况,我在点 (3) 处使用称为 semaphoreForDocCompletedEvent = WaitForDocumentCompleted(6); 的 (1) 重载
现在我用来暂停、查看和阅读的三种方法:
(1) 在引发 DocumentCompleted 时停止而不会对 DocumentCompleted 方法过度收费,该方法可用于多个单一目的(如您的情况)
private bool WaitForDocumentCompleted()
{
Thread.SpinWait(1000); //This is dirty but working
while (Program.BrowsingSystem.IsBusy) //BrowsingSystem is another link to Browser that is made public in my Form and IsBusy is just a bool put to TRUE when Navigating event is raised and but to False when the DocumentCOmpleted is fired.
{
Application.DoEvents();
Thread.SpinWait(1000);
}
if (Program.BrowsingSystem.IsInfoAvailable) //IsInfoAvailable is just a get property to cover webBroweser.Document inside a lock statement to protect from concurrent accesses.
{
return true;
}
else
return false;
}
(2) 等待页面中的特定标签可用:
public static bool WaitForAvailability(this HtmlElement tag, string id, HtmlDocument documentToExtractFrom, long maxCycles)
{
bool cond = true;
long counter = 0;
while (cond)
{
Application.DoEvents(); //VERIFY trovare un modo per rimuovere questa porcheria
tag = documentToExtractFrom.GetElementById(id);
if (tag != null)
cond = false;
Thread.Yield();
Thread.SpinWait(100000);
counter++;
if (counter > maxCycles)
return false;
}
return true;
}
(3) 等待 DocumentCompleted 到达的肮脏技巧,因为页面上不需要重新加载框架!
private bool WaitForDocumentCompleted(int seconds)
{
int counter = 0;
while (Program.BrowsingSystem.IsBusy)
{
Application.DoEvents();
Thread.Sleep(1000);
if (counter == seconds)
{
return true;
}
counter++;
}
return true;
}
我还将 DocumentCompleted 方法和导航传递给您,让您全面了解我是如何使用它们的。
private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (Program.BrowsingSystem.BrowserLink.ReadyState == WebBrowserReadyState.Complete)
{
lock (Program.BrowsingSystem.BrowserLocker)
{
Program.BrowsingSystem.ActualPosition = Program.BrowsingSystem.UpdatePosition(Program.BrowsingSystem.Document);
Program.BrowsingSystem.CheckContentAvailability();
Program.BrowsingSystem.IsBusy = false;
}
}
}
private void webBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
lock (Program.BrowsingSystem.BrowserLocker)
{
Program.BrowsingSystem.ActualPosition.PageName = OgamePages.OnChange;
Program.BrowsingSystem.IsBusy = true;
}
}
如果您现在了解此处介绍的实现背后的细节,请查看here 以了解 DoEvents() 背后的混乱情况(希望从 S.Overflow 链接其他站点不是问题) .
最后一点说明,当您从 Form 实例中使用 Navigate 方法时,您需要将调用放在 Invoke 中:这很清楚您需要一个 Invoke,因为需要在webBrowser(或什至将其作为参考变量纳入范围)需要在 webBrowser 本身的同一线程上启动!
此外,如果 WB 是某种 Form 容器的子容器,它还需要实例化它的线程与 Form 创建相同,并且为了传递性,需要在 WB 上工作的所有方法都需要在 Form 线程上调用(在您的情况下,调用会在 Form 本机线程上重新定位您的调用)。
我希望这对你有用(我只是用我的母语在代码中留下了 //VERIFY 注释,让你知道我对 Application.DoEvents() 的看法)。
亲切的问候,
亚历克斯