【问题标题】:Printing static content from a WebView in a Windows Store App从 Windows 应用商店应用程序中的 WebView 打印静态内容
【发布时间】:2026-01-28 04:35:01
【问题描述】:

更新 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.*.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;
}

我唯一的改变是从 webview 中删除 Source 属性并将这一行添加到 MainPage() 方法中

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

Sherlock.html 摘自 Arthur Conan Doyle 的故事,红头联盟,此 HTML 页面的完整代码是 here - 请注意,接近结尾的部分重复了几个次。这是在测试中完成的,我将在下面解释

这个示例最初对我有用,在一个小文件上。 (没有填充章节的 Sherlock.html)

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

WebView 缩小到一个非常窄的视图

(请参阅更新说明,了解为何删除此内容)

然后屏幕变灰。 (发一张灰屏的截图感觉有点傻,但我在这里尽量做到彻底)

应用程序不会崩溃。没有调试消息出现,没有异常发生。 好像Grid 被简单地删除了。 罢工>。这不是启动画面(我设置为蓝色),也不是应用程序背景(深灰色)。就好像在页面顶部加载了其他内容。

我已尝试调整代码的每一部分,但我似乎无法找出导致问题的原因。此外,这段代码的某些部分让我很困惑。例如,在Load_Completed 方法中

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

Line1: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;

据我所知,webview 从来不是Collapsed,但它被设置为Visible 三倍。

如果有人可以向我解释这些(甚至可能是 JerryNixon 本人?),我会真的感激不尽。我花了几个月的时间开发一个应用程序,这是最后一块拼图。没有打印能力,我无法启动!

更新

今天早上有了一些发现。我一直在 XAML 中设置WebView 的宽度。没有这个,即使是最小的文件也会失败。基于这个发现,我将 html 文件本身的宽度设置为 800 像素,并且它起作用了。万岁,问题解决了,对吧?很不幸的是,不行。我再次尝试使用更长的文件(红发联盟完整版本,here),同样的问题再次发生。现在它变得更奇怪了

我在 GetWebPages 方法中的 webView.Height = _ContentHeight; 处插入了一个断点。这表明 _ContentHeight 正好是“13241”。

但是,插入断点会使代码工作。一旦我休息并继续,一切似乎都在加载。尽管再次奇怪的是,在我将鼠标悬停在滚动条上之前,WebView 是不可见的。

如果我删除断点,我会再次出现灰屏。

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

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

当它起作用时,还要注意MyWebViewRectangle 中的额外空白和MyPrintPages 中的额外空白页:

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

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

更新 2

一些成功。

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

(注意,我更改了背景颜色,因为蓝色上的红色让我偏头痛)

我已经浏览了代码并进行了一些修改,现在我完全不再出现灰屏了。但是,我运行它的每 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 空白页面的一部分

这就是回答这个问题所需要知道的几乎所有内容。

我已经在这个问题上悬赏。我正在寻找这个问题的解决方案,如果可能的话,我正在寻找一种方法来为每个页面添加填充,以便文本不会跑到边缘。

【问题讨论】:

  • 在 Windows 8.1 中有很多成功和失败状态的事件,例如 LongRunningScriptDetected、UnsafeContentWarningDisplaying 和 UnviewableContentIdentified。尝试添加这些事件并查看是否有任何火灾。另一个要尝试的是新的NavigateToLocalStreamUri 方法。
  • 谢谢内特。我刚刚添加了所有这些事件,并为每个事件添加了 throw new Exception...。没有例外。同样,只是一个灰屏。我确认这也不是网格被删除 - 我页面本身的背景颜色是深灰色。这种灰色是“应用启动”灰色。
  • 你试过不同尺寸的屏幕吗?尝试在具有不同大小屏幕的模拟器中加载它,看看是否会改变数字。

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


【解决方案1】:

[更新]

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

如果您有机会,请尝试将其侧加载到另一台使用不同制造商的视频卡的机器上,以确认它确实可以工作?我将尝试挖掘 Surface Pro 1,看看能否重现您的发现。

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

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

Direct2Dapp printing sample

希望对你有帮助,

詹姆斯


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

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

大部分更改都在 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 有一些布局问题和分页问题我没有尝试解决。

希望对你有帮助,

詹姆斯

【讨论】:

  • 我真的很感激所花费的时间,我很想说这解决了这个问题,但对我来说它仍然在完全相同的地方中断。您对资源的看法可能是正确的——我的开发机器是 4GB 的 Surface Pro。我想我将不得不尝试一种完全不同的方法——将 HTML 转换为 XAML,将其呈现为 RichTextBlock 并从那里打印。这不是我想要的,但它应该有望解决内存问题。
【解决方案2】:

不是一个解决方案,但由于我自己在 UWP 打印上苦苦挣扎,我想我会分享我的发现:

(1) 灰屏:

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

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

(2) WebViewBrush的大小限制:

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

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

(3) 内存限制

是的,这是个问题。我尝试简单地为每个页面使用单独的 WebView,但应用程序在 240 页后内存不足。

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

我还尝试使用 MS Edge 加载和打印 500 页的 PDF,这很有效,因此可以打印大型文档。

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

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

【讨论】: