クリス、
ここで問題を解決する可能性のある実装をお渡ししますが、すべてが期待どおりに機能する前に直面して修正する必要があったというコメントをここで見てください。これは、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 クラスに格納します。
実装と、実際にはケースに依存する (そしてユーザーにとっては役に立たない) コードのロジックとは別に、ユーザーのケースに注意するとよい興味深い要素がいくつかあります。コードにいくつかのコメントを入れて、3 つの重要なポイントを強調しました [記号(1)
,(2)
と(3)
]
アルゴリズムは次のとおりです。
1) ページに到着
2) webBrowser から基になるドキュメントを取得する
3) クリックしてメッセージページに移動する要素を見つける [: で完了HtmlElement ele = doc.GetElementById("message_alert_box");
]
4) MethodInfo インスタンスとリフレクションごとの呼び出しを介してクリックのイベントをトリガーします [これは別のページを呼び出すため、遅かれ早かれ DocumentCompleted が到着します]
5) 完了したドキュメントが呼び出されるのを待ってから続行します [完了:semaphoreForDocCompletedEvent = WaitForDocumentCompleted();
ポイント (1) で]
6) ページが変更された後、webBrowser から新しいドキュメントをフェッチします。
7) 読みたいメッセージがどこにあるかを定義しているページ上の特定のアンカーを見つける
8)そのようなタグがページに存在することを確認してください(AJAXが読み取りたいものの準備を遅らせる可能性があるため)[完了:ele.WaitForAvailability("mailz", Program.BrowsingSystem.Document, 10000)
それがポイント(2)です]
9) 各メッセージを読み取るためのループ全体を実行します。これは、同じページにあるモーダル ダイアログ フォームを開くことを意味するため、DocumentCompleted を生成せず、準備ができたら読み取り、閉じて、再ループします。この特定のケースではsemaphoreForDocCompletedEvent = WaitForDocumentCompleted(6);
、ポイント (3) で呼び出される (1) のオーバーロードを使用します。
ここで、一時停止、確認、および読み取りに使用する 3 つの方法を次に示します。
(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 Methods と Navigating も渡し、それらの使用方法の全体像を示します。
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;
}
}
ここに示した実装の背後にある詳細に気付いている場合は、DoEvents() の背後にある混乱を知るためにここを見てください(S.Overflow から他のサイトにリンクすることが問題にならないことを願っています)。
Form インスタンスから Navigate メソッドを使用する場合、Invoke 内に Navigate メソッドの呼び出しを配置する必要があるという事実についての最後の小さな注意: これは、webBrowser (または webBrowser (または参照変数としてスコープに入れても) webBrowser 自体の同じスレッドで起動する必要があります!
さらに、WB が何らかのフォーム コンテナの子である場合、インスタンス化されるスレッドがフォームの作成と同じである必要があり、推移性のために、WB で動作する必要があるすべてのメソッドを呼び出す必要があります。フォームスレッドで(あなたの場合、呼び出しはフォームネイティブスレッドで呼び出しを再配置します)。これがお役に立てば幸いです (Application.DoEvents() について私が考えていることをお知らせするために、母国語のコードに //VERIFY コメントを残しました)。
敬具、アレックス