WPF print window without displaying it - c#

I have several WPF windows with controls. I would like to print them one after the other without displaying them. Is there a way of doing that?
This is the printing method:
public void printItemList(string printerName)
{
printButton.Visibility = Visibility.Collapsed;
cancelButton.Visibility = Visibility.Collapsed;
this.Height = 1250;
this.Width = 815;
PrintDialog printDlg = new System.Windows.Controls.PrintDialog();
printDlg.PrintQueue = new System.Printing.PrintQueue(new System.Printing.PrintServer(), printerName);
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(this.ActualWidth, this.ActualHeight); //(8.5 * 96.0, 11.0 * 96.0);
//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, String.Empty);
}

Moving out the window was the key, thanks for the tip!
ItemListWindow il = new ItemListWindow();
il.Show();
var desktopWorkingArea = System.Windows.SystemParameters.WorkArea;
il.Top = desktopWorkingArea.Bottom + 100;
il.printItemList(printerComboBox.SelectedItem.ToString());
il.Close();

Related

Capture/Snapshot current WPF content window and sent it to default printer in C# VS 2008

I have a MVVM WPF application.
I am trying to capture current content of the window without the Window Form Header and Window Form borders.
I know this is easy to do in a Windows Forms application, here is an example.
I would like to do the same in my WPF application but I am lost. I have found an example here but it captures all the screen.
How can I do this? Also, once captured I need to send it directly to print to the default printer.
So I am trying to do the following without success:
Capture WPF window content without the Window Form Header and Window Form borders.
Print this screen capture to default printer without showing any print dialog.
ATTEMPT #1 - ISSUE 1 - Capture screen:
Regarding point 1 (capturing content of window) I have done below. SnapShotPNG solution is working perfectly.
TopGrid UI Element in GetSnapshotButton_Click is the grid from which I want to do snapshot its content.
GetJpgImage solution is not working at all. In the snapshot appears black zones. Why?
Solutions extracted from here and here.
private void GetSnapshotButton_Click(object sender, RoutedEventArgs e)
{
var uri = new System.Uri("c:\\Temp\\capture1.png");
SnapShotPNG(TopGrid, uri, 1);
uri = new System.Uri("c:\\Temp\\capture2.jpg");
GetJpgImage(TopGrid, uri, 1, 100);
}
public void SnapShotPNG(UIElement source, Uri destination, int scale)
{
try
{
double actualHeight = source.RenderSize.Height;
double actualWidth = source.RenderSize.Width;
double renderHeight = actualHeight * scale;
double renderWidth = actualWidth * scale;
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)renderWidth, (int)renderHeight, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(source);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.PushTransform(new ScaleTransform(scale, scale));
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
renderTarget.Render(drawingVisual);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream stream = new FileStream(destination.LocalPath, FileMode.Create, FileAccess.Write))
{
encoder.Save(stream);
}
}
catch (Exception e)
{
//MessageBox.Show(e);
}
}
///
/// Gets a JPG "screenshot" of the current UIElement
///
/// UIElement to screenshot
/// Scale to render the screenshot
/// JPG Quality
/// Byte array of JPG data
public void GetJpgImage(UIElement source, Uri destination, double scale, int quality)
{
double actualHeight = source.RenderSize.Height;
double actualWidth = source.RenderSize.Width;
double renderHeight = actualHeight * scale;
double renderWidth = actualWidth * scale;
RenderTargetBitmap renderTarget = new RenderTargetBitmap((int)renderWidth, (int)renderHeight, 96, 96, PixelFormats.Pbgra32);
VisualBrush sourceBrush = new VisualBrush(source);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.PushTransform(new ScaleTransform(scale, scale));
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
}
renderTarget.Render(drawingVisual);
JpegBitmapEncoder jpgEncoder = new JpegBitmapEncoder();
jpgEncoder.QualityLevel = quality;
jpgEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (FileStream stream = new FileStream(destination.LocalPath, FileMode.Create, FileAccess.Write))
{
jpgEncoder.Save(stream);
}
}
ATTEMPT #2 - ISSUE 1 - Capture screen:
Using Dymanoid solution works but with one problem: Content does not fit in one page. I am trying now to fit into one page. To fit content into one page I am trying to do what is explained here.
for printing grid cotent (see here):
PrintDialog printDlg = new PrintDialog();
printDlg.PrintVisual(TopGrid, "Grid Printing.");
for printing entire wpf window (see here):
PrintDialog printDlg = new PrintDialog();
printDlg.PrintVisual(this, "Window Printing.");

WPF create one Window for each screen, and center them on each screen

EDIT: I solved it. See my fix in the answer below.
I'm working on an application that should open a small popup window on each screen connected to the computer it runs on. Simple enough to do on a single screen (using WindowStartupLocation = CenterScreen), but surprisingly difficult to do on multiple screens.
My current code is this:
foreach (var s in Screen.AllScreens) //System.Windows.Forms.Screen
{
var b = s.Bounds;
var w = new PopupWindow();
var oW = w.Width; //Keep track of original size ...
var oH = w.Height;
w.Width = 0; //then set the size to 0, to avoid that the
w.Height = 0;//popup shows before it is correctly positioned
w.Show(); //Now show it, so that we can place it (when I
//tried to place it before showing, the window
//was always repositioned when Show() was called)
double dpiX = 1, dpiY = 1;
var presentationsource = PresentationSource.FromVisual(w);
if (presentationsource != null)
{
dpiX = presentationsource.CompositionTarget.TransformToDevice.M11;
dpiY = presentationsource.CompositionTarget.TransformToDevice.M22;
}
var aW = oW*dpiX; //Calculate the actual size of the window
var aH = oH*dpiY;
//***** THIS IS WRONG, SEE ANSWER *****
w.Left = (b.X + (b.Width / dpiX - aW) / 2); //Place it
w.Top = (b.Y + (b.Height / dpiY - aH) / 2);
//*************************************
w.Width = oW; //And set the size back to the original size
w.Height = oH;
}
This seems to work only on the primary screen. On the other screens, the windows are not properly centered.
I guess this is because my knowledge of WPF and DPI is very limited, and I'm probably doing something wrong. Could somebody point me in the right direction?
Of course, I managed to solve it after posting it here. Looks like I did something else wrong when I tried to divide the entire location with the DPI, which led me onto the wrong path I posted above.
The correct lines for placing the form should be this (all the other code works):
w.Left = (b.X + (b.Width - aW) / 2) / dpiX;
w.Top = (b.Y + (b.Height - aH) / 2) / dpiX;
But, I still think this is a lot of code for a simple task, so if somebody has better ideas, please let me know!
So this is the (working) code I'm using now:
foreach (var s in Screen.AllScreens) //System.Windows.Forms.Screen
{
var b = s.Bounds;
var w = new PopupWindow();
var oW = w.Width; //Keep track of original size ...
var oH = w.Height;
w.Width = 0; //then set the size to 0, to avoid that the
w.Height = 0;//popup shows before it is correctly positioned
w.Show(); //Now show it, so that we can place it (when I
//tried to place it before showing, the window
//was always repositioned when Show() was called)
double dpiX = 1, dpiY = 1;
var ps = PresentationSource.FromVisual(w);
if (ps != null)
{
dpiX = ps.CompositionTarget.TransformToDevice.M11;
dpiY = ps.CompositionTarget.TransformToDevice.M22;
}
var aW = oW*dpiX; //Calculate the actual size of the window
var aH = oH*dpiY;
w.Left = (b.X + (b.Width - aW) / 2) / dpiX;
w.Top = (b.Y + (b.Height - aH) / 2) / dpiX;
w.Width = oW; //And set the size back to the original size
w.Height = oH;
}
var centers =
System.Windows.Forms.Screen.AllScreens.Select(
s =>
new
{
Left = s.Bounds.X + (s.WorkingArea.Right - s.WorkingArea.Left)/2,
Top = s.Bounds.Y + (s.WorkingArea.Bottom - s.WorkingArea.Top)/2
});
foreach (var c in centers)
{
var w = new Window1();
w.Left = c.Left - w.Width/2;
w.Top = c.Top - w.Height/2;
w.Show();
}

How to calculate FixedPage dimensions

This snippet is part of some code used to generate an XPS document. XPS document generation is no joke. I wish to avoid pasting any of that XPS code here if at all possible. Instead, this code focuses on the WPF portion of the problem.
The problem I am asking for you to help with is here. I have hard coded the dimensions to work for a test image:
double magicNumber_X = 3.5;//trial and error...3 too small 4 too big
fixedPage.Arrange(new Rect(new Point(magicNumber_X, 0), size));
Instead, how can I fix this code to calculate the coordinates?
Full Method:
private PageContent AddContentFromImage()
{
var pageContent = new PageContent();
var fixedPage = new FixedPage();
var bitmapImage = new BitmapImage(new Uri(hardCodedImageSampleFilePath, UriKind.RelativeOrAbsolute));
var image = new Image();
image.Source = bitmapImage;
fixedPage.Children.Add(image);
((IAddChild)pageContent).AddChild(fixedPage);
double pageWidth = 96 * 8.5;//XPS documents are 96 units per inch
double pageHeight = 96 * 11;
fixedPage.Width = pageWidth;
fixedPage.Height = pageHeight;
var size = new Size(8.5 * 96, 11 * 96);
fixedPage.Measure(size);
double magicNumber_X = 3.5;//trial and error...3 too small 4 too big
double magicNumber_Y = 0;
fixedPage.Arrange(new Rect(new Point(magicNumber_X, magicNumber_Y), size));
fixedPage.UpdateLayout();
return pageContent;
}
I'm a little surprised FixedPage.Measure(size) does not correct the issue by itself. I tried passing no params, e.g. fixedPage.Arrange(new Rect(), size)) still no go.
FWIW, this calculation worked fine when I was using PrintDocument.
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;
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));
Rectangle rectangle = new Rectangle(0,0, availableWidth -1, availableHeight - 1);
g.DrawImage(_image, rectangle);
I hooked into FixedPage.Loaded event because FixedPage.ActualHeight is required in order to perform the calculation and will not be set until the control has loaded. This also means that with this mechanism FixedPage has to be displayed to correctly perform an automated print.
void fixedPage_Loaded(object sender, RoutedEventArgs e)
{
var fixedDocument = sender as FixedPage;
CalculateSize(fixedDocument);
}
private void CalculateSize(FixedPage fixedPage)
{
PrintQueue printQueue = LocalPrintServer.GetDefaultPrintQueue();
PrintCapabilities capabilities = printQueue.GetPrintCapabilities();
//get scale of the print wrt to screen of WPF visual
double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / fixedPage.ActualWidth, capabilities.PageImageableArea.ExtentHeight / fixedPage.ActualHeight);
//Transform the Visual to scale
fixedPage.LayoutTransform = new ScaleTransform(scale, scale);
//get the size of the printer page
var sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
//update the layout of the visual to the printer page size.
fixedPage.Measure(sz);
double x = capabilities.PageImageableArea.OriginWidth;
double y = capabilities.PageImageableArea.OriginHeight;
fixedPage.Arrange(new Rect(new Point(x, y), sz));
fixedPage.UpdateLayout();
}

Keeping a PictureBox centered inside a container

I am designing a simple picture viewer with ability to do some basic image processing. At the moment I have the problem of keeping the PictureBox centered inside a TabPage all the time as well as keeping the picturebox width and size same as the picture its showing. So far I had no success.
I have the following code that I call in form constructor to position it in center. it works the first time to center the picturebox:
private void SetPictureBoxOriginalSizeAndLocation(bool makeImageNull = false, DockStyle dockStyle = DockStyle.None)
{
if (makeImageNull) picBoxView.Image = null;
picBoxView.Dock = dockStyle;
var xPoint = tabImageView.Location.X + ((splitContainer.Panel2.Width / 2) / 2);
var yPoint = tabImageView.Location.Y;
var width = tabImageView.Width / 2;
var height = (tabImageView.Height / 2) - toolStripImageView.Height;
if (picBoxView.Image == null) return;
//Resize image according to width
picBoxView.Image = ImageMethods.ResizeImage(picBoxView.Image.Tag.ToString(), width, height, false);
picBoxView.Location = new Point(xPoint, yPoint);
picBoxView.Width = width;
picBoxView.Height = height;
}
But it does not resize the picturebox to its image (you can see the black part that is back color for the picturebox control):
The problem is getting worse as soon as I resize the form, the picturebox position will goes to top:
I call the code above in form's resize event as well, no idea why it works when application starts. Would be nice if someone can tell me what properties I should take care of to achieve a nicely centered picturebox which always is as big as its image.
It's pretty easy if you just set the Anchor style to none:
picBoxView = new PictureBox();
picBoxView.SizeMode = PictureBoxSizeMode.AutoSize;
picBoxView.Anchor = AnchorStyles.None;
tabImageView.Controls.Add(picBoxView);
CenterPictureBox(picBoxView, myImage);
Then just center the PictureBox initially whenever you change the image of the PictureBox:
private void CenterPictureBox(PictureBox picBox, Bitmap picImage) {
picBox.Image = picImage;
picBox.Location = new Point((picBox.Parent.ClientSize.Width / 2) - (picImage.Width / 2),
(picBox.Parent.ClientSize.Height / 2) - (picImage.Height / 2));
picBox.Refresh();
}
Having the Anchor = None will center the PictureBox control for you whenever the parent container gets resized because it "isn't" anchored to the default Left and Top locations.
Givien a Form with TabControl, which has Dock set to Fill, below will keep your PictureBox in the centre. It also sets PictureBox size to Bitmap size:
public partial class Form1 : Form
{
Bitmap b = new Bitmap(320, 200);
public Form1()
{
InitializeComponent();
CenterTheBox();
}
private void Form1_Resize(object sender, EventArgs e)
{
CenterTheBox();
}
void CenterTheBox()
{
pictureBox1.Size = b.Size;
var left = (tabPage1.ClientRectangle.Width - pictureBox1.ClientRectangle.Width) / 2;
var top = (tabPage1.ClientRectangle.Height - pictureBox1.ClientRectangle.Height) / 2;
pictureBox1.Location = new Point(tabPage1.ClientRectangle.Location.X + left, tabPage1.ClientRectangle.Location.Y + top);
}
}
I believe your problem lies here
var xPoint = tabImageView.Location.X + ((splitContainer.Panel2.Width / 2) / 2);
var yPoint = tabImageView.Location.Y;
var width = tabImageView.Width / 2;
var height = (tabImageView.Height / 2) - toolStripImageView.Height;
ypoint is alwways set to tabImageView Y, althought it should be set to
tabImageView.Location.Y + (tabImageView.Size.Height - picBoxView.Size.Height)/2
should be almost the same with xPoint
tabImageView.Location.X + (tabImageView.Size.Width - picBoxView.Size.Width)/2
and
width = picBoxView.Image.Width;
height = picBoxView.Image.Height;

Saving an image of a UIElement rendered larger than it appears on screen

I have a chart that I'm displaying to the user and I want to be able to export the chart as an image to disk so they can use it outside of the application (for a presentation or something).
I've managed to get the basic idea working using PngBitmapEncoder and RenderTargetBitmap but the image I get out of it is way to small to really be usable and I want to get a much larger image.
I tried to simply increase the Height and Width of the control I was wanting to render, but the parent seems to have direct control on the render size. From this I tried to duplicate the UIElement in memory but then the render size was (0,0) and when I tried to use methods to get it to render, such as Measure() Arrange() and UpdateLayout() they throw exceptions about needing to decouple the parent to call these, but as it's in memory and not rendered there shouldn't be a parent?
This is all done with Visiblox charting API
Here is what I've got currently, except it doesn't work :(
var width = 1600;
var height = 1200;
var newChart = new Chart { Width = width, Height = height, Title = chart.Title, XAxis = chart.XAxis, YAxis = chart.YAxis, Series = chart.Series};
Debug.WriteLine(newChart.RenderSize);
var size = new Size(width, height);
newChart.Measure(size);
newChart.Arrange(new Rect(size));
newChart.UpdateLayout();
Debug.WriteLine(newChart.RenderSize);
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(newChart);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
using (var stream = fileDialog.OpenFile())
encoder.Save(stream);
I've gotten a bit closer, it now render the graph background the axis' etc. but just not the actual lines that are being graphed. Below is an updated source
public static void RenderChartToImage(Chart elementToRender, string filename)
{
if (elementToRender == null)
return;
Debug.Write(elementToRender.RenderSize);
var clone = new Chart();
clone.Width = clone.Height = double.NaN;
clone.HorizontalAlignment = HorizontalAlignment.Stretch;
clone.VerticalAlignment = VerticalAlignment.Stretch;
clone.Margin = new Thickness();
clone.Title = elementToRender.Title;
clone.XAxis = new DateTimeAxis();
clone.YAxis = new LinearAxis() { Range = Range<double>)elementToRender.YAxis.Range};
foreach (var series in elementToRender.Series)
{
var lineSeries = new LineSeries
{
LineStroke = (series as LineSeries).LineStroke,
DataSeries = series.DataSeries
};
clone.Series.Add(lineSeries);
}
var size = new Size(1600, 1200);
clone.Measure(size);
clone.Arrange(new Rect(size));
clone.UpdateLayout();
Debug.Write(clone.RenderSize);
var height = (int)clone.ActualHeight;
var width = (int)clone.ActualWidth;
var renderer = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Pbgra32);
renderer.Render(clone);
var pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(renderer));
using (var file = File.Create(filename))
{
pngEncoder.Save(file);
}
}
So I get out something like this:
Which while big, isn't useful as it has nothing charted.
http://www.visiblox.com/blog/2011/05/printing-visiblox-charts
The main point I was missing was
InvalidationHandler.ForceImmediateInvalidate = true;
Setting this before I rendered the chart in memory and then reverting it once I had finished. From there it was smooth sailing :D
RenderTargetBitmap DrawToImage<T>(T source, double scale) where T:FrameworkElement
{
var clone = Clone(source);
clone.Width = clone.Height = Double.NaN;
clone.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
clone.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
clone.Margin = new Thickness();
var size = new Size(source.ActualWidth * scale, source.ActualHeight * scale);
clone.Measure(size);
clone.Arrange(new Rect(size));
var renderBitmap = new RenderTargetBitmap((int)clone.ActualWidth, (int)clone.ActualHeight, 96, 96, PixelFormats.Pbgra32);
renderBitmap.Render(clone);
return renderBitmap;
}
static T Clone<T>(T source) where T:UIElement
{
if (source == null)
return null;
string xaml = XamlWriter.Save(source);
var reader = new StringReader(xaml);
var xmlReader = XmlTextReader.Create(reader, new XmlReaderSettings());
return (T)XamlReader.Load(xmlReader);
}
I think these might help :
Exporting Visifire Silverlight Chart as Image with a downloadable silverlight solution.
Exporting Chart as Image in WPF

Categories