- Как сохранить скриншот страницы.
- Как в своем коде обработать событие веб-страницы.
- Как предотвратить запуск Internet Explorer.
- В mshtml нет функций querySelector, querySelectorAll и других, можно ли их использовать в WebBrowser?
Как сохранить скриншот страницы.
Действительно, для большинства элементов управления это делается с помощью метода DrawToBitmap. У WebBrowser этот метод не работает. Точнее работает, но сохраняет не вебстранцу, отображенную в нем, а сам элемент управления, то есть белый прямоугольник.
Раньше можно было использовать для прорисовки любого узла страницы интерфейс
IHTMLElementRender interface (Windows). Утилита tlbimp немного неправильно создавала сигнатуры для него и приходилось создавать в коде исправленную версию интерфейса, выглядело это так.
vb.net |
1
2
3
4
5
| <ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("3050F669-98B5-11CF-BB82-00AA00BDCE0B")>
Interface IHTMLElementRenderFixed
Sub DrawToDC(hdc As IntPtr)
Sub SetDocumentPrinter(bstrPrinterName As String, hdc As IntPtr)
End Interface
|
|
Ну и код, который мог это использовать выглядел примерно так
vb.net |
1
2
3
4
5
6
7
8
9
10
11
| If SaveFileDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
Dim render As IHTMLElementRenderFixed = CType(WebBrowser1.Document.Body.DomElement, IHTMLElementRenderFixed)
Using bmp As New Bitmap(WebBrowser1.ClientSize.Width, WebBrowser1.ClientSize.Height)
Using gr = Graphics.FromImage(bmp)
Dim dc = gr.GetHdc
render.DrawToDC(dc)
bmp.Save(SaveFileDialog1.FileName)
gr.ReleaseHdc(dc)
End Using
End Using
End If
|
|
К сожалению, сейчас (насколько я знаю, начиная с IE9) этот код, хоть и не выдает ошибок, но и не работает, а просто оставляет изображение нетронутым. Компенсировать это обстоятельство, насколько я знаю, нечем (по крайней мере в самом WebBrowser и библиотеке mshtm), поскольку этот способ мог не только скриншот контрола делать, но и для прорисовки отдельного элемента страницы тоже подходил.
Для того, чтобы сделать скриншот экрана нашел вот такой способ.
c# - Converting WebBrowser.Document To A Bitmap? - Stack Overflow
На VB.Net это будет выглядеть так
vb.net |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| Public Class NativeMethods
<ComImport>
<Guid("0000010D-0000-0000-C000-000000000046")>
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Interface IViewObject
Sub Draw(<MarshalAs(UnmanagedType.U4)> dwAspect As UInteger, lindex As Integer, pvAspect As IntPtr, <[In]> ptd As IntPtr, hdcTargetDev As IntPtr, hdcDraw As IntPtr, <MarshalAs(UnmanagedType.Struct)> ByRef lprcBounds As RECT, <[In]> lprcWBounds As IntPtr, pfnContinue As IntPtr, <MarshalAs(UnmanagedType.U4)> dwContinue As UInteger)
End Interface
<StructLayout(LayoutKind.Sequential, Pack:=4)>
Structure RECT
Public Left As Integer
Public Top As Integer
Public Right As Integer
Public Bottom As Integer
End Structure
Public Shared Sub GetImage(obj As Object, destination As Image, backgroundColor As Color)
Using graphics As Graphics = graphics.FromImage(destination)
Dim deviceContextHandle As IntPtr = IntPtr.Zero
Dim rectangle As RECT = New RECT()
rectangle.Right = destination.Width
rectangle.Bottom = destination.Height
graphics.Clear(backgroundColor)
Try
deviceContextHandle = graphics.GetHdc()
Dim viewObject As IViewObject = CType(obj, IViewObject)
viewObject.Draw(1, -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, deviceContextHandle, rectangle, IntPtr.Zero, IntPtr.Zero, 0)
Finally
If deviceContextHandle <> IntPtr.Zero Then
graphics.ReleaseHdc(deviceContextHandle)
End If
End Try
End Using
End Sub
End Class
|
|
Ну и собственно запускается это так
vb.net |
1
2
3
4
5
6
| If SaveFileDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
Using bm As New Bitmap(WebBrowser1.ClientSize.Width, WebBrowser1.ClientSize.Height)
NativeMethods.GetImage(WebBrowser1.ActiveXInstance, bm, Color.White)
bm.Save(SaveFileDialog1.FileName)
End Using
End If
|
|
Метод прекрасно работает, но сохраняет сам элемент управления и видимую часть страницы. Если же надо работать с отдельными фрагментами, то видимо WebBrowser тут не подойдет и лучше использовать что-то другое. Вот кое-какая информация к размышлению на эту тему
Awesomium.NET - Home
HTML Renderer - Home
C#: WebBrowser vs Gecko vs Awesomium vs OpenWebKitSharp: What To Choose And How to Use - CodeProject
Кроме того для получения изображения всего документа (разбитого на страницы) можно использовать методы Print и ShowPrintDialog. В системе Windows есть виртуальный принтер, печатающий в xps-документ. Пакет Microsoft Office устанавливает виртуальный принтер, печатающий заметку OneNote. Есть и другие виртуальные принтеры, которые также можно установить. Обычно они не добавляют команду Print к HTML-файлам и просто вызвать печать их с помощью передачи этой команды Process.Start вместе с объектом ProcessStartInfo не получится. А WebBrowser вполне решает проблему печати. Приведу примерный код печати документа без визуального представления браузера
vb.net |
1
2
3
4
5
6
7
8
9
| Sub PrintHtml(htmlCode As String)
Dim browser As New WebBrowser
AddHandler browser.DocumentCompleted,
Sub(s, e)
browser.Print()
browser.Dispose()
End Sub
browser.DocumentText = htmlCode
End Sub
|
|
Если браузер отображается на форме, то надо просто вызвать метод Print для печати на принтере по умолчанию, или ShowPrintDialog, если принтер надо выбрать.
Как в своем коде обработать событие веб-страницы.
Для начала самый простой вариант. Добавляем в проект веб-страницу следующего содержания.
HTML5 |
1
2
3
4
5
6
7
8
9
10
11
| <!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<button id="button1">Кнопка 1</button>
</body>
</html>
|
|
Настраиваем свойства элемента проекта, чтобы страница копировалась в каталог исполняемого файла. Создаем переменную с путем к странице.
vb.net |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| Dim pagePath = IO.Path.Combine(Application.StartupPath, "PageEventHandler.html")
Private Sub PageEventHandlerForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
WebBrowser1.ScriptErrorsSuppressed = True
WebBrowser1.Navigate(pagePath)
End Sub
Private Sub WebBrowser1_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
Dim button1 = WebBrowser1.Document.GetElementById("button1")
' Имя события надо указывать с префиксом on
button1.AttachEventHandler("onclick", AddressOf WebPage_Button1_Click)
End Sub
Sub WebPage_Button1_Click(sender As Object, e As EventArgs)
MsgBox("Нажата кнопка 1")
End Sub
|
|
Запускаем, нажимаем, получаем сообщение, радуемся... не долго. Если заменить код обработчика на такой
vb.net |
1
| MsgBox(CType(sender, HtmlElement).InnerText)
|
|
то вопреки ожиданиям ничего не произойдет. А если обработать ошибку, то можно получить NullReferenceException. То есть узнать, через sender, какой именно объект на странице создал событие не получится.
Это плохая новость, поскольку на веб странице удобно обрабатывать события группы элементов, добавив обработчик ближайшему общему контейнеру и там уже в обработчике можно определить из аргументов события, какой именно элемент его инициировал. Но проблема решается благодаря тому, что в IE информация о событии передается в обработчик не через аргументы, а через свойство event объекта window. Изменим код страницы следующим образом.
HTML5 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| <!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<button id="button1">Кнопка 1</button>
<div id="buttonrange">
<button>Кнопка 2</button>
<button>Кнопка 3</button>
<button>Кнопка 4</button>
<button>Кнопка 5</button>
<button>Кнопка 6</button>
</div>
</body>
</html>
|
|
Изменим код добавления события и создадим обработчик кликов всех новых кнопок
vb.net |
1
2
3
4
5
6
7
8
9
10
11
12
13
| Private Sub WebBrowser1_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
Dim button1 = WebBrowser1.Document.GetElementById("button1")
button1.AttachEventHandler("onclick", AddressOf WebPage_Button1_Click)
Dim buttonrange = WebBrowser1.Document.GetElementById("buttonrange")
buttonrange.AttachEventHandler("onclick", AddressOf WebPage_Buttonrange_Click)
End Sub
Sub WebPage_Buttonrange_Click(sender As Object, e As EventArgs)
Dim window = WebBrowser1.Document.Window.DomWindow
Dim btn = window.[event].srcElement
MsgBox("Нажата " & btn.innerText)
End Sub
|
|
Теперь можно нажимать любую кнопку и получать сообщение с текстом этой кнопки.
Ну, уж коль скоро дело все равно не обошлось без DomWindow и позднего связывания, то видимо совсем не лишним будет объяснить, как эту задачу решить с использованием библиотеки mshtml.
Подключаем библиотеку к проекту(либо на вкладке .Net можно найти одну из готовых сборок взаимодействия, либо на вкладке COM, тогда сборка взаимодействия будет сгенерирована специально для проекта, на вкладке COM она назыается Microsoft HTML Object Library).
Теперь изменим код следующим образом
vb.net |
1
2
3
4
5
6
7
8
9
10
11
12
| Private Sub WebBrowser1_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
Dim button1 = WebBrowser1.Document.GetElementById("button1")
button1.AttachEventHandler("onclick", AddressOf WebPage_Button1_Click)
Dim buttonrange = CType(WebBrowser1.Document.GetElementById("buttonrange").DomElement, mshtml.HTMLElementEvents2_Event)
AddHandler buttonrange.onclick, AddressOf ButtonRange_onclick
End Sub
Private Function ButtonRange_onclick(pEvtObj As mshtml.IHTMLEventObj) As Boolean
Dim e As mshtml.IHTMLEventObj2 = CType(pEvtObj, mshtml.IHTMLEventObj2)
MsgBox(e.srcElement.innerText)
Return True
End Function
|
|
Таким образом мы получаем подсказки по типам и их членам, контроль типов на этапе разработки, но при этом придется много думать о том, где какой тип использовать. Например в данном коде мы привели div к типу mshtml.HTMLElementEvents2_Event, если бы это была кнопка , то понадобился бы тип mshtml.HTMLButtonElementEvents2_Event (хотя возможно и этот бы тоже подошел). В обработчике нам пришлось привести объект аргументов события к другому интерфейсу, чтобы получить доступ к интересующему нас свойству. И вся работа с типами этой библиотеки сводится к подобным манипуляциям, поэтому иногда проще написать код, не указывая типов.
Как предотвратить запуск Internet Explorer.
Проблема возникает когда на странице пользователь либо кликает ссылку, которая должна открыться в новом окне, либо выполняется код window.open(), либо когда пользователь из контекстного меню ссылки выбирает "Открыть в новом окне", либо использует "горячие клавиши" для той же цели. Когда это происходит в IE, то вроде все нормально, а вот когда окно IE открывается при действиях пользователя в другом приложении, использующем WebBrowser, то это уже выглядит совсем не комильфо.
Решается проблема довольно просто - в обработчике события NewWindow. Там можно просто запретить открытие нового окна и выполнить вместо этого какие-то другие действия.
vb.net |
1
2
3
4
5
6
7
8
| Private Sub WebBrowser1_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
Dim links = (From l As HtmlElement In WebBrowser1.Document.GetElementsByTagName("a")).ToArray
Array.ForEach(links, Sub(a) a.SetAttribute("target", "_blank"))
End Sub
Private Sub WebBrowser1_NewWindow(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles WebBrowser1.NewWindow
e.Cancel = True
End Sub
|
|
В данном примере при загрузке документа всем ссылкам добавляется атрибут target="_blank", благодаря чему они должны открываться в новом окне и в этом случае открылось бы окно Internet Explorer, если бы не обработчик события NewWindow, в котором мы запретили открытие новых окон.
Но тут возникает вопрос: а как же быть со ссылкой? Ведь хотелось бы, чтобы она открывалась или в новой вкладке или в другом окне, но этого же приложения, или, на худой конец в этом же окне. Это можно сделать, если знать адрес, который должен был открыться в новом окне. Но, увы - это событие нам не дает такой информации. Есть "топорное" решение этой проблемы - использовать в качестве адреса текст статусбара
vb.net |
1
| OpenNewTab(WebBrowser1.Document.Window.StatusBarText)
|
|
Обычно это работает, поскольку нечасто встречаются страницы, на которых при наведении курсора на ссылку в статусбаре появляется что-то другое кроме ее адреса. Но такое вполне возможно. Кроме того возможно программное открытие окна и клик по ссылке без наведения мыши (можно найти ссылку табулятором или клавишами со стрелками). Так что этот способ, хоть и можно иногда использовать, но все-таки лучше найти что-нибудь понадежнее.
Для решения этой проблемы нам понадобится добавить в на панель инструментов WebBrowser из вкладки COM. В принципе его можно даже и не использовать, но добавить на форму надо для того, чтобы вместе с ним в проекте появились сборки взаимодействия, в которых определено все, что нам понадобится для решения задачи.
Итак в контекстном меню Панели Элементов выбираем пункт "Выбрать элементы". В открывшемся диалоговом окне переходим на вкладку COM-компоненты и устанавливаем флажок на элементе Microsoft Web Browser. Нажимаем OK и элемент появится на панели. Далее его надо перетащить на форму. Сам Control лучше не использовать, поскольку в нем есть много неудобств из-за отсутствия многих привычных свойств (таких как Dock например). Но при перетаскивании его на форму в проект добавляются две сборки: AxInterop.SHDocVw и Interop.SHDocVw. Они-то нам и нужны, без них ничего не получится.
Теперь COM-браузер можно убрать с формы и обработать событие "обычного" браузера. Код примерно такой.
vb.net |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| Private Sub WebBrowser1_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
Dim links = (From l As HtmlElement In WebBrowser1.Document.GetElementsByTagName("a")).ToArray
Array.ForEach(links, Sub(a) a.SetAttribute("target", "_blank"))
Dim axbr As SHDocVw.WebBrowser = CType(WebBrowser1.ActiveXInstance, SHDocVw.WebBrowser)
AddHandler axbr.NewWindow3, AddressOf WebBrower1_NewWindow3
End Sub
Private Sub WebBrower1_NewWindow3(ByRef ppDisp As Object, ByRef Cancel As Boolean, dwFlags As UInteger, bstrUrlContext As String, bstrUrl As String)
Cancel = True
Dim tpage As New TabPage
Dim br As New WebBrowser
tpage.Controls.Add(br)
br.Dock = DockStyle.Fill
br.Navigate(bstrUrl)
br.ScriptErrorsSuppressed = True
TabControl1.TabPages.Add(tpage)
Dim axbr As SHDocVw.WebBrowser = br.ActiveXInstance
AddHandler axbr.NewWindow3, AddressOf WebBrower1_NewWindow3
AddHandler br.DocumentCompleted,
Sub(sender As Object, ea As WebBrowserDocumentCompletedEventArgs)
tpage.Text = CType(sender, WebBrowser).Document.Title
End Sub
End Sub
|
|
На форме TabControl с WebBrowser и вместо открытия IE будет появляться новая вкладка.
В mshtml нет функций querySelector, querySelectorAll и других, можно ли их использовать в WebBrowser?
Использовать можно, но только через скрипты. Как это работает - не знаю, скорей всего они реализованы на уровне движка JavaScript. Для часто используемых функций можно создать расширения управляемых классов.
vb.net |
1
2
3
4
| <Extension>
Public Function QuerySelector(document As HtmlDocument, selector As String)
Return document.InvokeScript("eval", New Object() {String.Format("document.querySelector(""{0}"")", selector)})
End Function
|
|
Единственное, что надо понимать, что это может не работать, если код страницы отображается в режиме совместимости со старыми версиями. Если свои страницы(например запущенные локально) не поддерживают новых возможностей, то иногда приходится явно указывать, с какими версиями должна быть совместима страница.
HTML5 |
1
| <meta http-equiv="x-ua-compatible" content="IE=9;IE=10;IE=11">
|
|
Комментариев нет :
Отправить комментарий