There are many complaints online (such as this one and this one) about the printing of a webpage in WebVeiw where the printed page only shows the current view on the screen and not the entire page. By going through some code from online searches, I was able to print the entire page using the following code. But the printed page has tried to print it all in one page with a scrollbar enabled. Moreover, the printed content is very blurred (as shown below).
Question: How can we make the printed content not blurred while still printing the entire page with scrollbar enable?
Website used in the WebView: Wikipedia
Reference: WebView to WebViewBrush
MainPage.xaml:
<Page
x:Class="UWP_WebViewPrinting.MainPage"
....>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<WebView x:Name="wvTest" Source="https://en.wikipedia.org/wiki/Universal_Windows_Platform" Grid.Row="1" Margin="0,61,0,0"/>
<Button x:Name="btnTest" Content="Test" Grid.Row="0" VerticalAlignment="Top" Click="btnTest_Click"/>
<Rectangle x:Name="RectangleToPrint" Grid.Row="1"/>
</Grid>
</Page>
MainPage.xaml.cs:
private async void btnTest_Click(object sender, RoutedEventArgs e)
{
//Step 1: use WebViewBrush to render the content of webview into the Rectangle
int width;
int height;
// get the total width and height
var widthString = await wvTest.InvokeScriptAsync("eval", new[] { "document.body.scrollWidth.toString()" });
var heightString = await wvTest.InvokeScriptAsync("eval", new[] { "document.body.scrollHeight.toString()" });
if (!int.TryParse(widthString, out width))
{
throw new Exception("Unable to get page width");
}
if (!int.TryParse(heightString, out height))
{
throw new Exception("Unable to get page height");
}
// resize the webview to the content
wvTest.Width = width;
wvTest.Height = height;
WebViewBrush b = new WebViewBrush();
b.SourceName = "wvTest";
b.Redraw();
RectangleToPrint.Fill = b;
//Step 2: Then print the rectangle
if (PrintManager.IsSupported())
{
try
{
// Show print UI
await PrintManager.ShowPrintUIAsync();
}
catch
{
// Printing cannot proceed at this time
ContentDialog noPrintingDialog = new ContentDialog()
{
Title = "Printing error",
Content = "\nSorry, printing can' t proceed at this time.",
PrimaryButtonText = "OK"
};
await noPrintingDialog.ShowAsync();
}
}
else
{
// Printing is not supported on this device
ContentDialog noPrintingDialog = new ContentDialog()
{
Title = "Printing not supported",
Content = "\nSorry, printing is not supported on this device.",
PrimaryButtonText = "OK"
};
await noPrintingDialog.ShowAsync();
}
}
#region Register for printing
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Register for PrintTaskRequested event
printMan = PrintManager.GetForCurrentView();
printMan.PrintTaskRequested += PrintTaskRequested;
// Build a PrintDocument and register for callbacks
printDoc = new PrintDocument();
printDocSource = printDoc.DocumentSource;
printDoc.Paginate += Paginate;
printDoc.GetPreviewPage += GetPreviewPage;
printDoc.AddPages += AddPages;
}
#endregion
#region Showing the print dialog
private void PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs args)
{
// Create the PrintTask.
// Defines the title and delegate for PrintTaskSourceRequested
var printTask = args.Request.CreatePrintTask("Print", PrintTaskSourceRequrested);
// Handle PrintTask.Completed to catch failed print jobs
printTask.Completed += PrintTaskCompleted;
}
private void PrintTaskSourceRequrested(PrintTaskSourceRequestedArgs args)
{
// Set the document source.
args.SetSource(printDocSource);
}
#endregion
#region Print preview
private void Paginate(object sender, PaginateEventArgs e)
{
// As I only want to print one Rectangle, so I set the count to 1
printDoc.SetPreviewPageCount(1, PreviewPageCountType.Final);
}
private void GetPreviewPage(object sender, GetPreviewPageEventArgs e)
{
// Provide a UIElement as the print preview.
printDoc.SetPreviewPage(e.PageNumber, this.RectangleToPrint);
}
#endregion
#region Add pages to send to the printer
private void AddPages(object sender, AddPagesEventArgs e)
{
printDoc.AddPage(this.RectangleToPrint);
// Indicate that all of the print pages have been provided
printDoc.AddPagesComplete();
}
#endregion
#region Print task completed
private async void PrintTaskCompleted(PrintTask sender, PrintTaskCompletedEventArgs args)
{
// Notify the user when the print operation fails.
if (args.Completion == PrintTaskCompletion.Failed)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
ContentDialog noPrintingDialog = new ContentDialog()
{
Title = "Printing error",
Content = "\nSorry, failed to print.",
PrimaryButtonText = "OK"
};
await noPrintingDialog.ShowAsync();
});
}
}
#endregion
Printed PDF: Available here
UPDATE:
A response from #Faywang - MSFT seems quite promising. I tried it as follows:
Created a method call PrintWebView() with the code from the OnPrintButtonClick() event from here
Added all the other methods/events shown in my post above.
In my btnTest_Click(...), I modified the code from user #Faywang as follows
.
List<Rectangle> allpages = await GetWebPages(wvTest, new Windows.Foundation.Size(750d, 950d));
//print these pages
foreach (Rectangle rectangle in allpages)
PrintWebView();
When the app runs in default mode it shows count for allpages to be 7 and the above foreach calls PrintWebView() 7 times. If I put the app screen in max mode, the count for allpages is 3 and the foreach calls PrintWebView() 3 times (as expected). In both cases I was expecting the last iteration of the loop will bring the print dialog and print all pages 7 (or 3 depending on default or max mode of the app's screen). Instead, the debugger went to the catch block of the PrintWebView() method whose code, as stated earlier, is taken from here
Question: Did I follow the above steps correctly? If not, what would be a better way of doing it?
When you compress and redraw the webview to the rectangle, it will become blurred, it's better to display the webview in mutiple pages. First I give the width and height of printed page as 750, 950, and then according to the scale to cacluate how many pages it needs to be. After that, you can print these Rectangles. For example:
private async void btnTest_Click(object sender, RoutedEventArgs e)
{
allPages = await GetWebPages(wvTest, new Windows.Foundation.Size(750d, 950d));
//print these pages
}
async Task<List<Windows.UI.Xaml.Shapes.Rectangle>> GetWebPages(Windows.UI.Xaml.Controls.WebView webView, Windows.Foundation.Size page)
{
// ask the content its width
var _WidthString = await webView.InvokeScriptAsync("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 = await webView.InvokeScriptAsync("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 Windows.UI.Xaml.Thickness(5),
Tag = new Windows.UI.Xaml.Media.TranslateTransform { Y = _TranslateY },
};
_Page.Loaded += async (s, e) =>
{
var _Rectangle = s as Windows.UI.Xaml.Shapes.Rectangle;
var _Brush = await GetWebViewBrush(webView);
_Brush.Stretch = Windows.UI.Xaml.Media.Stretch.UniformToFill;
_Brush.AlignmentY = Windows.UI.Xaml.Media.AlignmentY.Top;
_Brush.Transform = _Rectangle.Tag as Windows.UI.Xaml.Media.TranslateTransform;
_Rectangle.Fill = _Brush;
};
_Pages.Add(_Page);
}
return _Pages;
}
async Task<WebViewBrush> GetWebViewBrush(Windows.UI.Xaml.Controls.WebView webView)
{
// resize width to content
var _OriginalWidth = webView.Width;
var _WidthString = await webView.InvokeScriptAsync("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 = await webView.InvokeScriptAsync("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 = Windows.UI.Xaml.Media.Stretch.Uniform
};
_Brush.Redraw();
// reset, return
webView.Width = _OriginalWidth;
webView.Height = _OriginalHeight;
webView.Visibility = _OriginalVisibilty;
return _Brush;
}
Related
This is a C# web form project I am starting with after a long time away from IDE coding...
I am trying to make a simple custom dialog box class. This is my code.
public static class Dialogo
{
public static int show ()
{
Form dialogo = new Form();
dialogo.Width = 300;
dialogo.Height = 300;
Button btnSim = new Button() { Text = "Sim", Left = 30, Width = 100 };
Button btnNao = new Button() { Text = "Não", Left = 150, Width = 100 };
dialogo.Controls.Add(btnSim);
dialogo.Controls.Add(btnNao);
dialogo.ShowDialog();
// the following two lines are the problematic ones
btnSim += new EventHandler(btnSim_Click);
btnNao += new EventHandler(btnNao_Click);
return -1;
}
}
It's underlining the text within parenthesis and the message says:
The name btnSim_Click' does not exist in the current context
The problem is that I tried to add the following in my code but it doesn't let me put it anywhere (it always says that is something wrong):
private int btnNao_Click (object sender, EventArgs e)
{
return 0;
}
private int btnSim_Click (object sender, EventArgs e)
{
return 1;
}
My objective is that each of the both buttons btnSim and btnNao return a different value (say 1 and 0).
What am I doing wrong?
EventHandler is a delegate for a method that returns void.
Your methods return int.
Try something like this:
public static int show()
{
int returnValue = -1;
using (Form dialogo = new Form())
{
dialogo.Width = 300;
dialogo.Height = 300;
Button btnSim = new Button() { Text = "Sim", Left = 30, Width = 100 };
Button btnNao = new Button() { Text = "Não", Left = 150, Width = 100 };
dialogo.Controls.Add(btnSim);
dialogo.Controls.Add(btnNao);
btnSim.Click += (s, e) => { returnValue = 0; dialogo.DialogResult = DialogResult.OK; };
btnNao.Click += (s, e) => { returnValue = 1; dialogo.DialogResult = DialogResult.OK; };
dialogo.Disposed += (s, e) =>
{
btnSim?.Dispose();
btnSim = null;
btnNao?.Dispose();
btnNao = null;
};
dialogo.ShowDialog();
}
return returnValue;
}
First off, I'm not sure if I phrased the title correctly. There are UserControls which are added via a ViewModel and I find them by searching the VisualTree and add them to a ObservableCollection<Grid>. What I would like to do is Print each instance of the UserControl that I retrieve from the VisualTree into a FixedDocument but with each UserControl being on a single page until it fills that page and moves on to the next page.
Here is the code for my current Print Button Click Event:
private async void btnPrint_Click(object sender, RoutedEventArgs e)
{
try
{
if (tabMain.Items.Count > 0)
{
tab = new TabLayout();
tab.Payslip = new ObservableCollection<Grid>();
foreach (var grid in FindVisualChildren<Grid>(this))
{
if (grid.Name == "pdfFile")
{
tab.Payslip.Add(grid);
}
}
FrameworkElement toPrint = new FrameworkElement();
PrintDialog printDialog = new PrintDialog();
PrintCapabilities capabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
Size pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);
Size visibleSize = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
FixedDocument fixedDoc = new FixedDocument();
StackPanel panel = new StackPanel(); //was trying to stack them in a stackpanel first but it threw an exception about same instance of usercontrol blah blah...
foreach (var doc in tab.Payslip.ToList())
{
double yOffset = 0;
doc.Measure((new Size(double.PositiveInfinity, double.PositiveInfinity)));
doc.Arrange(new Rect(new Point(0, 0), doc.DesiredSize));
Size size = doc.DesiredSize;
while (yOffset < size.Height)
{
VisualBrush vb = new VisualBrush(doc);
vb.Stretch = Stretch.None;
vb.AlignmentX = AlignmentX.Left;
vb.AlignmentY = AlignmentY.Top;
vb.ViewboxUnits = BrushMappingMode.Absolute;
vb.TileMode = TileMode.None;
vb.Viewbox = new Rect(0, yOffset, visibleSize.Width, visibleSize.Height);
FixedPage page = new FixedPage();
PageContent pageContent = new PageContent();
((IAddChild)pageContent).AddChild(page);
fixedDoc.Pages.Add(pageContent);
page.Width = fixedDoc.DocumentPaginator.PageSize.Width;
page.Height = fixedDoc.DocumentPaginator.PageSize.Height;
Canvas 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;
}
}
//printDialog.PrintDocument(fixedDoc.DocumentPaginator, "");
ShowPrintPreview(fixedDoc);
}
}
catch(Exception ex)
{
var exceptionDialog = new MessageDialog
{
Message = { Text = ex.ToString() }
};
await DialogHost.Show(exceptionDialog, "RootDialog");
}
}
This is how it looks when I try printing three Tabs (UserControls are hosted in Tabs):
As you can see here it prints three separate pages
I want all three on one page and if I print 10 tabs, then it should fill the first page and go on to the next page.
The last time I asked a similar question I was questioned on whether I wrote the code. Bits and pieces of this code came from similar FixedDocument questions on StackOverflow but it has been edited to the point where it actually works for me. So yes I know that the FixedPage reference inside the foreach is whats causing the creation of the three separate pages and I do understand the code.
Summary:
What I want to know is how to get the UserControls from each Tab, onto a single page until its full, without getting the "Specified element is already the logical child of another element. Disconnect it first." error.
I ended up using ItemsControl to hold my ObservableCollection of Controls as a list and print from the ItemsControl using this XpsDocumentWriter method.
public void PrintItemsTo(ItemsControl ic, String jobName)
{
PrintDialog dlg = new PrintDialog();
dlg.UserPageRangeEnabled = true;
if (dlg.ShowDialog().GetValueOrDefault())
{
PageRange range = dlg.PageRange;
// range check - user selection starts from 1
if (range.PageTo > ic.Items.Count)
range.PageTo = ic.Items.Count;
dlg.PrintQueue.CurrentJobSettings.Description = jobName;
XpsDocumentWriter xdw = PrintQueue.CreateXpsDocumentWriter(dlg.PrintQueue);
if (dlg.UserPageRangeEnabled == false || range.PageTo < range.PageFrom)
WriteAllItems(ic, xdw);
else
WriteSelectedItems(ic, xdw, range);
}
}
private void WriteAllItems(ItemsControl ic, XpsDocumentWriter xdw)
{
PageRange range = new PageRange(1, ic.Items.Count);
WriteSelectedItems(ic, xdw, range);
}
private void WriteSelectedItems(ItemsControl ic, XpsDocumentWriter xdw, PageRange range)
{
IItemContainerGenerator generator = ic.ItemContainerGenerator;
// write visuals using a batch operation
VisualsToXpsDocument collator = (VisualsToXpsDocument)xdw.CreateVisualsCollator();
collator.BeginBatchWrite();
if (WritePageRange(collator, generator, range))
collator.EndBatchWrite();
}
private bool WritePageRange(VisualsToXpsDocument collator, IItemContainerGenerator generator, PageRange range)
{
// Get the generator position of the first data item
// PageRange reflects user's selection starts from 1
GeneratorPosition startPos = generator.GeneratorPositionFromIndex(range.PageFrom - 1);
// If PageFrom > PageTo, print in reverse order
// UPDATE: never occurs; PrintDialog always 'normalises' the PageRange
GeneratorDirection direction = (range.PageFrom <= range.PageTo) ? GeneratorDirection.Forward : GeneratorDirection.Backward;
using (generator.StartAt(startPos, direction, true))
{
for (int numPages = Math.Abs(range.PageTo - range.PageFrom) + 1; numPages > 0; --numPages)
{
bool newlyRealized;
// Get or create the child
UIElement next = generator.GenerateNext(out newlyRealized) as UIElement;
if (newlyRealized)
{
generator.PrepareItemContainer(next);
}
// The collator doesn't like ContentPresenters and produces blank pages
// for pages 2-n. Finding the child of the ContentPresenter and writing
// that solves seems to solve the problem
if (next is ContentPresenter)
{
ContentPresenter presenter = (ContentPresenter)next;
presenter.UpdateLayout();
presenter.ApplyTemplate(); // not sure if this is necessary
DependencyObject child = VisualTreeHelper.GetChild(presenter, 0);
if (child is UIElement)
next = (UIElement)child;
}
try
{
collator.Write(next);
}
catch
{
// if user prints to Microsoft XPS Document Writer
// and cancels the SaveAs dialog, we get an exception.
return false;
}
}
}
return true;
}
PrintItemsTo(), displays a PrintDialog to print to any printer while WriteAllItems displays a Dialog to save as PDF.
Summary: I have a collection FrameworkElements (basically web view brushes drawn on rectanlges), and I'd like to save each of these as a PNG file in my UWP app.
More details: I followed the example at https://stackoverflow.com/a/17222629/2884981 to split the content of a WebView into separate "pages".
I've put the main bits of code at the bottom.
At the bottom of GetWebPages() I have return pages;
At this point I have a list of all the "pages".
What I'd like to do, is then convert each of those pages into an image (so by the end of it I'd have a collection of PNG files, for instance).
Does anyone know how I can do this? Thanks in advance.
public async Task<WebViewBrush> GetWebViewBrush(WebView webView)
{
// resize width to content
double originalWidth = webView.Width;
var widthString = await webView.InvokeScriptAsync("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
double originalHeight = webView.Height;
var heightString = await webView.InvokeScriptAsync("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;
WebViewBrush brush = new WebViewBrush
{
SourceName = webView.Name,
Stretch = Stretch.Uniform
};
brush.Redraw();
// reset, return
webView.Width = originalWidth;
webView.Height = originalHeight;
webView.Visibility = originalVisibilty;
return brush;
}
And:
public async Task<IEnumerable<FrameworkElement>> GetWebPages(WebView webView, Windows.Foundation.Size page)
{
// ask the content its width
var widthString = await webView.InvokeScriptAsync("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 = await webView.InvokeScriptAsync("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?
double scale = page.Width / contentWidth;
double scaledHeight = (contentHeight * scale);
double 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 rectanglePage = new Windows.UI.Xaml.Shapes.Rectangle
{
Height = page.Height,
Width = page.Width,
Margin = new Thickness(5),
Tag = new TranslateTransform { Y = translateY },
};
rectanglePage.Loaded += (async (s, e) =>
{
var subRectangle = s as Windows.UI.Xaml.Shapes.Rectangle;
var subBrush = await GetWebViewBrush(webView);
subBrush.Stretch = Stretch.UniformToFill;
subBrush.AlignmentY = AlignmentY.Top;
subBrush.Transform = subRectangle.Tag as TranslateTransform;
subRectangle.Fill = subBrush;
});
pages.Add(rectanglePage);
}
return pages;
}
I'd like to save each of these as a PNG file in my UWP app.
You can get all the rectangles and show them in the ItemsControl in the NavigationCompleted event of WebView like this:
private async void webView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
MyWebViewRectangle.Fill = await GetWebViewBrush(webView);
MyPrintPages.ItemsSource = await GetWebPages(webView, new Windows.Foundation.Size(842, 595));
}
Then in a button click event you can save all the items as .png images like this:
private async void Savetoimage_Clicked(object sender, RoutedEventArgs e)
{
var piclib = Windows.Storage.KnownFolders.PicturesLibrary;
foreach (var item in MyPrintPages.Items)
{
var rect = item as Rectangle;
RenderTargetBitmap renderbmp = new RenderTargetBitmap();
await renderbmp.RenderAsync(rect);
var pixels = await renderbmp.GetPixelsAsync();
var file = await piclib.CreateFileAsync("webview.png", Windows.Storage.CreationCollisionOption.GenerateUniqueName);
using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
byte[] bytes = pixels.ToArray();
encoder.SetPixelData(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)rect.Width, (uint)rect.Height,
0, 0, bytes);
await encoder.FlushAsync();
}
}
}
Here is how I can capture screen shot of current webpage, but sometimes it is capturing only visible area of webview (what user actually see).
But I'm not sure what was wrong.
I think var heightString = await WebView.InvokeScriptAsync("eval", new[] { "document.body.scrollHeight.toString()" }) was finish too late - but program should wait in if (!int.TryParse(heightString, out height)) to complete... Am I wrong?
private async Task CaptureWebView()
{
int width;
int height;
var originalWidth = WebView.Width;
var originalHeight = WebView.Height;
// ask the content its width
var widthString = await WebView.InvokeScriptAsync("eval", new[] { "document.body.scrollWidth.toString()" });
// ask the content its height
var heightString = await WebView.InvokeScriptAsync("eval", new[] { "document.body.scrollHeight.toString()" });
if (!int.TryParse(widthString, out width))
{
throw new Exception("Unable to get page width");
}
if (!int.TryParse(heightString, out height))
{
throw new Exception("Unable to get page height");
}
// resize the webview to the content
WebView.Width = width;
WebView.Height = height;
await DoCapture("captured.png");
WebView.Width = originalWidth;
WebView.Height = originalHeight;
Painter.Width = width;
Painter.Height = height;
var i = await LoadCaptured("captured.png");
Painter.Fill = i;
}
Test repository on Github
At the end problem itself was in WebView.CapturePreviewToStreamAsync() method. I used it to capture full web screen shot, but it didn't work very well. But then I found WebViewBrush and realized that is the right way.
So here is final code snippet or GitHub
private async Task CaptureWebView()
{
int width;
int height;
var originalWidth = WebView.ActualWidth;
var originalHeight = WebView.ActualHeight;
var widthString = await WebView.InvokeScriptAsync("eval", new[] { "document.body.scrollWidth.toString()" });
var heightString = await WebView.InvokeScriptAsync("eval", new[] { "document.body.scrollHeight.toString()" });
if (!int.TryParse(widthString, out width))
{
throw new Exception("Unable to get page width");
}
if (!int.TryParse(heightString, out height))
{
throw new Exception("Unable to get page height");
}
// resize the webview to the content
WebView.Width = width;
WebView.Height = height;
var brush = new WebViewBrush();
brush.SetSource(WebView);
Painter.Width = width;
Painter.Height = height;
Painter.Fill = brush;
}
I'm building a series of apps for my place of business so I'm trying to create my own printing class that I can refer to for all of my applications.
The issue is, I'm trying to figure out a way for the application to tell the class when to print the page, and I am unable to find a way to do so.
For example, this is what I have so far:
// Report Variables
private bool bPrinting = false;
private int iPage = 0;
private float fOverflow = 0.00F;
private string sPrintLine = null;
private Font fontTmpFont = null;
private PrintPageEventArgs ppeaEv = null;
private Margins mMargins = new System.Drawing.Printing.Margins(25, 25, 25, 25); // Set wide margins
// Clear the print line
public void LineClear()
{
sPrintLine = null;
}
// Insert a string into the print line at the specified position within the line
public void LineInsert(string _InsertString, int _InsertPosition)
{
if (sPrintLine.Length <= _InsertPosition)
sPrintLine = sPrintLine.PadLeft(_InsertPosition) + _InsertString;
else if (sPrintLine.Length <= (_InsertPosition + _InsertString.Length))
sPrintLine = sPrintLine.Substring(0, _InsertPosition) + _InsertString;
else
sPrintLine = sPrintLine.Substring(0, _InsertPosition) + _InsertString + sPrintLine.Substring(_InsertPosition + _InsertString.Length);
}
// Check to see if the line we're trying to print is at the end of the page
public bool AtEndOfPage()
{
return AtEndOfPage(new Font("Courier", 10));
}
public bool AtEndOfPage(Font _Font)
{
if ((fOverflow + _Font.GetHeight(ppeaEv.Graphics)) > ppeaEv.MarginBounds.Height)
return true;
else
return false;
}
// Attempt to print the line
public void LinePrint()
{
LinePrint(null, null);
}
public void LinePrint(Font _Font)
{
LinePrint(_Font, null);
}
public void LinePrint(Font _Font, Brush _Color)
{
if (_Font == null)
_Font = new Font("Courier", 10);
if (_Color == null)
_Color = Brushes.Black;
ppeaEv.Graphics.DrawString(sPrintLine, _Font, _Color,
ppeaEv.MarginBounds.Left, ppeaEv.MarginBounds.Top + fOverflow,
new StringFormat()); // 'Draw' line on page
fOverflow += _Font.GetHeight(ppeaEv.Graphics);
}
// We are done with the report, tell the Print Service to finish up
public void EndReport()
{
ppeaEv.HasMorePages = false;
}
// This is what gets called when the user clicks on 'Print'
private void Print_Click(object sender, EventArgs e)
{
PrintDocument printDocument = new PrintDocument();
printDocument.DefaultPageSettings.Margins = mMargins; // Set margins for pages
printDocument.DefaultPageSettings.Landscape = false; // Set Portrait mode
printDocument.BeginPrint += new PrintEventHandler(printDocument_BeginPrint);
printDocument.EndPrint += new PrintEventHandler(printDocument_EndPrint);
PrintDialog printDialog = new PrintDialog();
printDialog.Document = printDocument; // Set the Document for this print dialog
printDialog.AllowSomePages = true; // Allow the user to select only some pages to print
printDialog.ShowHelp = true; // Allow help button
DialogResult result = printDialog.ShowDialog();
if (result == DialogResult.OK)
{
printDocument.Print(); // Raises PrintPage event
}
printDocument.Dispose();
printDialog.Dispose();
}
void printDocument_BeginPrint(object sender, PrintEventArgs e)
{
iPage = 0;
fOverflow = 0.00F;
}
void printDocument_EndPrint(object sender, PrintEventArgs e)
{
}
As you may be able to tell, I'm trying to fill the page externally to the class, and then have the app tell the class when to print the page when "AtEndOfPage"
I know about "printDocument.PrintPage", but that doesn't seem to be what I need; It builds the page internal to the method and then prints it. I'm not going to be building the print page within that method.
I also am trying to allow for multiple fonts on a single page.
Is there a way to do this?
Thank you all in advance,
Robert