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

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.

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?

C# Webview2 Margins doesn't work in print function

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

Tool tip content is not accessible using keyboard

I am unable to see the contents of the image tool tip when focussed on it using keyboard. Below is a code example:
<img id= "id1" title ="title1" tabindex ="0" src="/images/home.gif" alt="title1" />
I am able to see the tool tip contents when hovered using mouse.
Short Answer
Don't use the title attribute, it doesn't serve any purpose that wouldn't be better suited as a caption under an image or a <label> on an <input> (please don't use a title on an input....even for a search box, even 'placeholder' text is preferable as at least some screen readers will read that and your touch screen users will still be able to read what the input is for).
Medium Answer
The title attribute has low support, offers very little to people in the modern age of touch screens (it is only sighted mouse users who don't use magnifiers or other assistive tech who gain anything from a title attribute) and in general is not a good idea for most use cases.
As a general rule if you do want to use it (so mouse users can see what an image is about) then make sure it is the same as the alt attribute so that you are providing the same information / experience to screen reader users as non screen reader users.
Long Answer
We played with this for a while, there are some circumstances where the title attribute (or at least the effect of revealing extra information about an image) can be useful. In the end we 'rolled our own' version of the title attribute that allowed us to have additional information about a picture, without interrupting the flow of a document with a caption.
In the example below (a stripped back version of what we use) we have:-
Made it accessible via keyboard and on hover.
Provided useful information to those who need it, that has the added benefit of being accessible (as title tooltips don't follow minimum sizing guidelines in some browsers and don't scale even if you change the font size settings in your browser).
Made it work on touch devices.
Designed to function like a plugin, in that you produce standard markup and a little bit of JavaScript and CSS magic does the rest.
It still isn't as good as simply providing a caption under the image but I believe it captures the spirit of what the 'title' attribute on an image is designed for, while accounting for accessibility and technology changes from when it was introduced.
Please Note - the first 80 or so lines of JavaScript are just a small helper library that allows jQuery style syntax, the relevant part starts at $("img").each(function(){.
//tiny replacement for jQuery - adapted version of ki.js
!function (b, c, d, e, f) {
f = b['add' + e]
function i(a, d, i) {
for(d = (a && a.nodeType ? [a] : '' + a === a ? b.querySelectorAll(a) : c), i = d.length; i--; c.unshift.call(this, d[i]));
}
$ = function (a) {
return /^f/.test(typeof a) ? /in/.test(b.readyState) ? setTimeout(function() { $(a); }, 9) : a() : new i(a);
};
$[d] = i[d] = {
on: function (a, b) {
return this.each(function (c) {
f ? c['add' + e](a, b, false) : c.attachEvent('on' + a, b)
})
},
off: function (a, b) {
return this.each(function (c) {
f ? c['remove' + e](a, b) : c.detachEvent('on' + a, b)
})
},
each: function (a, b) {
for (var c = this, d = 0, e = c.length; d < e; ++d) {
a.call(b || c[d], c[d], d, c)
}
return c
},
splice: c.splice
}
}(document, [], 'prototype', 'EventListener');
$.each = function(arr, callback) {
if(toString.call(arr) === '[object Array]'){
var i = 0, l = arr.length;
for(; i < l; ++i) {
callback.call(arr[i], i, arr[i]);
}
} else {
for (i in arr)
callback.call(arr[i], i, arr[i]);
}
return arr;
};
//extended to include "attr"
$.prototype.attr = function(a, b) {
return b === []._ ? this[0].getAttribute(a) : this.each(function(c) {
c.setAttribute(a, b);
});
};
//extended to include "removeAttr"
$.prototype.removeAttr = function(a) {
return this.each(function(b) {
b.removeAttribute(a);
});
};
//extend to include "parent"
$.prototype.parent = function() {
return (this.length < 2) ? $(this[0].parentNode): [];
};
//custom function to wrap an element in another
$.prototype.wrap = function(a) {
return this.each(function(b) {
var c = document.createElement(a)
b.parentNode.insertBefore(c, b);
c.appendChild(b);
});
};
//quick way of exposing everything like 'addClass', 'removeClass' etc. without having to define each one indivdually
var props = ['add', 'remove', 'toggle', 'has'],
maps = ['add', 'remove', 'toggle', 'contains'];
props.forEach(function(prop, index) {
$.prototype[prop + 'Class'] = function(a) {
return this.each(function(b) {
if(a){
b.classList[maps[index]](a);
}
});
};
});
//extend to include "after"
$.prototype.after = function(a) {
return this.each(function(b) {
b.insertAdjacentHTML('afterend', a);
});
};
//Below is the actual function, all of the above is just a simple replacement for jQuery.
//Should work with just jQuery but you would have to check.
$("img").each(function(){
$(this).wrap("div"); //create a div around an image
var title = $(this).attr("title"); //grab the title
var wrapper = $(this).parent(); //grab the div we just created
wrapper.attr("data-title", title); //set the data-title that we use in the CSS on the wrapper
wrapper.addClass("image"); //add the class that we use for CSS
wrapper.attr("tabindex", "0"); //make the div focusable with tabindex="0"
$(this).after('<span class="visually-hidden">, Title ' + title + '</span>'); //add a span with the title in that is accessible to screen readers - note the use of a comma before the 'Title' part as this makes it more natural (as we are 'hacking' an experience similar to that of a screen reader reading an actual title.)
$(this).removeAttr('title'); //remove the actual title, otherwise some screen readers will announce the title twice.
});
.image{
display:block;
overflow:hidden;
}
/*need relative position in order to absolutely position the overlay*/
.image {
position:relative;
width:200px;
height:200px;
margin: 10px;
}
.image img {
width:100%;
vertical-align:top;
}
/*add a transition*/
.image:after,
.image:before {
position:absolute;
opacity:0;
transition: all 0.5s;
}
/*remove the transition for people who have reduced motion as a preference*/
#media (prefers-reduced-motion: reduce) {
.image:after,
.image:before {
transition: none;
}
}
/*create an overlay*/
.image:after {
content:'';
width:100%;
height:100%;
top:0;
left:0;
background:rgba(0,0,0,0.4);
}
/*create a box at the bottom that contains the 'data-title' text that was added to the div we created*/
.image:before {
content: attr(data-title);
font-size: 1.25rem;
line-height: 1.9rem;
width:100%;
color:#fff;
z-index:1;
bottom:0;
padding:4px 10px;
text-align:left;
background:black;
box-sizing:border-box;
-moz-box-sizing:border-box;
}
/*make the overlay visible on hover and focus*/
.image:hover::after,
.image:hover::before,
.image:focus::after,
.image:focus::before{
opacity:1;
}
/*put a border around on focus*/
.image:focus{
outline: 2px solid #333;
outline-offset: 4px;
}
/*visually hidden class used to make text screen reader accessible but not visible*/
.visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap; /* added line */
}
<img src="https://via.placeholder.com/150" title="First Image" alt="First Image"/>
<img src="https://via.placeholder.com/150" title="Second Image, A Longer Text Test for a more complex title, adjust to your needs" alt="Second Image"/>
According to the HTML 5.2 specification,
The title attribute represents advisory information for the element, such as would be appropriate for a tooltip.
The specification also adds the following warning:
Warning! Relying on the title attribute is currently discouraged as many user agents do not expose the attribute in an accessible manner as required by this specification (e.g., requiring a pointing device such as a mouse to cause a tooltip to appear, which excludes keyboard-only users and touch-only users, such as anyone with a modern phone or tablet).
In other words, one should not rely on the title attribute for exposing information to keyboard users, including screen reader users. This issue has been around for many years. Accessibility standards recommend(ed) the use of the title attribute only on the frame element (which is deprecated in HTML 5) the input element (if you don't use a label element for aesthetic reasons) and the abbr element. See also Steve Faulkner's blogpost Using the HTML title attribute – updated. (Even though the blogpost was last updated in 2013, the advice is essentially still valid.)
If you want the content of the title attribute to be exposed visually on keyboard focus, you'll need to rely on CSS, JavaScript or a combination of both. Note, however, that the img element is not keyboard focusable by default.

Infinite scrolling WP8 LongListSelector with Images

I have a LongListSelector that I am using to display a bunch of images as like thumbnails. At a certain point when scrolling (an infinite scrolling scenario) the images are downloading and the app slows down (i.e scrolling becomes slow on LongListSelector) after the images show up the app is back snappy again.
I am using xaml that looks like this:
<Image Stretch="UniformToFill"
Margin="-3,0,0,0"
Source="{Binding video_thumbnail}"
Opacity="1"
Height="200" Width="480" />
video_thumbnail is a string.
Should I be creating the image this way or is there a better way to optimize my code?
I had similar problem. I have solved it by implementing my own queue for downloading images. Just download one image at the time, so you will have just one thread for it.
You may think that images will be downloaded slower, but it's not true. Downloading 5 images one by one takes the same amount of time when downloading all 5 at the same time. I tested it in my own app.
Here's my example. First, I have created a class to store basic info about videos: url to a thumbnail and the thumbnail itself as ImageSource, so you can easily bind it to Source property of Image in XAML.
public class VideoItem
{
public string Url { get; private set; }
public ImageSource Thumbnail { get; set; }
public VideoItem(string url)
{
this.Url = url;
}
}
The next class will store all the videos in one observable list, so you can use it in binding. The list will grow as thumbnails will be downloaded, one by one.
public class VideoLibrary
{
private WebClient thumbnailDownloader = new WebClient();
private Queue<VideoItem> downloadQueue = new Queue<VideoItem>();
private bool isBusy = false;
public ObservableCollection<VideoItem> Videos = new ObservableCollection<VideoItem>();
public VideoLibrary()
{
thumbnailDownloader.OpenReadCompleted += OnThumbnailDownloaded;
}
// It will not start downloading process but only add a new video item (without thumbnail) to download queue.
public void Download(string url)
{
downloadQueue.Enqueue(new VideoItem(url)); // Just add to queue.
CheckQueue();
}
// Check whether there are new thumbnails to download. If so, start downloading just one.
private void CheckQueue()
{
if (isBusy) // Stop! Downloading in progress...
return;
if (downloadQueue.Count > 0)
{
isBusy = true;
VideoItem item = downloadQueue.Peek();
thumbnailDownloader.OpenReadAsync(new Uri(item.Url));
}
}
// One thumbnail has been downloaded. We can add it to the list of videos and check the queue again.
private void OnThumbnailDownloaded(object sender, OpenReadCompletedEventArgs e)
{
isBusy = false;
if (e.Cancelled)
{
CheckQueue(); // Just try again.
}
else if (e.Error != null)
{
downloadQueue.Dequeue(); // Skip this video (thumbnail), check the next one.
CheckQueue();
}
else if (downloadQueue.Count > 0)
{
VideoItem item = downloadQueue.Dequeue(); // Remove the video from queue.
WriteableBitmap bitmap = new WriteableBitmap(null);
bitmap.SetSource(e.Result);
item.Thumbnail = bitmap; // Thumbnail is ready to use.
Videos.Add(item); // Add to list.
CheckQueue();
}
}
}
According to this article:
Never bind server-hosted images directly to the control, because Silverlight runtime will use the UI thread (using WebClient) to fetch that image from the server, that can make the UI unresponsive for some time.
Use a background thread and HttpWebRequest class based implementation to download the image data in an efficient way which finally creates BitmapImage and sets that as the source. A clean MVVM wrapper around this would make your entire Image management pretty easy.
Also, go through Best Practices for Windows Phone development`
set BitMapCredtiOptions value to BackGroundCreation.This will allow it to use a cached image if one already exists for the UriSource.
If the image is a large size, such as a high resolution image captured by many phone cameras, then a considerable amount of CPU processing can be used up in decoding all the pixels of the image. If you know the size of the image frame you intend to render, you can use that information to set the pixel size to decode.
<Image ..>
<Image.Source>
<BitmapImage
UriSource="{Binding video_thumbnail}"
CreateOptions="BackgroundCreation"/>
</Image.Source>
</Image>

Saving a scrollable panel as PDF using .NET

I have a Panel filled with a lot of controls for users to fill. These include textboxes, checkboxes, radiobuttons etc. It is a long form to fill so the controls are in a scrollable panel. What I need is to save the whole panel as pdf. I think PDFsharp is a good library to be able to save any text or image as a pdf file but I don't want to write code for every single control inside the panel. I once wrote a class to create a pdf file from a Control object. It was iterating all inner controls (and their inner controls until no inner control is left) of the given control and write their Text property (yes/no for chekable controls) to pdf using their Location and Size properties. I could not find it now but I remember it was having issues with some of the DevExpress controls I use so I didn't bother writing it again. (Edit: I had to, you can find it below.) I think taking a screenshot and save that image as pdf would be nice but I couldn't find out how to achieve it. This question seems like it but there is no satisfying answer to that.
So, screenshot or not I'm open for any advice. There should be many occasions where users must fill long forms and be able to keep it as pdf. Again, any advice or workaround would be appreciated. (I think about creating the form using html, displaying it in a WebBrowser control and using an html to pdf library but I really prefer using my existent form)
Many Thanks.
Edit:
I had to write something iterates inner controls of a container control (like a panel) and writes every inner control to a pdf using their Location, Size and Font properties though, I don't recommend to use it (at least as it is) because of these:
It sets the page's size to given control's size and use only one (usually huge) pdf page. You can add a logic to split it to pages if you need to. (I didn't, but I guess you'll probably need your pdf more printer friendly).
Cheeso's method (using a FlowDocument) is a much more "legitimate" way for a task like this. I prefer using that over this but I didn't have a choice in this instance.
I used PDFsharp in this code. You can find it in it's hompage or it's CodePlex page.
PdfReport class:
private PdfDocument Document;
public Control Control { get; private set; }
public PdfReport(Control control) { Control = control; }
public PdfDocument CreatePdf(PdfDocument document = null)
{
Document = document != null ? document : new PdfDocument();
PdfPage page = Document.AddPage();
page.Height = Control.Height;
page.Width = Control.Width;
XGraphics gfx = XGraphics.FromPdfPage(page);
foreach (PdfItem item in CreatePdf(new Point(0, 0), Control.Controls))
{
XStringFormat format = item.IsContainer ? XStringFormats.TopLeft : item.TextAlign == ContentAlignment.BottomCenter ? XStringFormats.BottomCenter : item.TextAlign == ContentAlignment.TopLeft ? XStringFormats.TopLeft : item.TextAlign == ContentAlignment.TopCenter ? XStringFormats.TopCenter : XStringFormats.Center;
gfx.DrawString(item.Text, item.Font, item.Brush, new XRect(item.Location, item.Size), format);
}
return Document;
}
private IEnumerable<PdfItem> CreatePdf(Point location, Control.ControlCollection controls)
{
List<PdfItem> items = new List<PdfItem>();
foreach (Control control in controls)
{
if (control.Controls.Count > 0)
items.AddRange(CreatePdf(control.Location, control.Controls));
items.Add(new PdfItem(control, location));
}
return items;
}
public void SaveAsPdf(string path, bool open = false)
{
CreatePdf().Save(path);
if (open)
Process.Start(path);
}
PdfItem class:
public string Text { get; set; }
public Point Location { get; set; }
public Size Size { get; set; }
public Font Font { get; set; }
public bool IsContainer { get; set; }
public ContentAlignment TextAlign { get; set; }
public Color ForeColor { get; set; }
public XBrush Brush { get { return new SolidBrush(ForeColor); } }
public PdfItem() { }
public PdfItem(string text, Point location, Font font, Color foreColor, Size size, bool isContainer = false, ContentAlignment alignment = ContentAlignment.MiddleCenter)
{
Text = text;
Location = location;
Size = size;
Font = new Font(font.FontFamily, font.Size, font.Style, GraphicsUnit.World);
TextAlign = alignment;
ForeColor = foreColor;
IsContainer = isContainer;
}
public PdfItem(string text, Point location, Size size)
: this(text, location, new Font("Calibri", 12), Color.Black, size) { }
public PdfItem(Control control, Point parentLocation)
: this(control.Text, control.Location, control.Font, control.ForeColor, control.Size, control.Controls.Count > 0)
{
Location = new Point(Location.X + parentLocation.X, Location.Y + parentLocation.Y);
IEnumerable<PropertyInfo> properties = control.GetType().GetProperties();
if (properties.FirstOrDefault(p => p.Name == "TextAlign" && p.PropertyType == typeof(ContentAlignment)) != null)
TextAlign = (control as dynamic).TextAlign;
if (properties.FirstOrDefault(p => p.Name == "Checked" && p.PropertyType == typeof(bool)) != null)
{
string title = control.Text != null && control.Text.Length > 0 ? string.Format("{0}: ", control.Text) : string.Empty;
Text = string.Format("{0}{1}", title, (control as dynamic).Checked ? "Yes" : "No");
}
}
Regarding
. I think taking a screenshot and save that image as pdf would be nice but I couldn't find out how to achieve it.
There is a tool called "cropper" available on codeplex.com. It is designed to be used as a user tool that can take screenshots. It is managed code, open source.
I can imagine embedding some of the cropper magic into your app so that you could take that screenshot. I can also imagine this would be useful for collecting a diagnostic image of the screen at the time of a problem.
On the other hand... if you are interested in producing a printed form that reproduces the content on the screen, then I think you should be using WPF, in which case doing what you want is pretty easy. For example, this question describes how to do a print-preview for a FlowDocument. From that point your user can print to PDF (if he has a PDF printer installed) or print to XPS, or print to a physical output device, and so on.
I don't know if this would help you or not, but DocRaptor.com's pdf api could be built in so it would do it for you, no matter what the user inputs. It uses basic html.
As you can use the below :)
YourPanel.AutoSize = true;
int width = YourPanel.Size.Width;
int height = YourPanel.Size.Height;
Bitmap bm = new Bitmap(width, height);
YourPanel.DrawToBitmap(bm, new Rectangle(0, 0, width, height));
string outputFileName = #"C:\YourDirectory/myimage.bmp";
using (MemoryStream memory = new MemoryStream())
{
using (FileStream fs = new FileStream(outputFileName, FileMode.Create, FileAccess.ReadWrite))
{
bm.Save(memory, ImageFormat.Bmp);
Clipboard.SetImage(bm);
byte[] bytes = memory.ToArray();
fs.Write(bytes, 0, bytes.Length);
}
}
YourPanel.AutoSize = false;
The Clipboard.SetImage will send you bm to the clipboard so you can paste them to your pdf form or whatever document
This also has an example built in that saves it as a image for you if you want.
The trick here is Autosize for your panel. It needs to be set to true so the panel resizes itself as a whole area visible, then right after you do your work you can resize it to false so it uses scrollbars again for the users screen (you may see it flash for half a second, but this code does work.
Saving it in a PDF I personally just prefer to write it there as my clipboard or you can write byte. But ITextSharp is a great library for the extension to work with!
I Really hope this helps.

Categories

Resources