UPDATE 3
There's a bounty on this question now. The sample project and problem brief are at the bottom of the page under TL;DR; so save yourself some reading and skip to the end!
...
I've been trying to get this highly detailed, upvoted answer from JerryNixon working using a statically loaded page via NavigateToString. I don't see why this shouldn't work, but admittedly there are parts of the code that absolutely baffle me.
The original answer is here but I'll replicate the code here for completeness sake. Jerry provides the following sample / solution to those who want to print from a 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;
}
My only change is to remove the Source attribute from the webview and add this line to the MainPage() method
var html = await Windows.Storage.PathIO.ReadTextAsync("ms-appx:///Assets/sherlock.html");
MyWebView.NavigateToString(html);
Sherlock.html is a excerpt from the Arthur Conan Doyle story, The Red-Headed League, the full code of this HTML page is here - note that a segment near the end repeats several times. This was done in testing, I'll explain below
This sample initially worked for me, on a small file. (Sherlock.html without the padded chapters)
However when I get above a certain file-size (usually around 4000 words+) the following behaviour occurs with the sample.
The WebView shrinks to a very narrow view
(see update notes on why this is struck out)
and then the screen goes gray. (I feel a little silly posting a screenshot of a gray screen, but I'm trying to be thorough here)
The app does NOT crash. No debugging messages appear, no exceptions occur. It is as if the Grid is simply removed. . It's not the splash screen (which I've set blue) and it's not the app background (darker gray). It's as if something else is loaded over the top of the page.
I've tried adjusting every part of the code, but I can't seem to isolate what causes the issue. Furthermore, parts of this code just confuse the hell out of me. For instance, in the Load_Completed Method
MyWebViewRectangle.Fill = GetWebViewBrush(MyWebView);
MyPrintPages.ItemsSource = GetWebPages(MyWebView, new Windows.Foundation.Size(100d, 150d));
MyWebView.Visibility = Windows.UI.Xaml.Visibility.Visible;
Line1: MyWebViewRectangle is filled with a shot of the WebView. Why? This control is never referenced again. I guess it could be for the user to get a clearer view of the page, but it seems to serve no programmatic purpose
Line3: MyWebView.Visibility is set to Visible This happens again later, in GetWebViewBrush.
var _OriginalVisibilty = webView.Visibility;
webView.Visibility = Windows.UI.Xaml.Visibility.Visible;
// and a few lines later...
webView.Visibility = _OriginalVisibilty;
As far as I can tell, the webview is never Collapsed, yet it is set to Visible three times.
If anyone can explain any of this to me (maybe even JerryNixon himself?) I would really appreciate it. I've spent several months working on an app and this is the last piece of the puzzle. I can't launch without the ability to print!
UPDATE
Made some discoveries this morning. I had been setting the width of the WebView in XAML. Without this even the smallest files were failing. Based on this discovery I set the width in the html file itself to 800px, and it worked. Hooray, problem solved, right? Unfortunately, no. I tried with a longer file again (the full version of The Red-headed league, here) and the same issue is happening again. It's now gotten a lot weirder.
I inserted a breakpoint at webView.Height = _ContentHeight; in the GetWebPages method. This shows me that _ContentHeight is exactly "13241".
However, inserting a breakpoint makes the code work. Once I hit the break and continue, it all seems to load. Although oddly again, the WebView is invisible until I mouseover the scrollbar.
If I remove the breakpoint, I get the grey screen again.
If I manually set _ContentHeight to 13230, the page loads. This seems to be the magic number. Anything higher than this and the page fails. However, if I change the width of the html to less than 800px, it also fails. So logically, there's some kind of magic ratio of 800:13230 pixels (or 1:16.5375). If I go over this ratio, I set off the gray screen of death. I can disable the Red rectangle and restrict GetWebPages to 3 pages, and the gray screen is still there. That tells me that the issue is with the WebView itself
An issue that goes away if I set a breakpoint.
Also note the extra whitespace in MyWebViewRectangle and extra blank pages in MyPrintPages when it does work:
Note: I've also added NateDiamond's suggestions of the events LongRunningScriptDetected, UnsafeContentWarningDisplaying, and UnviewableContentIdentified
Here's the complete source of my sample project:
https://onedrive.live.com/redir?resid=EE99EF9560A6740E!110509&authkey=!ACrZgm6uq5k5yck&ithint=file%2czip
UPDATE 2
Some success.
I put some headers into my sample file (found the "bottom" of each computed page by trial and error) and upped the page size so I could see the markers. Although there are 9 pages, only 5 and a small part of 6 are rendered. 9 Page objects are created by GetWebPages, but only 5ish have rendered HTML content. The rest are empty
(Note, I changed the background colors as the red on blue was giving me a migraine)
I've gone through the code and made some modifications, and now I no longer get the gray screen at all. However, about 2 out of every 3 times I run it, only the first page is rendered, and it is cropped. This seems to be totally random. When 9 pages are rendered not all have content, as stated.
Here are my changes:
added new method 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;
}
in MyWebView_LoadCompleted commented out the rectangle fill, added call to ResizeWebView():
void MyWebView_LoadCompleted(object sender, NavigationEventArgs e)
{
ResizeWebView();
//MyWebViewRectangle.Fill = GetWebViewBrush(MyWebView);
MyPrintPages.ItemsSource = GetWebPages(MyWebView, new Windows.Foundation.Size(300d, 450d));
}
in GetMyWebPages changed where the brush is loaded:
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;
}
removed now unnecessary resize code from 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;
}
This is becoming a seriously epic question.
TL;DR;
Here is the updated project.
https://onedrive.live.com/redir?resid=EE99EF9560A6740E!110545&authkey=!AMcnB_0xKRhYHIc&ithint=file%2czip
Load and observe. there are 9 pages in the document.
GetWebPages occasionally loads only one page, cropped
the rest of the time, GetWebPages loads pages 1-5, part of 6 and 3 blank pages
This is pretty much all you need to know to answer this question.
I've put a bounty on this question. I'm looking for resolution of this issue, and if possible a way to add padding to each page so that the text does not run to the edges.
[Update]
The fact that it is cutting off at the exact same place with this new code is extremely strange. This would indicate that the data doesn’t exist in the WebViewBrush. If you render the entire brush to a control does it render everything? If my theory is correct you should only see up to the page 6 cutoff. This is starting to smell like a video driver bug on the Surface pro. The Surface pro uses the Intel video chipset and uses shared i.e. system memory. You should have plenty to render a single instance of the brush (all nine pages). Keep in mind that even on better hardware this approach is still going to likely cause resource problems at large page counts.
If you have a chance can you please try side loading it on another machine with a video card from a different manufacturer to confirm that it does indeed work? I'm going to try to dig up a Surface Pro 1 and see if I can reproduce your findings.
I need to warn you about the known resource pitfalls of XAML printing before you head down that road. The XAML printing subsystem requires that you pass ALL of the pages to it before it will render. It then sends the document as a whole to the printer. This requires that every page be cached as XAML and then every page gets rendered as a bitmap. This all happens in main memory. You likely can get away with twenty pages or so but you will quickly run out of system resources once you get past that. Fifty to a hundred pages will likely cause the runtime to shut you down due to memory pressure. You will get even fewer pages on low resource devices.
Initially I thought your goal was to print HTML pages. The solution below is perfect for that. However, if your ultimate goal is to allow your users to print large documents from your app the real answer (and only solution for Windows Store apps) is to use Direct2D and DirectWrite. These technologies allow you to “stream” the pages to the printer cache. The printer cache is backed by disk and doesn’t cause the same memory pressure problems (although you can still theoretically run out of disk space). Needless to say this is the ideal solution but does require a lot of expertise. You need to know something about C++ / CX as well as Direct2D to really be able to paginate the pages correctly.
Direct2Dapp printing sample
I hope this helps,
James
I took a close look at this today and I think that you are running into is resource limitations. I am not able to reproduce the issue on my workstation class machine and my colleague was not able to reproduce the problem on his workstation class laptop. What I see in your code is that you are creating a new brush for every page. Even though you are transforming the brush the entire WebView is getting transformed into a bitmap, a very big bitmap, for every page. This puts resource constraints on the XAML rich compositor. Since the compositor is D3D based you are likely running out of texture memory on your video card.
I think I have a solution for you. I'm not going to lie it is ugly but its working for me and I hope that it will work for you. Basically I'm rendering the page section via the brush to a hidden rectangle declared in the XAML. I then use RenderTargetBitmap to render the hidden rectangle. This has the effect of only rendering the portion of WebView that we need. Then I'm setting the RenderTargetBitmap as the source to an Image. I add the Images to the pages collection. The pages collection is set to your ItemsControl and and bob's your uncle.
The bulk of the changes are in 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;
}
The entire modified project can be found here: http://1drv.ms/1y91iv7
Please give it a try and let me know if it works for you.
PS There are some layout issues and pagenation issues that I didn't try to fix.
I hope this helps,
James
Not a solution, but since I'm struggling with UWP printing myself, I'd thought I share my findings:
(1) Gray screen:
You use WebViewBrush.Redraw() to capture a "page" of your WebView. However, Redraw() happens asynchronously. Unfortunately we can't use await to wait for the WebViewBrush to capture the WebView.
I suspect you are processing the next page, but WebViewBrush has not yet captured the previous page. That would explain why it works when you add a breakpoint.
(2) Size limitation of WebViewBrush:
I'm seeing something similar For me, WebVewBrush stops working if my WebView is higher than 8000px. I assume it also depends on the width of the view and hence the total number of pixels.
My attempt here: resize the WebView to the page size and scroll from page to page using JavaScript. But this does not solve the gray page problem above.
(3) Memory constraints
Yes, that's a problem. I tried simply using a separate WebView for each page, but the app runs out of memory after 240 pages.
However, I was able to use 500 "plain" pages rendered as Rectangle.
I also tried to load and print a 500 page PDF with MS Edge and this works, so it is possible to print large documents.
For reference, here's my own question (I'll post an update should I ever find a solution):
How to wait for WebViewBrush.Redraw() to finish (UWP printing)?
Related
I am currently trying to print the contents of a content container (it only contains datagrids with information) and an image using PrintFixedDocument. It prints flawlessly on my machine (windows 10) with full image quality and on another pc which is windows 8, the quality is the same.
However when this is done on a Windows 7 pc the image quality becomes very poor and the final result is very blurred. This is a problem as the computer cannot be updated from windows 7 for various reasons, so im wondering if anyone else has experienced this and if so is there a workaround? Also could be an issue with my GetFixedDocument method, though I cannot work out why this would work on both win 10 and 8 but not 7.
NOTE THIS IS RUNNING FROM AN INSTALLED VERSION OF THE APPLICATION ON EACH PC
ALSO BEEN TRIED ON MULTIPLE PRINTERS ON ALL 3 OPERATING SYSTEMS
Any help would be appreciated
Xaml:
<StackPanel Margin="-105,146,66,0" Height="900" VerticalAlignment="Top" x:Name="PrintImageContextMenu">
<Image Canvas.ZIndex="0" Source="{Binding Coupon.OverlayImagePath}" Margin="0,-21,-76,108" Stretch="Fill" />
<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource DataViewerDataTemplateSelector}" />
</StackPanel>
C#:
public partial class CouponViewerView
{
public CouponViewerView()
{
InitializeComponent();
}
public void Print()
{
//Executes On Thread
Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (EventHandler)delegate
{
UpdateLayout();
var fixedDoc = PrintHelper.GetFixedDocument(StackPanelToPrint, new PrintDialog());
PrintHelper.ShowPrintPreview(fixedDoc);
}, null, null);
}
private void PrintCurrentForm(object sender, RoutedEventArgs e)
{
Print();
}
C# Print helper code:
public static void ShowPrintPreview(FixedDocument fixedDoc)
{
var wnd = new Window();
var viewer = new DocumentViewer();
viewer.Document = fixedDoc;
wnd.Content = viewer;
wnd.ShowDialog();
}
public static FixedDocument GetFixedDocument(FrameworkElement toPrint, PrintDialog printDialog)
{
var capabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
var pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);
var visibleSize = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
var fixedDoc = new FixedDocument();
//If the toPrint visual is not displayed on screen we neeed to measure and arrange it
toPrint.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
toPrint.Arrange(new Rect(new Point(0, 0), toPrint.DesiredSize));
//
var size = toPrint.DesiredSize;
//Will assume for simplicity the control fits horizontally on the page
double yOffset = 0;
while (yOffset < size.Height)
{
var vb = new VisualBrush(toPrint)
{
Stretch = Stretch.None,
AlignmentX = AlignmentX.Left,
AlignmentY = AlignmentY.Top,
ViewboxUnits = BrushMappingMode.Absolute,
TileMode = TileMode.None,
Viewbox = new Rect(0, yOffset, visibleSize.Width, visibleSize.Height)
};
var pageContent = new PageContent();
var page = new FixedPage();
((IAddChild)pageContent).AddChild(page);
fixedDoc.Pages.Add(pageContent);
page.Width = pageSize.Width;
page.Height = pageSize.Height;
var canvas = new Canvas();
FixedPage.SetLeft(canvas, capabilities.PageImageableArea.OriginWidth);
FixedPage.SetTop(canvas, capabilities.PageImageableArea.OriginHeight);
canvas.Width = visibleSize.Width;
canvas.Height = visibleSize.Height;
canvas.Background = vb;
page.Children.Add(canvas);
yOffset += visibleSize.Height;
}
return fixedDoc;
}
anyone else has experienced this and if so is there a workaround?
That's the only directly answerable question, yes, many. Keep in mind that you often substantially rescale an image on a printer, they are devices with very high dots-per-inch resolution compared to a monitor. A machine that boots Win7 often runs at 96dpi, later ones tend to have better monitors so are often run at higher dpi settings. First thing to watch out for is the source image. If it has a pixel size that's adequate for that Win7 PC then it can get very blurry when it is blown-up to 600 dpi.
Probably the most unintuitive scaling behavior in WPF is what happens when the image alignment does not perfectly match a target pixel after scaling. A problem described well in this blog post. Be sure to also read this SO question, an almost perfect fit for your usage of VisualBrush and its blurriness problem. This problem was addressed in .NET 4.0 with the added UseLayoutRounding property. You are not using it, you definitely should. Don't blindly apply the BitmapScalingMode that the dup recommends, the type of image (line-art vs photo) matters a lot.
I had a similar issue I ended up instead of directly printing to creating a PDF and having that open for the client and they could print it if they wanted just fine.
Windows 7 just seems broken with some of the WPF printing.
As an UWP App runs in window mode on common desktop systems the "old" way of getting the screen resolution won't work anymore.
Old Resolution with Window.Current.Bounds was like shown in.
Is there another way to get the resolution of the (primary) display?
To improve the other answers even a bit more, the following code also takes care of scaling factors, e.g. for my 200% for my Windows display (correctly returns 3200x1800) and 300% of the Lumia 930 (1920x1080).
var bounds = ApplicationView.GetForCurrentView().VisibleBounds;
var scaleFactor = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
var size = new Size(bounds.Width*scaleFactor, bounds.Height*scaleFactor);
As stated in the other answers, this only returns the correct size on Desktop before the size of root frame is changed.
Call this method anywhere, anytime (tested in mobile/desktop App):
public static Size GetCurrentDisplaySize() {
var displayInformation = DisplayInformation.GetForCurrentView();
TypeInfo t = typeof(DisplayInformation).GetTypeInfo();
var props = t.DeclaredProperties.Where(x => x.Name.StartsWith("Screen") && x.Name.EndsWith("InRawPixels")).ToArray();
var w = props.Where(x => x.Name.Contains("Width")).First().GetValue(displayInformation);
var h = props.Where(x => x.Name.Contains("Height")).First().GetValue(displayInformation);
var size = new Size(System.Convert.ToDouble(w), System.Convert.ToDouble(h));
switch (displayInformation.CurrentOrientation) {
case DisplayOrientations.Landscape:
case DisplayOrientations.LandscapeFlipped:
size = new Size(Math.Max(size.Width, size.Height), Math.Min(size.Width, size.Height));
break;
case DisplayOrientations.Portrait:
case DisplayOrientations.PortraitFlipped:
size = new Size(Math.Min(size.Width, size.Height), Math.Max(size.Width, size.Height));
break;
}
return size;
}
The more simple way:
var displayInformation = DisplayInformation.GetForCurrentView();
var screenSize = new Size(displayInformation.ScreenWidthInRawPixels,
displayInformation.ScreenHeightInRawPixels);
This does not depends on current view size. At any time it returns real screen resolution.
Okay so the Answer from Juan Pablo Garcia Coello lead me to the Solution - Thanks for that!
You can use
var bounds = ApplicationView.GetForCurrentView().VisibleBounds;
but you must call it before the windows is displayed in my case right after
Window.Current.Activate();
is a good place. At this time you will get the bounds of the window on which your app will appear.
Thanks a lot for help me solving it :)
Regards Alex
The only way I found is inside the constructor of a Page:
public MainPage()
{
this.InitializeComponent();
var test = ApplicationView.GetForCurrentView().VisibleBounds;
}
I have not tested in Windows 10 Mobile, when the new release appears I will test that.
Use this method to get the screen size:
public static Size GetScreenResolutionInfo()
{
var applicationView = ApplicationView.GetForCurrentView();
var displayInformation = DisplayInformation.GetForCurrentView();
var bounds = applicationView.VisibleBounds;
var scale = displayInformation.RawPixelsPerViewPixel;
var size = new Size(bounds.Width * scale, bounds.Height * scale);
return size;
}
You should call this method from App.xaml.cs, after Window.Current.Activate(); in OnLaunched method.
Here's the sample code and you can download the full project.
Just set a name for main Grid or Page and call its width or height for an element you want:
Element.Height = PagePane.Height;
Element.width = PagePane.Width;
it's the easiest way you can use!
Has anyone got any idea why I'm getting an OutOfMemoryException when I'm creating my custom tile?
I'm trying to create custom images for my primary tile on a windows phone 8 app from a ScheduledAgent. The error doesn't occur until my very last line of code is executed which is the NotifyComplete().
Here is the code (Not the cleanest but ok for prototyping I guess). This code only handles the wide tile and it tries to load an image an image downloaded from a website and then it tries to render a logo and a description over this image.
Here is the code:
private void CreateTiles()
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
for (int i = 0; i < 2; i++)
{
var bmp = new WriteableBitmap(691, 336);
var articleImg = new BitmapImage(new Uri(articles[i].ImageFilename, UriKind.Relative));
var articleImage = new Image { Source = articleImg };
articleImage.Stretch = Stretch.UniformToFill;
articleImg.CreateOptions = BitmapCreateOptions.None; // Force the bitmapimage to load it's properties so the transform will work
var bmpLogo = new WriteableBitmap(100, 100);
var logoImg = new BitmapImage(new Uri("/Assets/Tiles/FlipCycleTileSmall.png", UriKind.Relative));
var logoImage = new Image { Source = logoImg };
logoImage.Opacity = 1.0;
logoImg.CreateOptions = BitmapCreateOptions.None; // Force the bitmapimage to load it's properties so the transform will work
var articleBannerGrid = new Grid();
articleBannerGrid.Background = ColorExtensions.ToSolidColorBrush("#000F558E");
articleBannerGrid.Opacity = .5;
articleBannerGrid.Height = 100;
articleBannerGrid.VerticalAlignment = VerticalAlignment.Bottom;
articleBannerGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(100) });
articleBannerGrid.ColumnDefinitions.Add(new ColumnDefinition());
articleBannerGrid.Children.Add(logoImage);
Grid.SetColumn(logoImage, 0);
var textBlock = new TextBlock();
textBlock.Text = articles[i].Description;
textBlock.FontWeight = FontWeights.Bold;
textBlock.Margin = new Thickness(10, 5, 30, 5);
textBlock.TextWrapping = TextWrapping.Wrap;
textBlock.Foreground = new SolidColorBrush(Colors.White); //color of the text on the Tile
textBlock.FontSize = 30;
textBlock.Opacity = 1.0;
var articleTextGrid = new Grid();
articleTextGrid.Height = 100;
articleTextGrid.VerticalAlignment = VerticalAlignment.Bottom;
articleTextGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(100) });
articleTextGrid.ColumnDefinitions.Add(new ColumnDefinition());
articleTextGrid.Children.Add(textBlock);
Grid.SetColumn(textBlock, 1);
var canvas = new Grid();
canvas.Width = articleImg.PixelWidth;
canvas.Height = articleImg.PixelHeight;
canvas.Children.Add(articleImage);
canvas.Children.Add(articleBannerGrid);
canvas.Children.Add(articleTextGrid);
bmp.Render(canvas, null);
bmp.Invalidate(); //Draw bitmap
FileStream fs = new FileStream(articles[i].ImageFilename, FileMode.Create);
bmp.SaveJpeg(fs, bmp.PixelWidth, bmp.PixelHeight, 0, 100);
fs.Close();
fs.Dispose();
articleImage = null;
articleImg = null;
}
//GC.Collect();
//GC.WaitForPendingFinalizers();
ShellTile TileToFind = ShellTile.ActiveTiles.FirstOrDefault();
if (TileToFind != null)
{
string title = articles[0].Tag;
string backTitle = articles[1].Tag;
string content = articles[0].Description;
string backContent = articles[1].Description;
FlipTileData tileData = new FlipTileData()
{
Title = title,
BackTitle = backTitle,
BackContent = backContent,
WideBackContent = backContent,
BackgroundImage = new Uri(articles[0].ImageFilename, UriKind.Relative),
BackBackgroundImage = new Uri(articles[1].ImageFilename, UriKind.Relative),
WideBackgroundImage = new Uri(articles[0].ImageFilename, UriKind.Relative),
WideBackBackgroundImage = new Uri(articles[1].ImageFilename, UriKind.Relative),
};
TileToFind.Update(tileData);
}
NotifyComplete();
});
}
I assume that it is the correct place to generate the custom tile i.e. ScheduledAgent.
I found an article Dynamic Live Tile issue WP8 [WriteableBitmap] on the Nokia website with the same problem but no solution there either.
I'll continue debugging this tomorrow by removing everything and adding each bit individually step by step to see if I can spot anything but if anyone has got any suggestion or solution, I'd appreciate if you could help.
If I'm going about it the wrong way, please let me know what's the best method to update tiles in the scheduled agent.
Thanks.
WP BackgroundAgents have a small memory cap. 20MB for WP8 without update 3 and 25 for WP8 with Update 3.
I would suggest that you create a another project and add Coding4Fun toolkit and MemoryCounter in to your page and then add the code in your BackgroudAgent to the sample page and try to create the tiles. Then look at how much memory it uses. I think amount of memory usage is higher than the 20/25MB cap. if so you have to find a way to reduce it.
As usual, Microsoft is omitting critical information from its documentation.
After reducing the size of my tile from 691x336 to 336x165, it worked, so it got me thinking and I thought that the size recommended by Microsoft in this article Flip Tile template for Windows Phone 8 for wp8 and wp8.1 seemed excessive, so I did a bit more research and this is when I found this excellent explanation in an article in stackoverflow i.e.
Windows Phone 8 Startscreen Tile sizes and margins
This clearly states the size of the tiles but not based on OS versions but based on screen resolutions.
In my test environment, I was using the default 'Emulator WVGA 512MB' and again by default the screen size is 480x800, so taking into account the information provided in the answer of this article, my tile size should have been 430x210.
I resized my (already reduced) tile back up to 430x210, and it still worked.
While there is a memory cap of 20/25Mb depending on OS and patch being used, which probably causes many problems from the various articles I've read on the web, in my instance I believe it was more likely to have been affected by tile size being incorrect, and therefore the additional memory, or lack thereof.
I will update this article once I get to using 8.1 in my IDE with a larger resolution but for now, I have to assume that it was definitely related to the size of my tile.
I'll definitely make sure to add code to create tile based on os, patch and resolution being used. Bit of a pain to best honest!
Hope this helps.
I have written the following chunk of code that prints my ListBox perfectly when being sent to a physical printer, however when trying to send it to the XPS printer driver or using the XpsDocumentWriter class (I assume they use the same code under the hood) I receive the following exception:
System.ArgumentException was unhandled by user code
Message=Width and Height must be non-negative.
Source=ReachFramework
StackTrace:
at System.Windows.Xps.Serialization.VisualSerializer.WriteTileBrush(String element, TileBrush brush, Rect bounds)
The exception obviously points to an item not having a correct width/height however I have debugged the code when sending it to the different printers (physical and XPS driver) and I haven't been able to find any differences.
Below is how I create the visual to send to the printer:
private ScrollViewer GeneratePrintableView()
{
ScrollViewer scrollView = new ScrollViewer();
Grid grid = new Grid { Background = Brushes.White, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions[0].Height = new GridLength(0, GridUnitType.Auto);
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Auto);
// Add the title and icon to the top
VisualBrush titleClone = new VisualBrush(this.TitleBar);
var titleRectangle = new Rectangle { Fill = titleClone, Width = this.TitleBar.ActualWidth, Height = this.TitleBar.ActualHeight };
grid.Children.Add(titleRectangle);
Grid.SetRow(titleRectangle, 0);
this.myListBox.Width = this.myListBox.ActualWidth;
this.myListBox.Height = this.myListBox.ActualHeight;
VisualBrush clone = new VisualBrush(this.myListBox) { Stretch = Stretch.None, AutoLayoutContent = true };
var rectangle = new Rectangle { Fill = clone, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
Border border = new Border { Background = Brushes.White, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
border.Child = rectangle;
grid.Children.Add(border);
Grid.SetRow(border, 1);
scrollView.Width = this.myListBox.ActualWidth;
scrollView.Height = this.myListBox.ActualHeight;
scrollView.Content = grid;
scrollView.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
return scrollView;
}
Here is the GetPage override in my DocumentPaginator implementation:
public override DocumentPage GetPage(int pageNumber)
{
Page page = new Page();
double z = 0.0;
this.grid = new Grid();
this.grid.RowDefinitions.Add(new RowDefinition());
this.grid.RowDefinitions[0].Height = new GridLength(0, GridUnitType.Auto);
this.grid.Children.Add(this.printViewer);
Grid.SetRow(this.printViewer, 0);
//Adjusting the vertical scroll offset depending on the page number
if (pageNumber + 1 == 1) //if First Page
{
this.printViewer.ScrollToVerticalOffset(0);
this.printViewer.UpdateLayout();
}
else if (pageNumber + 1 == _verticalPageCount) //if Last Page
{
if (this.printViewer.ScrollableHeight == 0) //If printing only single page and the contents fits only on one page
{
this.printViewer.ScrollToVerticalOffset(0);
this.printViewer.UpdateLayout();
}
else if (this.printViewer.ScrollableHeight <= this.printViewer.Height) //If scrollconentheight is less or equal than scrollheight
{
this.printViewer.Height = this.printViewer.ScrollableHeight;
this.printViewer.ScrollToEnd();
this.printViewer.UpdateLayout();
}
else //if the scrollcontentheight is greater than scrollheight then set the scrollviewer height to be the remainder between scrollcontentheight and scrollheight
{
this.printViewer.Height = (this.printViewer.ScrollableHeight % this.printViewer.Height) + 5;
this.printViewer.ScrollToEnd();
this.printViewer.UpdateLayout();
}
}
else //Other Pages
{
z = z + this.printViewer.Height;
this.printViewer.ScrollToVerticalOffset(z);
this.printViewer.UpdateLayout();
}
page.Content = this.grid; //put the grid into the page
page.Arrange(new Rect(this.originalMargin.Left, this.originalMargin.Top, this.ContentSize.Width, this.ContentSize.Height));
page.UpdateLayout();
return new DocumentPage(page);
}
Interestingly if I change the Fill of rectangle to a Brush instead of clone then I do not receive the exception and the outputted file is the correct size.
I have spent over a day trying to debug why this isn't working and I am hoping that someone out there has either seen a similar issue or is able to point out any mistakes I am making.
Thanks for any responses.
I had to give up finding a solution with VisualBrush. If there is a GroupBox in the Visual for the brush I could never get it to produce a XPS file. It always fails with
System.ArgumentException was unhandled by user code Message=Width and Height must be non-negative. Source=ReachFramework StackTrace: at System.Windows.Xps.Serialization.VisualSerializer.WriteTileBrush(String element, TileBrush brush, Rect bounds)
The workaround was to clone the content that should go in the VisualBrush (Is there an easy/built-in way to get an exact copy (clone) of a XAML element?) and use that directly in a Grid instead of an VisualBrush
Have you checked the value of ActualWidth and ActualHeight of myListBox when the VisualBrush is being created? I don't know from where myListBox comes, but if it is not rendered by the time you are generating your xps document you may run into problems. You can try to manually force the control to render and see if it makes any difference.
I was unable to rectify the problem however using this link Paginated printing of WPF visuals I was able to find a suitable solution to allow printing of complicated visuals within my WPF application.
It's 2016 now and it's still not fixed. The problem is using TileBrush or any descendant type (VisualBrush in your case). If you use absolute mapping, it works, it's the relative mapping that causes the problem. Calculate the final size yourself and set Viewport to this size, ViewportUnits to Absolute. Also make sure you don't use Stretch.
When i select Microsoft XPS Document Writer as Printer, my output is perfect but when i select my HP 1020 printer machine, the printer outputs blank copy...Following is the code....
private void printButton_Click(object sender, RoutedEventArgs e)
{
PrintInvoice pi = new PrintInvoice();
pi.DataContext = this.DataContext;
PrintDialog printDlg = new System.Windows.Controls.PrintDialog();
if (printDlg.ShowDialog() == true)
{
pi.Margin = new Thickness(30);
//now print the visual to printer to fit on the one page.
printDlg.PrintVisual(pi, "First Fit to Page WPF Print");
}
}
This could be caused by a number of different things. There are a few steps that you can add which, when performed correctly, may cause the flying men to return with goods and knowledge.
First, you should scale to the printed page (code from a2zdotnet):
System.Printing.PrintCapabilities capabilities =
printDlg.PrintQueue.GetPrintCapabilities(printDlg.PrintTicket);
//get scale of the print wrt to screen of WPF visual
double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / this.ActualWidth, capabilities.PageImageableArea.ExtentHeight /
this.ActualHeight);
//Transform the Visual to scale
this.LayoutTransform = new ScaleTransform(scale, scale);
//get the size of the printer page
Size sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
//update the layout of the visual to the printer page size.
this.Measure(sz);
this.Arrange(new Rect(new Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight), sz));
//now print the visual to printer to fit on the one page.
printDlg.PrintVisual(this, "Code ganked from http://www.a2zdotnet.com/View.aspx?id=66");
The cargo-cult code is in the Measure and Arrange steps. Often, if you call Measure and pass in a new Size(Double.MaxValue, Double.MaxValue) that's all you need to do.
The second ritual involves bumping the Dispatcher.
visual.DataContext = foo;
Dispatcher.Invoke((Action)()=>{;}); // bamp
// print here
Try these and see if it helps.
this XAML does the trick in some situations
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<... Name="myPrintElement" />
</ScrollViewer >