C# Webview2 Margins doesn't work in print function - c#

We want to use the print function in WebView2, the Version of Webview2 is 1.0.1108.44. We try to print HTML to PDF. Our expectation is that the image starting from top left without any spaces. Therefore, we try to set the margins in the CoreWebView2PrintSettings, but it seems like no effect on the final result
private async void InitializeAsync()
{
WebViewTest.NavigationCompleted += WebView_NavigationCompleted;
await WebViewTest.EnsureCoreWebView2Async(null);
WebViewEnvironment = await Microsoft.Web.WebView2.Core.CoreWebView2Environment.CreateAsync().ConfigureAwait(true);
}
private async void WebView_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e)
{
//WebViewTest.CoreWebView2.ExecuteScriptAsync("window.print();");
CoreWebView2PrintSettings lPrintSettings = WebViewEnvironment.CreatePrintSettings();
lPrintSettings.MarginBottom = 0;
lPrintSettings.MarginLeft = 0;
lPrintSettings.MarginRight = 0;
lPrintSettings.MarginTop = 0;
lPrintSettings.ShouldPrintBackgrounds = true;
lPrintSettings.ShouldPrintHeaderAndFooter = false;
lPrintSettings.ShouldPrintSelectionOnly = false;
await WebViewTest.CoreWebView2.PrintToPdfAsync(PathPDF, lPrintSettings);
}
and then we get the result, it still contains a lot of spaces on the top and right sides
The expected print result is print with
WebViewTest.CoreWebView2.ExecuteScriptAsync("window.print();");
It uses windows print, and margins could be set to None, and image starts from top left.
Could you please help us to solve this problem, maybe we have set something wrong? Thank you in advance!
Update:
My original way is using HTML file and navigate function of CoreWebView2. In this case Margins doesn't work
private string PathHTML = #"E:\Capture.html";
WebViewTest.CoreWebView2.Navigate(PathHTML);
Now I try the code from Haldo, and put the capture.jpeg in the folder C:\inetpub\wwwroot, load this jpeg with img src and using NavigateToString, then Margins works
WebViewTest.NavigateToString(GetHtml());
private string GetHtml()
{
return #"<html><body>
<div>
<p style=""color: red; font-weight: bold;"">Some text</p>
<img src=""http://localhost/capture.JPG"" alt=""test image""/><br/>
</div>
</html></body>";
}
The result pdf screenshot is here, set Margin to 0 and set Margin to 1
But I still don't know, why in my case the margins doesn't work

Related

WinUI3: Get the WebView2 HTML height

I'm currently using a WebView2 in my WinUI3 application to display some HTML which is sent from our server.
The HTML itself doesn't contains a body / html tags and is displayed through NavigateToString:
await web.EnsureCoreWebView2Async();
web.NavigationCompleted += async (sender, args) => await sender.ResizeToContent(); // more about this later
web.NavigateToString(someText);
When I display this HTML in my WebView, the WebView's height is always set at 0 by default, and I want my WebView to autosize to its content (I cannot set a fixed sized for its container and stretch the webview to it).
I tried executing scripts found there to evaluate the HTML's size:
How to get height of entire document with JavaScript? :
public static async Task ResizeToContent(this WebView2 webView)
{
var script = "";
var heightString = await webView.ExecuteScriptAsync(script);
int height = 0;
if (int.TryParse(heightString, out height))
{
webView.Height = height;
}
}
Here are 2 differents scripts I tried:
eval(document.documentElement.scrollHeight.toString());
and
;(function() {
var pageHeight = 0;
function findHighestNode(nodesList) {
for (var i = nodesList.length - 1; i >= 0; i--) {
if (nodesList[i].scrollHeight && nodesList[i].clientHeight) {
var elHeight = Math.max(nodesList[i].scrollHeight, nodesList[i].clientHeight);
pageHeight = Math.max(elHeight, pageHeight);
}
if (nodesList[i].childNodes.length) findHighestNode(nodesList[i].childNodes);
}
}
findHighestNode(document.documentElement.childNodes);
return pageHeight;
})();
But in both cases, no mater the HTML provided, it always returns 1040 even with a simple HTML such as <p>test</p>
When I set a fixed height for my WebView, let's say of 60, this p is displayed correctly without scrollbar (while my script would return a height of 1040) BUT when I do some complex HTML intended to be bigger than those 60px, the webview displays a vertical scrollbar.
So all in one, it seems the WebView somehow knows that 1040 is not the real height (otherwise I'd have a scrollbar all the time).
Note that I've also tried to surround my text with <html><body>{myText}</body></html> with the same result.
How can I get the real actual content's height?
Thanks.
After trying other solutions, here is what I came up with which seems to work:
In my ViewModel:
Text = $"<div id='container'>{_source.Text}</div>";
And in my extension method to get the height:
public static async Task ResizeToContent(this WebView2 webView)
{
var script = "eval(document.getElementById('container').getBoundingClientRect().height.toString());";
var heightString = await webView.ExecuteScriptAsync(script);
if (int.TryParse(heightString, out int height))
{
webView.Height = height + 30;
}
}
I had to add the +30 because otherwise the scrollbar was displayed and the webview's content slightly truncated.
Is there any cleaner and less hacky way to do this?

Efficient way to display fast changing images in Blazor Server/Hybrid

I'm considering using Blazor Hybrid to rewrite my app. So far, I'm loving the productivity, but one of the core features is image processing.
To check whether the Blazor Hybrid is a valid option, I created a sample WPF app, added Blazor and I noticed that just binding to a source of <img> tag is extremely slow.
Here is a component I created:
#using System.Reactive.Subjects
#using System.Reactive
#using System.Reactive.Linq
#using SkiaSharp
#using MudBlazor
<h3>FollowCursor</h3>
<MudCheckBox #bind-Checked="#hideImage">Hide image with opacity</MudCheckBox>
<MudCheckBox #bind-Checked="#useVisibillityHidden">Visibillity hidden</MudCheckBox>
<MudCheckBox #bind-Checked="#useImageSource">Use image source</MudCheckBox>
<MudCheckBox #bind-Checked="#disableStateHasChangedOnMove">Disable StateHasChanged on move</MudCheckBox>
<p>Mouse position: #MousePosition</p>
<p>Image size: #ImageSize</p>
<div style="width: 100%; height: 500px;border: solid green 1px" #ref="Img" #onmousemove="disableStateHasChangedOnMove ? EventUtil.AsNonRenderingEventHandler<MouseEventArgs>(onMouseMove) : onMouseMove">
<img src="#ImageSource" style="height: 100%; width: 100%; margin: auto; border: solid red 1px;opacity: #(hideImage ? 0 : 1); visibility: #(useVisibillityHidden ? "hidden" : "visible")"
/>
</div>
#code {
public ElementReference Img { get; set; }
public string? ImageSource
{
get => useImageSource ? _imageSource : null;
set => _imageSource = value;
}
public SKPoint MousePosition { get; set; }
public SKSize ImageSize { get; set; }
Subject<Unit> _mouseMove = new();
private string? _imageSource;
bool useImageSource = true;
bool hideImage = false;
bool useVisibillityHidden = false;
bool disableStateHasChangedOnMove = true;
protected override async Task OnInitializedAsync()
{
StateHasChanged();
_mouseMove
// .Sample(TimeSpan.FromMilliseconds(1))
.Do(async _ =>
{
var drawMousePosition = DrawMousePosition();
await InvokeAsync(() =>
{
ImageSource = drawMousePosition;
if (disableStateHasChangedOnMove)
{
StateHasChanged();
}
});
}).Subscribe();
}
private string DrawMousePosition()
{
var bmp = new SKBitmap((int)ImageSize.Width, (int)ImageSize.Height);
using var canvas = new SKCanvas(bmp);
canvas.Clear(SKColors.White);
canvas.DrawCircle(MousePosition.X, MousePosition.Y, 10, new SKPaint
{
Color = SKColors.Red,
Style = SKPaintStyle.Fill
});
canvas.Save();
return ToBase64Image(bmp);
}
private async Task onMouseMove(MouseEventArgs e)
{
MousePosition = new SKPoint((float)e.OffsetX, (float)e.OffsetY);
_mouseMove.OnNext(Unit.Default);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
var rect = await Img.MudGetBoundingClientRectAsync();
ImageSize = new SKSize((float)rect.Width, (float)rect.Height);
}
public static string ToBase64Image( SKBitmap bmp)
{
using var image = SKImage.FromBitmap(bmp);
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
return "data:image/png;base64," + Convert.ToBase64String(data.ToArray());
}
}
Its job is to draw a circle under your cursor. This is how I achieved various drawing tools in the WPF app I am trying to replace - for example when you are drawing a line, I generate a preview image every time your mouse moves and refresh it. When LMB is released, latest preview replaces the edited image.
In WPF or AvaloniaUI this approach works extremely well.
In Blazor, it is really slow. I believe that the reason is converting base64 image to a bitmap in the BlazorWebView is the bottleneck, you can verify with the checkboxes. Also, I know its a good idea to silence the autotriggering of StateHasChanged on mouse move, but the result still isn't great.
Is there a better way to do this? I know that there is a special high performance view component for SkiaSharp, but it works only with Blazor WA - it is based on reusing memory between renderer and the bitmap itself. Not possible in Hybrid and server. There is an option of creating this in Blazor WA and publishing it as a custom component and embeding it in the Hybrid app, but it seems like a weird workaround.
As an alternative, I know that I can create the image editing part in the host tech for Hybrid (like maui to get multiplatform support), and show it on top of when needed, but maybe there is a way to do this in Blazor.
I found some canvas wrappers, but they still require to create the img tag with bound source, and this is the slowest part.
Any suggestions? Maybe the whole idea behind how to draw the preview is wrong?
For such a scenario you should use a javascript implementation and just steer/set the point on the client's browser. That way you avoid sending massive data to the client and all the dom-diffing that is so time consuming.

WPF RichTexBox printing is creating unexpected line breaks

In RichTextBox of my WPF app, following print method is creating unexpected line breaks. Question: What I may be missing here, and how can we fix the issue?
For example, when I enter the following text in the RichTextBox (RTB), the RTB looks like as shown in image 1. But when I call the following two print methods the first one does not create the unexpected line breaks, but the second method does create unexpected line breaks:
MainWindow.xaml
<StackPanel>
<RichTextBox Name="richTB" />
<Button Click="PrintCommand1">Print RTB Content</Button>
<Button Click="PrintCommand2">Print RTB Content</Button>
</StackPanel>
Method 1
private void PrintCommand1(Object sender, RoutedEventArgs args)
{
PrintDialog pd = new PrintDialog();
if ((pd.ShowDialog() == true))
{
pd.PrintVisual(richTB as Visual, "printing as visual");
}
}
Method 2
private void PrintCommand2(Object sender, RoutedEventArgs args)
{
PrintDialog pd = new PrintDialog();
if ((pd.ShowDialog() == true))
{
pd.PrintDocument((((IDocumentPaginatorSource)richTB.Document).DocumentPaginator), "printing as paginator");
}
}
The text I enter [Note: There is only one line break]
This is a test for testing purpose only. Another test: x6. Let us do some background and foreground colors.
This is a new line with formatting, as well.
Snapshot of the RichTexBox with above text
Snapshot of "Print to PDF" (on Windows 10) using Method 1 [Printed correctly with one real line break]
Snapshot of "Print to PDF" (on Windows 10) using Method 2 [Printed incorrectly with unexpected line breaks]
Because of the DocumentPaginator class takes context of the FlowDocument and split in into multiple pages to get desired result some of FlowDocument parameters should be configured before printing:
private void PrintCommand2(Object sender, RoutedEventArgs args)
{
var pd = new PrintDialog();
if (pd.ShowDialog() == true)
{
FlowDocument doc = richTB.Document;
// Save all settings that will be configured for printing.
double pageHeight = doc.PageHeight;
double pageWidth = doc.PageWidth;
double columnGap = doc.ColumnGap;
double columnWidth = doc.ColumnWidth;
// Make the FlowDocument page match the printed page.
doc.PageHeight = pd.PrintableAreaHeight;
doc.PageWidth = pd.PrintableAreaWidth;
doc.ColumnGap = 5;
// Set the minimum desired width of the column in the System.Windows.Documents.FlowDocument.
doc.ColumnWidth = doc.PageWidth - doc.ColumnGap - doc.PagePadding.Left - doc.PagePadding.Right;
pd.PrintDocument(((IDocumentPaginatorSource)doc).DocumentPaginator, "A Flow Document");
// Reapply the old settings.
doc.PageHeight = pageHeight;
doc.PageWidth = pageWidth;
doc.ColumnGap = columnGap;
doc.ColumnWidth = columnWidth;
}
}
With respect to Matthew MacDonald this way of the flow document content printing and more advanced techniques described in his book Pro WPF 4.5 in C# Windows Presentation Foundation in .NET 4.5 (Chapter 29).

How to set background image to non-tiling in asp.net code behind

Here is my code that i used to display an image in the background , but it is repeating and tiling . How do i do it in code behind to set the image to non-tiling ? i never use CSS to display the background image :
protected void Image_Selection_Change(object sender, EventArgs e)
{
//PageBody.Attributes["bgColor"] = System.Drawing.Color.FromName(DropDownList1.SelectedItem.Value).Name;
if (DropDownList1.SelectedItem.Text == "Ant and the Grasshopper")
{
PageBody.Attributes.Add("style", "background:url(images/ant-and-grasshopper.jpg);");
PageBody.Attributes.Add("body", "background-repeat: no-repeat");
Session["Background"] = "background:url(images/ant-and-grasshopper.jpg);";
ImageButton9.Visible = false;
ImageButton11.Visible = true;
}
if (DropDownList1.SelectedItem.Text == "Food Fit for the King")
{
PageBody.Attributes.Add("style", "background:no-repeat,background:url(images/King.jpg);");
Session["Background"] = "background:url(images/King.jpg);";
ImageButton11.Visible = false;
ImageButton9.Visible = true;
}
}
i tried the background-repeat: no repeat but to no avail . Thanks in advance.
"background:no-repeat,background:url(images/King.jpg);"
Ought to be
"background: url(images/King.jpg) no-repeat;"
Unless commas are treated specially, the comma between the two background attributes doesn't separate them. Only a semi-colon does that. Also, having two background attributes causes them to conflict.

PrintDocument using multiple page sizes

Working in .NET 3.5.
Summary:
Trying to replicate functionality of an existing third party component, which breaks in Windows 7.
Until now the user could select a bunch of image files to print, specify a page size for each image and then send them off to print all in one go. I am in dire need of a conceptual explanation of how to go about printing switching the page size on the fly when printing each page.
Details
So far I have figured out how to print multiple images all with the same page size. I use a list of images and use a PrintDocument object, setting the HasMorePages property of the PrintPageEventArgs to true until I reach the end of the list.
Here's a class I quickly threw together to test this:
public partial class Form1 : Form
{
private List<Image> images { get; set; }
private PrintDocument printDocument { get; set; }
public Form1()
{
InitializeComponent();
this.images = new List<Image>();
this.images.Add(Image.FromFile(#"C:\test60.bmp"));
this.images.Add(Image.FromFile(#"C:\SuperLargeTest.jpg"));
this.printDocument = new PrintDocument()
{
PrinterSettings = new PrinterSettings()
};
this.printDocument.PrintPage += printDocument_PrintPage;
}
private void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;
e.PageSettings.PaperSize = this.paperSizes[this.currentImageIndex];
RectangleF marginBounds = e.MarginBounds;
RectangleF printableArea = e.PageSettings.PrintableArea;
int availableWidth = (int)Math.Floor(printDocument.OriginAtMargins ? marginBounds.Width : (e.PageSettings.Landscape ? printableArea.Height : printableArea.Width));
int availableHeight = (int)Math.Floor(printDocument.OriginAtMargins ? marginBounds.Height : (e.PageSettings.Landscape ? printableArea.Width : printableArea.Height));
g.DrawRectangle(Pens.Red, 0, 0, availableWidth - 1, availableHeight - 1);
g.DrawImage(this.images[currentImageIndex], printableArea);
e.HasMorePages = ++currentImageIndex < this.images.Count();
}
private void button1_Click(object sender, EventArgs e)
{
this.printDocument.OriginAtMargins = false;
this.printDocument.Print();
}
}
The thing that I really can't figure out is how to go about changing the page size for, say, the second image.
If I wanted the first image to print in A4 and then the second one to print on A3, how would I go about doing that?
I found this SO question here which seemed to suggest changing the PageSize in the PrintPageEventArgs, but had no joy there.
I also tried to use the QueryPageSettingsEventArgs event and set the PageSettings there, but that didn't seem to work either...
What I would like to achieve is print multiple pages of different size as a single document. Any suggestions, links, explanations, sample code would be very much appreciated.
Anything in C# or VB.NET is fine.
That's work for me too.
Translated to C#:
private bool SetPaperSize(PrintDocument pd, PaperKind nKind)
{
foreach(System.Drawing.Printing.PaperSize ps in pd.PrinterSettings.PaperSizes)
{
if (ps.Kind == nKind)
{
pd.DefaultPageSettings.PaperSize = ps;
return true;
}
}
return false;
}
In VB.NET .. You can use this Sub ..
DocPrint is PrintDocument var
Sub SetPaperSize(ByVal nKind As PaperKind)
Dim ps As PaperSize
For ix As Integer = 0 To DocPrint.PrinterSettings.PaperSizes.Count - 1
If DocPrint.PrinterSettings.PaperSizes(ix).Kind = nKind Then
ps = DocPrint.PrinterSettings.PaperSizes(ix)
DocPrint.DefaultPageSettings.PaperSize = ps
End If
Next
End Sub
Hope this help ..
If you want all the pages to appear as one job (in short avoid being interleaved with other jobs), you can set the page size for the next page inside the PrintPage event handler by changing the default page size of the PrintDocument object.

Categories

Resources