从Windows应用商店应用中的WebView打印静态内容

时间:2014-09-29 22:16:26

标签: c# printing webview windows-store-apps brush

更新3

现在有一个关于这个问题的赏金。示例项目和问题简介位于 TL; DR; 下的页面底部,因此请保存一些阅读并跳到最后!

...

我一直试图通过NavigateToString使用静态加载页面从JerryNixon获得这个非常详细,有用的回答。我不明白为什么这不起作用,但不可否认的是,部分代码绝对令我感到困惑。

原始答案是here,但为了完整起见,我会在这里复制代码。 Jerry为想要从WebView

打印的人提供以下示例/解决方案
<Grid Background="Blue">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="995" />
        <ColumnDefinition Width="300" />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <WebView Grid.Column="0" x:Name="MyWebView" Source="http://www.stackoverflow.com" HorizontalAlignment="Right" />
    <Rectangle Grid.Column="1" x:Name="MyWebViewRectangle" Fill="Red" />
    <ScrollViewer Grid.Column="2" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <ItemsControl x:Name="MyPrintPages" VerticalAlignment="Top" HorizontalAlignment="Left">
            <Rectangle Height="150" Width="100" Fill="White" Margin="5" />
            <Rectangle Height="150" Width="100" Fill="White" Margin="5" />
            <Rectangle Height="150" Width="100" Fill="White" Margin="5" />
            <Rectangle Height="150" Width="100" Fill="White" Margin="5" />
            <Rectangle Height="150" Width="100" Fill="White" Margin="5" />
        </ItemsControl>
    </ScrollViewer>
</Grid>

public MainPage()
{
    this.InitializeComponent();
    MyWebView.LoadCompleted += MyWebView_LoadCompleted;
}

void MyWebView_LoadCompleted(object sender, NavigationEventArgs e)
{
    MyWebViewRectangle.Fill = GetWebViewBrush(MyWebView);
    MyPrintPages.ItemsSource = GetWebPages(MyWebView, new Windows.Foundation.Size(100d, 150d));
    MyWebView.Visibility = Windows.UI.Xaml.Visibility.Visible;
}

WebViewBrush GetWebViewBrush(WebView webView)
{
    // resize width to content
    var _OriginalWidth = webView.Width;
    var _WidthString = webView.InvokeScript("eval",
        new[] { "document.body.scrollWidth.toString()" });
    int _ContentWidth;
    if (!int.TryParse(_WidthString, out _ContentWidth))
        throw new Exception(string.Format("failure/width:{0}", _WidthString));
    webView.Width = _ContentWidth;

    // resize height to content
    var _OriginalHeight = webView.Height;
    var _HeightString = webView.InvokeScript("eval",
        new[] { "document.body.scrollHeight.toString()" });
    int _ContentHeight;
    if (!int.TryParse(_HeightString, out _ContentHeight))
        throw new Exception(string.Format("failure/height:{0}", _HeightString));
    webView.Height = _ContentHeight;

    // create brush
    var _OriginalVisibilty = webView.Visibility;
    webView.Visibility = Windows.UI.Xaml.Visibility.Visible;
    var _Brush = new WebViewBrush
    {
        SourceName = webView.Name,
        Stretch = Stretch.Uniform
    };
    _Brush.Redraw();

    // reset, return
    webView.Width = _OriginalWidth;
    webView.Height = _OriginalHeight;
    webView.Visibility = _OriginalVisibilty;
    return _Brush;
}

IEnumerable<FrameworkElement> GetWebPages(WebView webView, Windows.Foundation.Size page)
{
    // ask the content its width
    var _WidthString = webView.InvokeScript("eval",
        new[] { "document.body.scrollWidth.toString()" });
    int _ContentWidth;
    if (!int.TryParse(_WidthString, out _ContentWidth))
        throw new Exception(string.Format("failure/width:{0}", _WidthString));
    webView.Width = _ContentWidth;

    // ask the content its height
    var _HeightString = webView.InvokeScript("eval",
        new[] { "document.body.scrollHeight.toString()" });
    int _ContentHeight;
    if (!int.TryParse(_HeightString, out _ContentHeight))
        throw new Exception(string.Format("failure/height:{0}", _HeightString));
    webView.Height = _ContentHeight;

    // how many pages will there be?
    var _Scale = page.Width / _ContentWidth;
    var _ScaledHeight = (_ContentHeight * _Scale);
    var _PageCount = (double)_ScaledHeight / page.Height;
    _PageCount = _PageCount + ((_PageCount > (int)_PageCount) ? 1 : 0);

    // create the pages
    var _Pages = new List<Windows.UI.Xaml.Shapes.Rectangle>();
    for (int i = 0; i < (int)_PageCount; i++)
    {
        var _TranslateY = -page.Height * i;
        var _Page = new Windows.UI.Xaml.Shapes.Rectangle
        {
            Height = page.Height,
            Width = page.Width,
            Margin = new Thickness(5),
            Tag = new TranslateTransform { Y = _TranslateY },
        };
        _Page.Loaded += (s, e) =>
        {
            var _Rectangle = s as Windows.UI.Xaml.Shapes.Rectangle;
            var _Brush = GetWebViewBrush(webView);
            _Brush.Stretch = Stretch.UniformToFill;
            _Brush.AlignmentY = AlignmentY.Top;
            _Brush.Transform = _Rectangle.Tag as TranslateTransform;
            _Rectangle.Fill = _Brush;
        };
        _Pages.Add(_Page);
    }
    return _Pages;
}

我唯一的更改是从网页浏览中移除Source属性,并将此行添加到MainPage()方法

var html = await Windows.Storage.PathIO.ReadTextAsync("ms-appx:///Assets/sherlock.html");
MyWebView.NavigateToString(html);

Sherlock.html是Arthur Conan Doyle故事的摘录, The Red-Headed League ,此HTML页面的完整代码为here - 请注意接近结尾的片段重复几次。这是在测试中完成的,我将在下面解释

这个样本最初在我的小文件上工作。 (没有填充章节的Sherlock.html)

但是当我超过某个文件大小(通常大约4000字+)时,样本会出现以下行为。

WebView缩小到非常狭窄的视图

(请参阅更新说明,说明为什么会被删除)

然后屏幕变灰。 (我觉得发布一个灰色屏幕截图有点傻,但我试图在这里彻底了)

enter image description here

该应用不会崩溃。不显示调试消息,不会发生异常。 就好像只删除Grid一样。 。它不是启动画面(我设置为蓝色),它不是应用程序背景(深灰色)。它好像在页面顶部加载了其他内容。

我已尝试调整代码的每个部分,但我似乎无法找出导致问题的原因。此外,这段代码只是让我感到困惑。例如,在Load_Completed方法

MyWebViewRectangle.Fill = GetWebViewBrush(MyWebView);
MyPrintPages.ItemsSource = GetWebPages(MyWebView, new Windows.Foundation.Size(100d, 150d));
MyWebView.Visibility = Windows.UI.Xaml.Visibility.Visible;

第1行:MyWebViewRectangle充满了WebView的镜头。为什么?永远不会再次引用此控件。我想这可能是为了让用户更清楚地了解页面,但它似乎没有任何编程目的

第3行:MyWebView.Visibility设置为Visible这会在GetWebViewBrush稍后再次发生。

var _OriginalVisibilty = webView.Visibility;
webView.Visibility = Windows.UI.Xaml.Visibility.Visible;
// and a few lines later... 
webView.Visibility = _OriginalVisibilty;

据我所知,网页视图从不Collapsed,但设置为Visible三次。

如果有人能向我解释这些(甚至可能是JerryNixon本人?)我会真的欣赏它。我花了几个月的时间在一个应用程序上工作,这是这个难题的最后一部分。我没有能力打印就无法启动!

更新

今天早上做了一些发现。我一直在设置XAML中WebView的宽度。如果没有这个,即使最小的文件也会失败。基于这个发现,我将html文件本身的宽度设置为800px,并且它有效。万岁,问题解决了吧?很不幸的是,不行。我再次尝试使用更长的文件(完全版本的 The Red-headed league here),同样的问题再次发生。它现在变得非常强大。

我在GetWebPages方法的webView.Height = _ContentHeight;处插入了一个断点。这告诉我_ContentHeight正是&#34; 13241&#34;。

但是,插入断点会使代码工作。一旦我打破并继续,这一切似乎加载。虽然奇怪的是,在我将鼠标悬停在滚动条上之前,WebView是不可见的。

如果我删除断点,我会再次看到灰色屏幕。

如果我手动将_ContentHeight设置为13230,则会加载页面。这似乎是一个神奇的数字。任何高于此值的页面都会失败。但是,如果我将html的宽度更改为小于800px,它也会失败。从逻辑上讲,有一种魔法比例为800:13230像素(或1:16.5375)。如果我超过这个比例,我就会掀起死亡的灰色屏幕。我可以禁用红色矩形并将GetWebPages限制为3页,灰色屏幕仍然存在。这告诉我问题出在WebView本身

如果设置断点,问题就会消失。

还要注意MyWebViewRectangle中的额外空格和MyPrintPages中的额外空白页:

Working result, but with extra whitespace and blank pages appended

注意:我还添加了NateDiamond对事件LongRunningScriptDetectedUnsafeContentWarningDisplayingUnviewableContentIdentified

的建议

这是我的示例项目的完整来源: https://onedrive.live.com/redir?resid=EE99EF9560A6740E!110509&authkey=!ACrZgm6uq5k5yck&ithint=file%2czip

更新2

取得了一些成功。

我将一些标题放入我的示例文件中(通过反复试验找到每个计算页面的&#34;底部&#34;)并增加页面大小以便我可以看到标记。虽然有9页,但只有5页和6小部分被渲染。 9页面对象由GetWebPages创建,但只有5ish已呈现HTML内容。其余的都是空的

nothing past page 5

(注意,我改变了背景颜色,因为蓝色的红色给了我一个偏头痛)

我已经完成了代码并进行了一些修改,现在我根本不再获得灰色屏幕了。但是,我每运行3次中大约有2次,只渲染第一页,然后进行裁剪。这似乎是完全随机的。如上所述,当呈现9个页面时,并非所有页面都具有内容。

以下是我的更改:

添加了新方法Resize_WebView()

public void ResizeWebView()
{
    OriginalHeight = MyWebView.Height;

    var _WidthString = MyWebView.InvokeScript("eval", new[] { "document.body.scrollWidth.toString()" });
    int _ContentWidth;
    if (!int.TryParse(_WidthString, out _ContentWidth))
        throw new Exception(string.Format("failure/width:{0}", _WidthString));
    MyWebView.Width = _ContentWidth;

    // ask the content its height
    var _HeightString = MyWebView.InvokeScript("eval", new[] { "document.body.scrollHeight.toString()" });
    int _ContentHeight;
    if (!int.TryParse(_HeightString, out _ContentHeight))
        throw new Exception(string.Format("failure/height:{0}", _HeightString));
    MyWebView.Height = _ContentHeight;
}
MyWebView_LoadCompleted中的

注释掉矩形填充,添加了对ResizeWebView()的调用:

void MyWebView_LoadCompleted(object sender, NavigationEventArgs e)
{
    ResizeWebView();

    //MyWebViewRectangle.Fill = GetWebViewBrush(MyWebView);
    MyPrintPages.ItemsSource = GetWebPages(MyWebView, new Windows.Foundation.Size(300d, 450d));
}
GetMyWebPages中的

更改了画笔加载的位置:

IEnumerable<FrameworkElement> GetWebPages(WebView webView, Windows.Foundation.Size page) 
{
    // ask the content its width
    /*var _WidthString = webView.InvokeScript("eval",
        new[] { "document.body.scrollWidth.toString()" });
    int _ContentWidth;
    if (!int.TryParse(_WidthString, out _ContentWidth))
        throw new Exception(string.Format("failure/width:{0}", _WidthString));
    webView.Width = _ContentWidth;

    // ask the content its height
    var _HeightString = webView.InvokeScript("eval",
        new[] { "document.body.scrollHeight.toString()" });
    int _ContentHeight;
    if (!int.TryParse(_HeightString, out _ContentHeight))
        throw new Exception(string.Format("failure/height:{0}", _HeightString));
    webView.Height = _ContentHeight;

    // Manually set _ContentHeight: comment out the above block and uncomment this to test. 
    // int _ContentHeight = 13230;
    // webView.Height = _ContentHeight;*/

    // how many pages will there be?
    //var _Scale = page.Width / _ContentWidth;
    var _Scale = page.Width / webView.Width; // now sourced directly from webview
    //var _ScaledHeight = (_ContentHeight * _Scale); 
    var _ScaledHeight = (webView.Height * _Scale); // now sourced directly from webview
    var _PageCount = (double)_ScaledHeight / page.Height;
    _PageCount = _PageCount + ((_PageCount > (int)_PageCount) ? 1 : 0);

    // create the pages
    var _Pages = new List<Windows.UI.Xaml.Shapes.Rectangle>();
    for (int i = 0; i < (int)_PageCount; i++)
    {
        var _TranslateY = -page.Height * i;
        var _Page = new Windows.UI.Xaml.Shapes.Rectangle
        {
            Height = page.Height,
            Width = page.Width,
            Margin = new Thickness(5),
            Tag = new TranslateTransform { Y = _TranslateY },
        };
        var _Brush = GetWebViewBrush(webView);
        _Brush.Stretch = Stretch.UniformToFill;
        _Brush.AlignmentY = AlignmentY.Top;
        _Brush.Transform = _Page.Tag as TranslateTransform;
        _Page.Fill = _Brush;
        /*_Page.Loaded += async (s, e) =>
        {
            var _Rectangle = s as Windows.UI.Xaml.Shapes.Rectangle;
            var _Brush = await GetMyWebViewBrush(webView);
            _Brush.Stretch = Stretch.UniformToFill;
            _Brush.AlignmentY = AlignmentY.Top;
            _Brush.Transform = _Rectangle.Tag as TranslateTransform;
            _Rectangle.Fill = _Brush;
        };*/
        _Pages.Add(_Page);
    }
    return _Pages;
}

GetWebView

删除了现在不必要的调整大小代码
WebViewBrush GetWebViewBrush(WebView webView)
{
    /*// resize width to content
    var _OriginalWidth = webView.Width;
    var _WidthString = webView.InvokeScript("eval",
        new[] { "document.body.scrollWidth.toString()" });
    int _ContentWidth;
    if (!int.TryParse(_WidthString, out _ContentWidth))
        throw new Exception(string.Format("failure/width:{0}", _WidthString));
    webView.Width = _ContentWidth;

    // resize height to content
    var _OriginalHeight = webView.Height;
    var _HeightString = webView.InvokeScript("eval",
        new[] { "document.body.scrollHeight.toString()" });
    int _ContentHeight;
    if (!int.TryParse(_HeightString, out _ContentHeight))
        throw new Exception(string.Format("failure/height:{0}", _HeightString));
    webView.Height = _ContentHeight;

    // create brush
    var _OriginalVisibilty = webView.Visibility;
    webView.Visibility = Windows.UI.Xaml.Visibility.Visible;*/
    var _Brush = new WebViewBrush
    {
        SourceName = webView.Name,
        Stretch = Stretch.Uniform
    };
    _Brush.Redraw();

    // reset, return
    /*webView.Width = _OriginalWidth;
    webView.Height = _OriginalHeight;
    webView.Visibility = _OriginalVisibilty;*/
    return _Brush;
}

这正成为一个严重的史诗问题。

TL; DR;

这是更新的项目。 https://onedrive.live.com/redir?resid=EE99EF9560A6740E!110545&authkey=!AMcnB_0xKRhYHIc&ithint=file%2czip

加载并观察。文档中有9页。

  • GetWebPages偶尔只加载一页,裁剪
  • 其余时间,GetWebPages加载1-5页,6和3个空白页的一部分

回答这个问题,这几乎是你需要知道的全部内容。

我已经对这个问题表示了赏心悦目。我正在寻找解决此问题的方法,如果可能的话,还可以为每个页面添加填充,以便文本不会运行到边缘。

2 个答案:

答案 0 :(得分:0)

[更新]

使用这个新代码在完全相同的位置切断的事实非常奇怪。这表明WebViewBrush中不存在数据。如果将整个画笔渲染到控件中它是否渲染了所有内容?如果我的理论是正确的,你应该只看到第6页的截止。这开始闻起来像Surface Pro上的视频驱动程序错误。 Surface pro使用英特尔视频芯片组并使用共享即系统内存。你应该有足够的数据来渲染一个画笔实例(全部九页)。请记住,即使在更好的硬件上,这种方法仍然可能导致大页数的资源问题。

如果您有机会可以尝试使用来自其他制造商的视频卡在另一台计算机上加载它以确认它确实有效吗?我将尝试挖掘Surface Pro 1并查看是否可以重现您的发现。

在你走这条路之前,我需要警告你有关XAML打印的已知资源缺陷。 XAML打印子系统要求您在呈现之前将所有页面传递给它。然后它将文档作为一个整体发送到打印机。这要求将每个页面缓存为XAML,然后将每个页面呈现为位图。这一切都发生在主存中。您可能会在20页左右的时间内逃脱,但是一旦超过这一点,您将很快耗尽系统资源。由于内存压力,五十到一百页可能会导致运行时关闭。您将在低资源设备上获得更少的页面。

最初我认为你的目标是打印HTML页面。以下解决方案非常适合。但是,如果您的最终目标是允许您的用户从您的应用程序打印大型文档,那么真正的答案(并且只有Windows应用商店应用程序的解决方案)就是使用Direct2D和DirectWrite。这些技术允许您将页面“流式”传输到打印机缓存。打印机缓存由磁盘支持,不会导致相同的内存压力问题(尽管理论上您仍然可以用尽磁盘空间)。不用说这是理想的解决方案,但确实需要很多专业知识。你需要了解一些关于C ++ / CX以及Direct2D才能真正能够正确地对页面进行分页。

Direct2Dapp printing sample

我希望这有帮助,

詹姆斯


我今天仔细研究了这个,我认为你遇到的是资源限制。我无法在我的工作站类机器上重现该问题,我的同事无法在他的工作站级笔记本电脑上重现该问题。我在你的代码中看到的是你正在为每个页面创建一个新画笔。即使您正在转换画笔,整个WebView也会转换为每个页面的位图,一个非常大的位图。这会给XAML丰富的合成器带来资源限制。由于合成器是基于D3D的,因此您的视频卡可能没有纹理内存。

我想我有一个解决方案。我不会撒谎它是丑陋的,但它为我工作,我希望它对你有用。基本上我通过画笔将页面部分渲染到XAML中声明的隐藏矩形。然后我使用RenderTargetBitmap渲染隐藏的矩形。这具有仅渲染我们需要的WebView部分的效果。然后我将RenderTargetBitmap设置为Image的源。我将图像添加到页面集合中。页面集合设置为您的ItemsControl,并且是您的叔叔。

大部分更改都在GetWebPages中:

async Task<bool> GetWebPages(WebView webView, Windows.Foundation.Size page)
{
    // how many pages will there be?
    var _Scale = page.Width / webView.Width; // now sourced directly from webview
    var _ScaledHeight = (webView.Height * _Scale); // now sourced directly from webview
    var _PageCount = (double)_ScaledHeight / page.Height;
    _PageCount = _PageCount + ((_PageCount > (int)_PageCount) ? 1 : 0);

    var _Brush = GetWebViewBrush(webView);

    for (int i = 0; i < (int)_PageCount; i++)
    {
        var _TranslateY = -page.Height * i;
        // You can probably optimize this bit
        _RenderSource.Height = page.Height;
        _RenderSource.Width = page.Width;
        Margin = new Thickness(5);

        _Brush.Stretch = Stretch.UniformToFill;
        _Brush.AlignmentY = AlignmentY.Top;
        _Brush.Transform = new TranslateTransform { Y = _TranslateY };
        _RenderSource.Fill = _Brush;

        RenderTargetBitmap rtb = new RenderTargetBitmap();

        await rtb.RenderAsync(_RenderSource);

        var _iPge = new Image
        {
            Source = rtb,
            Margin = new Thickness(10)
        };

        _Pages.Add(_iPge);
    }
    return true;
}

可以在此处找到整个修改过的项目:http://1drv.ms/1y91iv7

请试一试,让我知道它是否适合您。

PS我没有尝试解决一些布局问题和分页问题。

我希望这有帮助,

詹姆斯

答案 1 :(得分:0)

不是解决方案,但由于我自己正在努力进行UWP打印,我想我会分享我的发现:

(1)灰色屏幕:

您使用WebViewBrush.Redraw()来捕获WebView的“页面”。但是,Redraw()异步发生。遗憾的是,我们无法使用await等待WebViewBrush捕获WebView。

我怀疑您正在处理下一页,但WebViewBrush尚未捕获上一页。这可以解释为什么在添加断点时它会起作用。

(2)WebViewBrush的大小限制:

我看到类似的东西对我来说,如果我的WebView高于8000px,WebVewBrush就会停止工作。我认为它还取决于视图的宽度,因此也取决于像素的总数。

我的尝试:将WebView调整为页面大小,并使用JavaScript在页面之间滚动。但这并不能解决上面的灰页问题。

(3)内存限制

是的,这是一个问题。我尝试只为每个页面使用单独的WebView,但应用程序在240页之后耗尽内存。

但是,我能够使用呈现为Rectangle的500个“普通”页面。

我还尝试使用MS Edge加载和打印500页PDF,这样就可以了,因此可以打印大型文档。

作为参考,这是我自己的问题(如果我找到解决方案,我会发布更新):

How to wait for WebViewBrush.Redraw() to finish (UWP printing)?