HTML - Как да разбера кога всички рамки са заредени?

Използвам контрола на .NET WebBrowser. Как да разбера кога една уеб страница е напълно заредена?

Искам да знам кога браузърът не извлича повече данни. (Моментът, в който IE пише „Готово“ в лентата на състоянието...).

Бележки:

  • Събитията DocumentComplete/NavigateComplete може да се появят многократно за уеб сайт, съдържащ множество рамки.
  • Състоянието на готовност на браузъра също не решава проблема.
  • Опитах се да проверя броя на кадрите в колекцията от рамки и след това да преброя колко пъти получавам събитие DocumentComplete, но това също не работи.
  • this.WebBrowser.IsBusy също не работи. Винаги е „невярно“, когато го проверявате в манипулатора за завършен документ.

person Yuval Peled    schedule 23.03.2009    source източник


Отговори (12)


Ето как реших проблема в моето приложение:

private void wbPost_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    if (e.Url != wbPost.Url)
        return;
    /* Document now loaded */
}
person Daniel Stutzbach    schedule 24.02.2010
comment
Ако направите напр. щракване в лента за навигация и това води до презареждане на нов уеб сайт в рамка/iframe, няма да сте доволни от това решение. - person phse; 04.07.2014

Моят подход да правя нещо, когато страницата е напълно заредена (включително рамки) е нещо подобно:

using System.Windows.Forms;
    protected delegate void Procedure();
    private void executeAfterLoadingComplete(Procedure doNext) {
        WebBrowserDocumentCompletedEventHandler handler = null;
        handler = delegate(object o, WebBrowserDocumentCompletedEventArgs e)
        {
            ie.DocumentCompleted -= handler;
            Timer timer = new Timer();
            EventHandler checker = delegate(object o1, EventArgs e1)
            {
                if (WebBrowserReadyState.Complete == ie.ReadyState)
                {
                    timer.Dispose();
                    doNext();
                }
            };
            timer.Tick += checker;
            timer.Interval = 200;
            timer.Start();
        };
        ie.DocumentCompleted += handler;
    }

От другите си подходи научих някои „не“:

  • не се опитвайте да огънете лъжицата ... ;-)
  • не се опитвайте да създавате сложна конструкция, използвайки събития DocumentComplete, Frames, HtmlWindow.Load. Вашето решение ще бъде крехко, ако изобщо работи.
  • не използвайте System.Timers.Timer вместо Windows.Forms.Timer, ако го направите, ще започнат да се появяват странни грешки на странни места, поради това, че таймерът работи на различна нишка от останалата част от приложението ви.
  • не използвайте само Timer без DocumentComplete, защото той може да се задейства преди страницата ви дори да започне да се зарежда и ще изпълни кода ви преждевременно.
person Kamil Szot    schedule 31.01.2010

Ето моята тествана версия. Просто направете това свой DocumentCompleted Event Handler и поставете кода, който искате да бъде извикан само веднъж в метода OnWebpageReallyLoaded(). Ефективно този подход определя кога страницата е била стабилна за 200 ms и след това върши работата си.

// event handler for when a document (or frame) has completed its download
Timer m_pageHasntChangedTimer = null;
private void webBrowser_DocumentCompleted( object sender, WebBrowserDocumentCompletedEventArgs e ) {
    // dynamic pages will often be loaded in parts e.g. multiple frames
    // need to check the page has remained static for a while before safely saying it is 'loaded'
    // use a timer to do this

    // destroy the old timer if it exists
    if ( m_pageHasntChangedTimer != null ) {
        m_pageHasntChangedTimer.Dispose();
    }

    // create a new timer which calls the 'OnWebpageReallyLoaded' method after 200ms
    // if additional frame or content is downloads in the meantime, this timer will be destroyed
    // and the process repeated
    m_pageHasntChangedTimer = new Timer();
    EventHandler checker = delegate( object o1, EventArgs e1 ) {
        // only if the page has been stable for 200ms already
        // check the official browser state flag, (euphemistically called) 'Ready'
        // and call our 'OnWebpageReallyLoaded' method
        if ( WebBrowserReadyState.Complete == webBrowser.ReadyState ) {
            m_pageHasntChangedTimer.Dispose();
            OnWebpageReallyLoaded();
        }
    };
    m_pageHasntChangedTimer.Tick += checker;
    m_pageHasntChangedTimer.Interval = 200;
    m_pageHasntChangedTimer.Start();
}

OnWebpageReallyLoaded() {
    /* place your harvester code here */
}
person Daniel Collicott    schedule 13.04.2010

Какво ще кажете да използвате javascript във всеки кадър, за да зададете флаг, когато кадърът е завършен, и след това C# да прегледа флаговете?

person mbeckish    schedule 23.03.2009
comment
Не искам да манипулирам DOM дървото на всеки сайт, към който навигира браузърът. Но да предположим, че използвам вашето решение, как да го направя в javascript? - person Yuval Peled; 25.03.2009
comment
Не виждам предимството да правя това в JS срещу C#. - person i_am_jorf; 26.03.2009

Не съм сигурен, че ще работи, но опитайте да добавите събитие за „onload“ на JavaScript към вашия фреймсет по следния начин:

function everythingIsLoaded() { alert("everything is loaded"); }
var frameset = document.getElementById("idOfYourFrameset");
if (frameset.addEventListener)
    frameset.addEventListener('load',everythingIsLoaded,false); 
else
    frameset.attachEvent('onload',everythingIsLoaded); 
person paulgreg    schedule 25.03.2009
comment
Искам да мога да знам дали всички рамки са заредени за който и да е уеб сайт, така че не знам кои рамки съдържа. - person Yuval Peled; 26.03.2009
comment
Трябва да направите това на фреймсет (родител на всички рамки), а не на всеки кадър. Доста лесно е да го получите от всеки уеб сайт като този: document.getElementsByTagName('frameset')[0] - person paulgreg; 26.03.2009

Можете ли да използвате jQuery? Тогава можете лесно да обвържете събития, готови за кадри, към целевите кадри. Вижте този отговор за указания. Тази публикация в блог също има дискусия за това. Накрая има добавка, която можете да използвате.

Идеята е да преброите броя на кадрите в уеб страницата, като използвате:

$("iframe").size()

и след това преброявате колко пъти е било задействано събитието за готовност на iframe.

person kgiannakakis    schedule 26.03.2009

Ще получите събитие BeforeNavigate и DocumentComplete за външната уеб страница, както и за всеки кадър. Знаете, че сте готови, когато получите събитието DocumentComplete за външната уеб страница. Трябва да можете да използвате управлявания еквивалент на IWebBrowser2:: TopLevelContainer(), за да определи това.

Внимавайте обаче, самият уебсайт може да задейства повече рамкови навигации, когато пожелае, така че никога не знаете дали дадена страница наистина е готова завинаги. Най-доброто, което можете да направите, е да поддържате броя на всички BeforeNavigates, които виждате, и да намалявате броя, когато получите DocumentComplete.

Редактиране: Ето управляваните документи: TopLevelContainer.

person i_am_jorf    schedule 26.03.2009
comment
Опитах се да преброя предишните навигации и завършения документ в контролата на WebBrowser. Не е синхронизирано... :(. Има повече преди навигация, отколкото завършен документ. [Може би е свързано с кеширане или дублирани кадри, които се извличат. Не знам]. - person Yuval Peled; 26.03.2009
comment
Относно събитието за завършване на документа: в C# WebBrowser не получавате обекта на документа, който току-що е завършил зареждането. Само url. Така че не можете да стигнете до неговия контейнер на браузъра. - person Yuval Peled; 26.03.2009

Ето какво най-накрая проработи при мен:

       public bool WebPageLoaded
    {
        get
        {
            if (this.WebBrowser.ReadyState != System.Windows.Forms.WebBrowserReadyState.Complete)
                return false;

            if (this.HtmlDomDocument == null)
                return false;

            // iterate over all the Html elements. Find all frame elements and check their ready state
            foreach (IHTMLDOMNode node in this.HtmlDomDocument.all)
            {
                IHTMLFrameBase2 frame = node as IHTMLFrameBase2;
                if (frame != null)
                {
                    if (!frame.readyState.Equals("complete", StringComparison.OrdinalIgnoreCase))
                        return false;

                }
            }

            Debug.Print(this.Name + " - I think it's loaded");
            return true;
        }
    }

При всяко събитие за завършване на документ преминавам през целия html елемент и проверявам всички налични рамки (знам, че може да се оптимизира). За всяка рамка проверявам дали е готова. Това е доста надеждно, но точно както jeffamaphone каза, вече съм виждал сайтове, които са задействали някои вътрешни опреснявания. Но горният код задоволява нуждите ми.

Редактиране: всеки кадър може да съдържа кадри в него, така че мисля, че този код трябва да се актуализира, за да проверява рекурсивно състоянието на всеки кадър.

person Yuval Peled    schedule 26.03.2009

Просто използвам метода webBrowser.StatusText. Когато пише "Готово" всичко се зарежда! Или пропускам нещо?

person Jeppoo    schedule 30.03.2010

Проверката за IE.readyState = READYSTATE_COMPLETE трябва да работи, но ако това не се окаже надеждно за вас и буквално искате да знаете „момента, когато IE пише „Готово“ в лентата на състоянието си“, тогава можете да направите цикъл, докато IE.StatusText съдържа "Свършен".

person thdoan    schedule 03.11.2011

Опитвали ли сте WebBrowser.IsBusy property?

person Anand Shah    schedule 23.03.2009
comment
да Уеб браузърът твърди, че не е зает всеки път, когато се извика манипулаторът за пълен документ... - person Yuval Peled; 23.03.2009

Нямам алтернатива за вас, но се чудя дали свойството IsBusy е true по време на манипулатора за завършен документ е защото манипулаторът все още работи и следователно контролата WebBrowser е технически все още „заета“.

Най-простото решение би било да имате цикъл, който се изпълнява на всеки 100 ms или така, докато флагът IsBusy не бъде нулиран (с максимално време за изпълнение в случай на грешки). Това разбира се предполага, че IsBusy няма да бъде зададено на false в нито един момент по време на зареждането на страницата.

Ако манипулаторът Document Complete се изпълнява на друга нишка, можете да използвате заключване, за да изпратите основната си нишка в режим на заспиване и да я събудите от нишката Document Complete. След това проверете флага IsBusy, повторното заключване на основната нишка е все още true.

person roryf    schedule 23.03.2009
comment
Но IsBusy е настроен на false твърде рано. Например, ако имате шест кадъра в уеб страница, когато първият кадър завърши зареждането, IsBusy е невярно при събитие DocumentComplete. - person Yuval Peled; 23.03.2009
comment
Всеки кадър получава свой собствен уеббраузър (IWebBrowser2 реализация). Вероятно атрибутът IsBusy се отнася само за конкретния кадър. И когато е завършен, вече не е зает. - person i_am_jorf; 26.03.2009