"Быстрое" отображение в DocumentViewer
Я создаю приложение WPF, в котором мне нужно отобразить предварительный просмотр документов, например, что достижимо с помощью DocumentViewer и DocumentPaginator. Однако преобразование отчета в XPS и загрузка его в DocumentViewer оказалось очень медленным, когда отчет большой (как обычный отчет, который мне нужно отобразить).
Это заставило меня задуматься о том, что, возможно, есть какой-то способ начать показ первых нескольких страниц отчета, в то время как остальные страницы "загружаются" в DocumentViewer - в основном загрузка / отображение страниц по мере их создания.
Кто-нибудь знает, возможно ли что-то подобное? И, если так, как бы вы предложили мне начать работать? Я потратил несколько часов на то, чтобы найти решение для более быстрого отображения отчета, но ничего не нашел.
Ради полного раскрытия, в этом случае отчет, который мне нужно отобразить, создается в HTML. Я знаю, что мне нужно преобразовать его в XPS, чтобы использовать DocumentViewer, но я привожу это к сведению, потому что, если у кого-то есть быстрый способ отображения HTML, пожалуйста, не стесняйтесь поднять это тоже. Я не могу использовать элемент управления WebBrowser, так как мне нужно, чтобы экран отображался в режиме "Предварительный просмотр". Хороший алгоритм принятия решения о том, как "разбить" страницу на HTML, вероятно, приведет меня к решению этой проблемы, а также я смогу создать собственный элемент управления для его отображения. Я бы использовал DocumentPaginator, но тогда выводимый файл - XPS, а затем я возвращаюсь к проблеме DocumentViewer.
Опять же, любая помощь очень ценится. Спасибо!
1 ответ
Хорошо, я думаю, что у меня есть кое-что...
Еще раз я нашел лучший URL для ссылки. Этот файл не загружался для меня сразу, поэтому я взял его из кэша Google: http://webcache.googleusercontent.com/search?q=cache:LgceMCkJBrsJ:joshclose.net/%3Fp%3D247
Определите интерфейс IViewObject, как описано в каждой статье:
[ComVisible(true), ComImport()]
[GuidAttribute("0000010d-0000-0000-C000-000000000046")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IViewObject
{
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int Draw(
[MarshalAs(UnmanagedType.U4)] UInt32 dwDrawAspect,
int lindex,
IntPtr pvAspect,
[In] IntPtr ptd,
IntPtr hdcTargetDev,
IntPtr hdcDraw,
[MarshalAs(UnmanagedType.Struct)] ref Rectangle lprcBounds,
[MarshalAs(UnmanagedType.Struct)] ref Rectangle lprcWBounds,
IntPtr pfnContinue,
[MarshalAs(UnmanagedType.U4)] UInt32 dwContinue);
[PreserveSig]
int GetColorSet([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect,
int lindex, IntPtr pvAspect, [In] IntPtr ptd,
IntPtr hicTargetDev, [Out] IntPtr ppColorSet);
[PreserveSig]
int Freeze([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect,
int lindex, IntPtr pvAspect, [Out] IntPtr pdwFreeze);
[PreserveSig]
int Unfreeze([In, MarshalAs(UnmanagedType.U4)] int dwFreeze);
void SetAdvise([In, MarshalAs(UnmanagedType.U4)] int aspects,
[In, MarshalAs(UnmanagedType.U4)] int advf,
[In, MarshalAs(UnmanagedType.Interface)] IAdviseSink pAdvSink);
void GetAdvise([In, Out, MarshalAs(UnmanagedType.LPArray)] int[] paspects,
[In, Out, MarshalAs(UnmanagedType.LPArray)] int[] advf,
[In, Out, MarshalAs(UnmanagedType.LPArray)] IAdviseSink[] pAdvSink);
}
Создайте класс HtmlPaginator, который создает снимок экрана документа браузера (как описано), но затем обрезает его по страницам / фреймам:
class HtmlPaginator
{
public event EventHandler<PageImageEventArgs> PageReady;
protected virtual void OnPageReady(PageImageEventArgs e)
{
EventHandler<PageImageEventArgs> handler = this.PageReady;
if (handler != null)
handler(this, e);
}
public class PageImageEventArgs : EventArgs
{
public Image PageImage { get; set; }
public int PageNumber { get; set; }
}
public void GeneratePages(string doc)
{
Bitmap htmlImage = RenderHtmlToBitmap(doc);
int pageWidth = 800;
int pageHeight = 600;
int xLoc = 0;
int yLoc = 0;
int pages = 0;
do
{
int remainingHeightOrPageHeight = Math.Min(htmlImage.Height - yLoc, pageHeight);
int remainingWidthOrPageWidth = Math.Min(htmlImage.Width - xLoc, pageWidth);
Rectangle cropFrame = new Rectangle(xLoc, yLoc, remainingWidthOrPageWidth, remainingHeightOrPageHeight);
Bitmap page = htmlImage.Clone(cropFrame, htmlImage.PixelFormat);
pages++;
PageImageEventArgs args = new PageImageEventArgs { PageImage = page, PageNumber = pages };
OnPageReady(args);
yLoc += pageHeight;
if (yLoc > htmlImage.Height)
{
xLoc += pageWidth;
if (xLoc < htmlImage.Width)
{
yLoc = 0;
}
}
}
while (yLoc < htmlImage.Height && xLoc < htmlImage.Width);
}
private static Bitmap RenderHtmlToBitmap(string doc)
{
Bitmap htmlImage = null;
using (var webBrowser = new WebBrowser())
{
webBrowser.ScrollBarsEnabled = false;
webBrowser.ScriptErrorsSuppressed = true;
webBrowser.DocumentText = doc;
while (webBrowser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
webBrowser.Width = webBrowser.Document.Body.ScrollRectangle.Width;
webBrowser.Height = webBrowser.Document.Body.ScrollRectangle.Height;
htmlImage = new Bitmap(webBrowser.Width, webBrowser.Height);
using (Graphics graphics = Graphics.FromImage(htmlImage))
{
var hdc = graphics.GetHdc();
var rect1 = new Rectangle(0, 0, webBrowser.Width, webBrowser.Height);
var rect2 = new Rectangle(0, 0, webBrowser.Width, webBrowser.Height);
var viewObject = (IViewObject)webBrowser.Document.DomDocument;
viewObject.Draw(1, -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, hdc, ref rect1, ref rect2, IntPtr.Zero, 0);
graphics.ReleaseHdc(hdc);
}
}
return htmlImage;
}
}
Назовите это так:
WebBrowser browser = new WebBrowser();
browser.Navigate("http://www.stackru.com");
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
HtmlPaginator pagr = new HtmlPaginator();
pagr.PageReady += new EventHandler<HtmlPaginator.PageImageEventArgs>(pagr_PageReady);
pagr.GeneratePages(browser.DocumentText);
Чтобы проверить это, я реализовал базовую форму с кнопкой, графическим блоком и коллекцией списков. Я добавляю страницы в коллекцию по мере их готовности из HtmlPaginator и использую кнопку, чтобы добавить следующее изображение в поле для картинок.
Магические числа - это желаемая ширина и высота. Я использовал 800x600, но у вас, вероятно, есть другие размеры, которые вы хотите.
Недостатком здесь является то, что вы все еще ждете, когда WebBrowser отобразит HTML, но я действительно не понимаю, как альтернативное решение сократит это время - что-то должно интерпретировать и рисовать HTML в первую очередь. Напишите свой собственный веб-браузер, я думаю.:)
Я попытался поиграть с IViewObject.Draw, чтобы посмотреть, смогу ли я просто визуализировать фреймы страницы напрямую, а не цикл обрезки, но это не сработало для меня.