如何等待WebViewBrush.Redraw()完成(UWP打印)?

时间:2018-03-21 16:02:37

标签: printing webview uwp windows-10 uwp-xaml

我有一个基本的UWP应用程序,内嵌WebView呈现相当大的HTML文档(最多500个字母大小的打印页面)。

我想添加对HTML文档打印的支持。这是我的方法:

  1. 为了支持分页,我生成了第二个HTML文档,分为&#34; pages&#34;为每个&#34;页面&#34;使用<div style="height:100vh">,最多500个。
  2. 我加载了#34;分页&#34;将HTML格式化为XAML页面上的第二个隐藏WebView,我根据用户选择的页面大小调整大小以适合一页。
  3. 我等待WebView完成加载......
  4. 现在每个&#34;页面&#34;:
    1. 我将WebView的scrollY设置为仅使用JavaScript显示当前页面:window.scrollTo(0, -pageOffset)
    2. 然后我使用WebViewBrush捕获WebView
    3. 中当前页面的快照
    4. 对剩下的所有页面重复此操作......
  5. 问题:

    我可以生成所有500页的打印预览,但有时预览会丢失页面而其他页面会多次显示。

    我怀疑这是因为我使用WebViewBrush.Redraw()来捕获滚动的WebView的快照,但文档说Redraw() 异步发生。我可能会在WebViewBrush有机会重绘之前滚动浏览当前页面,因此会意外捕获下一页。

    如何确保WebViewBrush已捕获WebView以便我可以滚动到下一页?

    我的代码生成一个页面:

        private async Task<Rectangle> MakePage(WebView webView, 
                            Size pageSize, double pageOffset)
        {
            // Scroll to next page:
            await webView.EvaluateJavaScriptSnippetAsync(
                                 $"window.scrollTo(0, {pageOffset})");
    
            var brush = new WebViewBrush();
            brush.Stretch = Stretch.Uniform;
            brush.SetSource(webView);
            brush.Redraw(); // XXX Need to wait for this, but there's no API
    
            // Add a delay hoping Redraw() finishes... I think here is the problem.
            await Task.Delay(150);
    
            var rectangle = new Rectangle()
            {
                Width = pageSize.Width,
                Height = pageSize.Height
            };
    
            rectangle.Fill = brush;
            brush.Stretch = Stretch.UniformToFill;
            brush.AlignmentY = AlignmentY.Top;
    
            return rectangle;
        }
    

    注意:如果有使用WebViewBrush打印500页的替代方法,我可以提供建议。我尝试为每个页面使用单独的WebView,但应用程序在200页后耗尽内存。

    BOUNTY:我开始向任何可以弄清楚如何打印500页的人提供100分的赏金。

3 个答案:

答案 0 :(得分:5)

根据WebViewBrush remarks的重要部分:

  

WebView控件具有固有的异步行为,可在内容完全加载时重绘控件。但是一旦解析了XAML(可能是在WebView加载URI内容之前),关联的WebViewBrush就会呈现。或者,您可以等待在WebViewBrush上调用SetSource,直到源内容完全加载为止(例如,通过在WebView.LoadCompleted事件的处理程序中调用SetSource。

这样您就可以在WebView.LoadCompleted之后调用SetSource WebViewBrush方法。

答案 1 :(得分:3)

我从您的问题中了解到,您只有一个HTML文档。这在内容方面非常大,您可以将其分为500页。您需要打印整个HTML页面。在这里,我认为HTML页面中没有您不想打印的内容或空格或部分。

我搜索了你的问题并得到了一些东西。我修改了该代码并在我的UWP应用程序上运行它,为您提供强大的基础。

我确信这不是您想要的确切解决方案,因为我不知道您要打印的页面大小和其他一些参数,但以下代码对您有90%以上的用处。

此代码根据您为方法提供的大小生成页面。它将把整个网页分成不。页面。它没有任何printng逻辑。我相信你有足够的能力(基于你的声誉点:))来玩C#和这段代码。您可以根据需要进行修改。

<强> XAML

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <WebView x:Name="wv" Source="http://www.stackoverflow.com"></WebView>
</Grid>

<强> C#

public MainPage()
{
    this.InitializeComponent();

    wv.LoadCompleted += Wv_LoadCompleted;
}

async private void Wv_LoadCompleted(object sender, NavigationEventArgs e)
{
    var allPages = await GetWebPages(wv, new Windows.Foundation.Size(100d, 150d));
}

async Task<IEnumerable<FrameworkElement>> GetWebPages(WebView webView, Windows.Foundation.Size pageSize)
{
    // GETTING WIDTH FROM WEVIEW CONTENT
    var widthFromView = await webView.InvokeScriptAsync("eval", new[] { "document.body.scrollWidth.toString()" });

    int contentWidth;
    if (!int.TryParse(widthFromView, out contentWidth))
        throw new Exception(string.Format("failure/width:{0}", widthFromView));
    webView.Width = contentWidth;

    // GETTING HEIGHT FROM WEBVIEW CONTENT
    var heightFromView = await webView.InvokeScriptAsync("eval", new[] { "document.body.scrollHeight.toString()" });

    int contentHeight;
    if (!int.TryParse(heightFromView, out contentHeight))
        throw new Exception(string.Format("failure/height:{0}", heightFromView));

    webView.Height = contentHeight;

    // CALCULATING NO OF PAGES
    var scale = pageSize.Width / contentWidth;
    var scaledHeight = (contentHeight * scale);
    var pageCount = (double)scaledHeight / pageSize.Height;
    pageCount = pageCount + ((pageCount > (int)pageCount) ? 1 : 0);

    // CREATE PAGES
    var pages = new List<Windows.UI.Xaml.Shapes.Rectangle>();
    for (int i = 0; i < (int)pageCount; i++)
    {
        var translateY = -pageSize.Height * i;
        var page = new Windows.UI.Xaml.Shapes.Rectangle
        {
            Height = pageSize.Height,
            Width = pageSize.Width,
            Margin = new Thickness(5),
            Tag = new TranslateTransform { Y = translateY },
        };

        page.Loaded += async (s, e) =>
        {
            var rectangle = s as Windows.UI.Xaml.Shapes.Rectangle;
            var wvBrush = await GetWebViewBrush(webView);
            wvBrush.Stretch = Stretch.UniformToFill;
            wvBrush.AlignmentY = AlignmentY.Top;
            wvBrush.Transform = rectangle.Tag as TranslateTransform;
            rectangle.Fill = wvBrush;
        };

        pages.Add(page);
    }
    return pages;
}

async Task<WebViewBrush> GetWebViewBrush(WebView webView)
{
    // ASSING ORIGINAL CONTENT WIDTH
    var originalWidth = webView.Width;

    var widthFromView = await webView.InvokeScriptAsync("eval", new[] { "document.body.scrollWidth.toString()" });

    int contentWidth;
    if (!int.TryParse(widthFromView, out contentWidth))
        throw new Exception(string.Format("failure/width:{0}", widthFromView));
    webView.Width = contentWidth;

    // ASSINGING ORIGINAL CONTENT HEIGHT
    var originalHeight = webView.Height;

    var heightFromView = await webView.InvokeScriptAsync("eval", new[] { "document.body.scrollHeight.toString()" });

    int contentHeight;
    if (!int.TryParse(heightFromView, out contentHeight))
        throw new Exception(string.Format("failure/height:{0}", heightFromView));
    webView.Height = contentHeight;

    // CREATING BRUSH
    var originalVisibilty = webView.Visibility;
    webView.Visibility = Windows.UI.Xaml.Visibility.Visible;

    var wvBrush = new WebViewBrush
    {
        SourceName = webView.Name,
        Stretch = Stretch.Uniform
    };
    wvBrush.Redraw();

    webView.Width = originalWidth;
    webView.Height = originalHeight;
    webView.Visibility = originalVisibilty;

    return wvBrush;
}

答案 2 :(得分:0)

按承诺重新访问。原始答案保留在末尾。

原来比我想的要困难。

以下是一种完全不同的解决方案,可以完全避免该问题:https://github.com/ArthurHub/HTML-Renderer/

该项目被放弃,但状态良好,并且仍然有意义。有一个核心渲染器和许多针对各种.NET平台的包装器项目

  • WinForms
  • WPF
  • 单音
  • dotnet核心

未列出UWP,但核心渲染器是纯C#解决方案,有意排除了与平台相关的内容,并复制了几个完全可用的PAL(平台适配器层)。

我已使用它从MVC5 Web API进行打印,该API为SPA客户端提供了浏览器内网络打印服务。该库非常快,比MSHTML或EdgeHTML小很多。它不打印,而是渲染。这些示例呈现一个位图,打印示例将其绘制在WinForms PrintDocument.PrintPage事件提供的Graphics对象上。这将产生可怕的上采样像素化,(完全没有记录在案)解决方案是渲染Windows图元文件。分页问题仍然存在,但是有一个PDF的PAL,您可以从中使用该方法。

著名的遗言:应该很难将WPF解决方案转换为UWP。

如果您有兴趣,我们可以分叉存储库并添加缺少的UWP PAL。这样做的风险是要获得支持,因为它对猴子小部件很有用。

对于预览方面,您可能会在UI上对图元文件进行分页和绘制。


我首先想到的是一个应该起作用的丑陋的hack:休眠线程。重绘已经在另一个线程上发生,这就是为什么您处于竞争状态。如果您保证其他线程有更多时间,您的机会就会大大提高。

新问题:Thread.Sleep在UWP中不可用,因为除了不支持时,所有内容都支持延续。

幸运的是,还有其他方法可以给这只猫蒙皮。

wvBrush.Redraw();

// System.Threading.Tasks.Task.Delay(30).Wait() is more elegant but less portable than...
using (var waitHandle = new System.Threading.ManualResetEventSlim(initialState: false))
  waitHandle.Wait(TimeSpan.FromMilliseconds(30)); // exploit timeout

下一个问题是渲染时间不可预测,长时间睡眠会损害性能,因此您需要一种测试成功的方法。

您说失败会产生一个全白页。我将为全白写一个测试,然后在我快速工作时返回并重写此答案的结尾。