Some years ago I found some example code to pretty print the content of a listview, repeating in each page a document header, footer, and table header.
Basically there is a class that implements DocumentPaginator and calculates the number of rows that fit in the page
Here is the code to launch the print:
PrintDialog printDialog = new PrintDialog();
printDialog.PrintQueue = LocalPrintServer.GetDefaultPrintQueue();
printDialog.PrintTicket = printDialog.PrintQueue.DefaultPrintTicket;
printDialog.PrintTicket.PageOrientation = PageOrientation.Landscape;
Size pageSize = new Size(printDialog.PrintableAreaWidth,
printDialog.PrintableAreaHeight);
DocumentPaginator paginator = new SimpleListDocumentPaginator(param as ListView,
documentTitle, pageSize, new Thickness(30, 20, 60, 20));
printDialog.PrintDocument(paginator, "Somename");
Here is the code in GetPage(int pageNumber) which adjust the grid dimension:
double width = this.PageSize.Width - (this.PageMargin.Left + this.PageMargin.Right);
double height = this.PageSize.Height - (this.PageMargin.Top + this.PageMargin.Bottom);
pageGrid.Measure(new Size(width, height));
pageGrid.Arrange(new Rect(this.PageMargin.Left, this.PageMargin.Top, width, height));
return new DocumentPage(pageGrid);
pageGrid is a grid created from the Listview, with same columns
In the past year with a different OS and a different .net framework it worked correctly, now the table is cut on the right at about 60% of the total width.
Page orientation is set to landscape and calculation are done with landscape values, but during printing it seems the document is considered 'Portrait' and cut at the limit.
The code for printing comes from an old project on Codeproject, Custom Data Grid Document Paginator.
As stated above I'm sure it worked well in the past changing the page orientation to Landscape, now it cuts the table at the page Portrait width.
DocumentPage has 2 constructors:
public DocumentPage (System.Windows.Media.Visual visual);
public DocumentPage (System.Windows.Media.Visual visual, System.Windows.Size pageSize, System.Windows.Rect bleedBox, System.Windows.Rect contentBox);
Using the second one and passing the correct pageSize it works correctly. The original source code uses the first one, the visual is measured and arranged to Landscape dimension, but DocumentPage uses Portrait dimensions. This is not the same behavior of 10 years ago, don't know when this change happened.
Related
I have the requirement to create 600 DPI "trifold" images, which have the dimensions of 25.5"x11" (three times the size of a Letter page). To do so, I’m using WPF Imaging through the DrawingVisual, DrawingContext, and RenderTargetBitmap classes.
When I generate the image in lower resolutions, 400 DPI or less for example, all of the text displays in the correct positions as expected. However, once I increase my image resolution to the 500 DPI level and beyond, certain text positioned in the far right of the image will simply disappear, while other relatively positioned text/shapes print perfectly. The craziest part about it is that as I try varying DPIs, different text will appear/disappear. In one test case, 600 DPI is missing one set of drawn FormattedTexts, 650 DPI is missing a different set of drawn FormattedTexts, and 700 DPI prints everything fine!
I’ve recreated the issue with the snippet of code below. Run as-is (600 DPI) and all you get is a very large white image. Change the Dpi constant to 400 or lower, and it prints the text just fine.
Note that I’ve tried turning many of the knobs within the DrawingVisual class (e.g. VisualBitmapScalingMode, VisualTextRenderingMode, VisualEdgeMode, etc.) to no avail. Most of my research on those settings found them as useful settings to correct “fuzzy” text, not disappearing text. I’ve also had no luck with any of the guideline/snap settings of the DrawingVisual or DrawingContext.
Note that I’ve recreated this issue on both Win7 and Win2008R2, and my application is running .NET 4.5.
Any ideas?
const double ImageWidthInches = 25.5;
const double ImageHeightInches = 11.0;
const double Dpi = 600.0;
const double DeviceIndependentUnits = 96.0;
const double TypographicUnits = 72.0;
var visual = new DrawingVisual();
var drawing = visual.RenderOpen();
drawing.DrawRectangle(
Brushes.White,
null,
new Rect(0,
0,
ImageWidthInches*DeviceIndependentUnits,
ImageHeightInches*DeviceIndependentUnits));
var formattedText = new FormattedText(
"Why doesn't this display?",
CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(new FontFamily("Arial Narrow"),
FontStyles.Normal,
FontWeights.Normal,
FontStretches.Normal),
8.0*DeviceIndependentUnits/TypographicUnits,
Brushes.Black);
drawing.DrawText(formattedText,
new Point(23.39*DeviceIndependentUnits,
2.6635416666666671*DeviceIndependentUnits));
drawing.Close();
var renderTarget = new RenderTargetBitmap(
(int) (ImageWidthInches*Dpi),
(int) (ImageHeightInches*Dpi),
Dpi,
Dpi,
PixelFormats.Default);
renderTarget.Render(visual);
var tiffEncoder = new TiffBitmapEncoder {Compression = TiffCompressOption.Ccitt4};
tiffEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
using (var fileStream = new FileStream(#"c:\recreateWpfTextIssue.tif", FileMode.Create, FileAccess.Write))
tiffEncoder.Save(fileStream);
The workaround to this bug is to round the font size to 2 decimal positions:
Math.Round(8.0*DeviceIndependentUnits/TypographicUnits, 2),
This and some extra information can be found in the matching MSDN post: http://social.msdn.microsoft.com/Forums/en-US/98717e53-89f7-4d5f-823b-7184781a7b85/wpf-formattedtext-randomly-disappears-in-high-resolution-images
Let me start by saying I have searched extensively on this and have found partial answers, but nothing that works all the way.
I need to display bitmap images in my WPF application that are not scaled. I want to map 1 pixel of the bitmap to 1 pixel of the display. I do intend to support multiple resolutions by shipping multiple versions of my bitmaps. But I want to know that, when a particular bitmap has been chosen, it will be rendered EXACTLY as it has been designed.
My strategy for overcoming the automatic scaling that happens in WPF is to look at what is being applied automatically (by virtue of the OS DPI setting), and then apply a LayoutTransform that is the inverse, to the outermost container of my window.
This ensures that, no matter what the user's DPI settings are, the app renders the contents of the window a 1:1 ratio of WPF pixels to hardware pixels. So far, so good.
That code looks like this. (Presume this is called with an argument of 1.0).
private void SetScale(double factor)
{
// First note the current window transform factor.
// This is the factor being applied to the entire window due to OS DPI settings.
Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
double currentWindowTransformFactorX = m.M11;
double currentWindowTransformFactorY = m.M22;
// Now calculate the inverse.
double currentWindowTransformInverseX = (1 / m.M11);
double currentWindowTransformInverseY = (1 / m.M22);
// This factor will put us "back to 1.0" in terms of a device-independent-pixel to physical pixel mapping.
// On top of this, we can apply our caller-specified factor.
double transformFactorX = currentWindowTransformInverseX * factor;
double transformFactorY = currentWindowTransformInverseY * factor;
// Apply the transform to the registered target container
ScaleTransform dpiTransform = new ScaleTransform(transformFactorX, transformFactorY);
if (dpiTransform.CanFreeze)
dpiTransform.Freeze();
this.pnlOutermost.LayoutTransform = dpiTransform;
}
Up to here, everything works great. No matter what I set my Windows DPI to, the contents of that main container are always exactly the same size, and the bitmaps are rendered precisely.
Now comes the fun part. I want to support different screen resolutions by providing resolution-specific artwork, and scaling my entire UI as appropriate.
It turns out that LayoutTransform works really well for this. So if I call the above method with 1.25 or 1.5 or whatever, the entire UI scales and everything looks perfect...except my images, which are back to looking stretched and crappy, even when I change the source to be an image that is exactly the right size for the new, scaled dimensions.
For example, suppose I have an image that is 100x100 in the XAML. My artwork comes in three flavors: 100x100, 125x125, and 150x150. When I scale the container that houses the image, I also change the source of that image to the appropriate one.
Interestingly, if the image object is sitting at a position that, when scaled by the factor, yields integral results, then the scaled image looks fine. That is to say, suppose the image has the following properties:
Canvas.Left = 12
Canvas.Top = 100
When we apply a factor of 1.25, this yields 15 and 125, and the image looks great. But if the image is moved by one pixel, to say:
Canvas.Left = 13
Canvas.Top = 100
Now when we apply a factor of 1.25, we get 15.25 and 125, and the result looks crappy.
Clearly, this looks like some kind of rounding issue or something like that. So I've tried:
UseLayoutRounding="True"
SnapsToDevicePixels="True"
RenderOptions.EdgeMode="Aliased"
RenderOptions.BitmapScalingMode="NearestNeighbor"
I've tried these in the window, in the container being scaled, and in the image object. And nothing works. And the BitmapScalingMode doesn't really make sense anyway, because the image should not be being scaled at all.
Eternal thanks to anyone who can shed some light on this.
I had the exact same problem so it looks like this has not been fixed in the framework as of 2019.
I managed to solve the issue using a three step approach.
Enable layout rounding on my top level UI element
<UserControl ... UseLayoutRounding="True">
Apply the inverse LayoutTransform to my Imageobjects (the LayoutTransformwas applied to the parent ListBox).
<Image ... LayoutTransform="{Binding Path=LayoutTransform.Inverse,
Mode=OneTime,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}}">
Subclass Imageand add a custom override for OnRender.
internal class CustomImage: Image {
private PresentationSource presentationSource;
public CustomImage() => Loaded += OnLoaded;
protected override void OnRender(DrawingContext dc) {
if (this.Source == null) {
return;
}
var offset = GetOffset();
dc.DrawImage(this.Source, new Rect(offset, this.RenderSize));
}
private Point GetOffset() {
var offset = new Point(0, 0);
var root = this.presentationSource?.RootVisual;
var compositionTarget = this.presentationSource?.CompositionTarget;
if (root == null || compositionTarget == null) {
return offset;
}
// Transform origin to device (pixel) coordinates.
offset = TransformToAncestor(root).Transform(offset);
offset = compositionTarget.TransformToDevice.Transform(offset);
// Round to nearest integer value.
offset.X = Math.Round(offset.X);
offset.Y = Math.Round(offset.Y);
// Transform back to local coordinate system.
offset = compositionTarget.TransformFromDevice.Transform(offset);
offset = root.TransformToDescendant(this).Transform(offset);
return offset;
}
private void OnLoaded(object sender, RoutedEventArgs e) {
this.presentationSource = PresentationSource.FromVisual(this);
InvalidateVisual();
}
}
}
The code from step 3 is based on this blogpost.
By using the CustomImage class in my XAML instead of Image and binding to a BitmapSource that will return a properly sized image based on the current scale factor, I managed to achieve great looking images without any unwanted scaling.
Note that you might need to call InvalidateVisual on your images when they need to be re-rendered.
I have a TreeView with small icons displayed in the data template. I'm trying to save the Treeview as a PNG using RenderTargetBitmap.
The image saves correctly on small data sets. However, if the data set becomes too large, some of the icons are excluded from the final image. The magic number seems to be 200 items. It doesn't seem to matter if the tree is deep or wide, after 200 items, the icons are not rendered.
Added Code
So here is my code that I'm using to create an image.
RenderTargetBitmap targetBitmap = new RenderTargetBitmap(
(int)_treeView.ActualWidth,
(int)_treeView.ActualHeight,
96, 96, PixelFormats.Default);
targetBitmap.Render(_treeView);
Added Screen Shot
Notice the missing icons way over on the right side of the tree.
Now if I collapse a few branches, thus hiding some of the other icons, then these icons are included. It's almost like RenderTargetBitmap.Render doesn't have the power to render all of the icons. Or it may have something to do with virtual panels.
Here is a closer look.
What I immediately noticed that you have HUGE image. Width 12000. I am surprised that you even got that close.
As MSDN states, the texture width/height are limited by DirectX texture limits.
The maximum rendered size of a XAML visual tree is restricted by the maximum dimensions of a Microsoft DirectX texture; for more info see Resource Limits (Direct3D). This limit can vary depending on the hardware whre the app runs. Very large content that exceeds this limit might be scaled to fit. If scaling limits are applied in this way, the rendered size after scaling can be queried using the PixelWidth and PixelHeight properties. For example, a 10000 by 10000 pixel XAML visual tree might be scaled to 4096 by 4096 pixels, an example of a particular limit as forced by the hardware where the app runs.
http://msdn.microsoft.com/library/windows/apps/dn298548
I suspect these things:
Virtualization cutting off some things - I've had the exact problem in past with DataGrid, and the problem was virtualization. Your case doesn't seem like one though.
Too big texture can cause undefined behaviour.
You can try disabling hardware acceleration. The thing causes quite few hardcore bugs. http://msdn.microsoft.com/en-us/library/system.windows.media.renderoptions.processrendermode.aspx
Other than that - it will be tricky, but I am pretty sure that it will work beautifully:
1) start with the root object, and traverse the root object childrens recursively, until you find an object that is less than 1000 x 1000. Take picture of it using RenderTargetBitmap(BMP) and merge it to IN-MEMORY-BMP. Do it for each children.
You should be able to calculate all this stuff.
For the records: there's a workaround.
Instead of rendering your Visual directly with RenderTargetBitmap, use an interim DrawingVisual. Paint your Visual into the DrawingVisual using a VisualBrush and then use RenderTargetBitmap with the DrawingVisual.
Like this:
public BitmapSource RenderVisualToBitmap(Visual visual)
{
var contentBounds = VisualTreeHelper.GetContentBounds(visual);
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var visualBrush = new VisualBrush(visual);
drawingContext.DrawRectangle(visualBrush, null, contentBounds);
}
var renderTargetBitmap = new RenderTargetBitmap((int)contentBounds.Width, (int)contentBounds.Height, 96, 96, PixelFormats.Default);
renderTargetBitmap.Render(drawingVisual);
return renderTargetBitmap;
}
Note however that as your VisualBrush gets bigger the resulting image gets more and more fuzzy (when rendering with high DPI). To work around this problem use a series of smaller VisualBrush "tiles" as described here:
https://srndolha.wordpress.com/2012/10/16/exported-drawingvisual-quality-when-using-visualbrush/
I'm trying to understand why my images are not snappy, so I built a sample to test WPF performance. I used a timer to calculate how long my "display images" event handler executed, and used a stop watch to measure how long it took the images to appear on the screen. The bottom line: when displaying 100, 1600, 2500 and 3600 images, WPF took 2, 9, 12 and 16 seconds after my code had finished to display the images on the screen. So I feel helpless: It seems I can't improve my code to make the images appear faster - I need to do something with WPF!
So my question is: What do I need to do differently to make the images display faster?
The test setup is simple:
The window contains a Grid. After the "test" button is clicked, row and column definitions are added.Then an Image is added to each cell of the grid as follows:
var image = new Image();
image.BeginInit();
image.Name = ImageNameFromCell(theRow, theColumn);
image.Stretch = Stretch.None;
image.HorizontalAlignment = HorizontalAlignment.Center;
image.VerticalAlignment = VerticalAlignment.Center;
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.LowQuality);
image.EndInit();
theGrid.Children.Add(image);
Finally, the Source of each image is set to a bitmap:a gray-scale image already scaled down to the estimated screen size. The bitmap is generated as follows:
var smallerBitmapImage = new BitmapImage();
smallerBitmapImage.BeginInit();
smallerBitmapImage.DecodePixelWidth = (int)(theImageWidth);
smallerBitmapImage.UriSource = theUri;
smallerBitmapImage.CacheOption = BitmapCacheOption.None;
smallerBitmapImage.EndInit();
//BitmapFrame bitmapFrame = BitmapFrame.Create(this.FullPath);
var convertedBitmap = new FormatConvertedBitmap();
convertedBitmap.BeginInit();
convertedBitmap.Source = smallerBitmapImage;
convertedBitmap.DestinationFormat = PixelFormats.Gray16;
convertedBitmap.EndInit();
convertedBitmap.Freeze();
So, I'm at my wits end. The images appear with a noticeable delay, and it seems to be out of my control. What can I do?
What appears to have made the difference is setting the image's cache option to OnLoad
smallerBitmapImage.CacheOption = BitmapCacheOption.OnLoad;
This moved the work to my event handler, so now I can use pre-fetching to do this at the background.
Do you actually see all those images at the same time? If not you can use some ItemsControl with a virtualizing panel so only images in view are displayed. (Speaking of panels, your current setup could also be replaced with an ItemsControl which uses a UniformGrid as panel)
You could also try to write a better decoder, which probably is a wasted effort.
I have a print preview that displays a captured panel on a form 'Panel1.DrawToBitmap(memoryImage, bounds);'
I also save the image to my hard drive - 'memoryImage.Save("diary.png")'
The image in the print preview at any zoom level is blurry, the saved image is perfect (viewed in windows photo viewer & PS).
Id like the print preview to be as good as the saved image, any ideas?
here's the code:-
private void CaptureScreen()
{
int x = splitContainerDiary.Location.X;
int y = splitContainerDiary.Location.Y;
int SCwidth = splitContainerDiary.Panel1.Width;
int SCheight = splitContainerDiary.Panel1.Height;
Rectangle bounds = new Rectangle(x, y, SCwidth, SCheight);
memoryImage = new Bitmap(SCwidth, SCheight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
splitContainerDiary.Panel1.DrawToBitmap(memoryImage, bounds);
memoryImage.Save("diary.png");
}
private void printDocumentDiary_PrintPage(object sender, PrintPageEventArgs e)
{
CaptureScreen();
Font HeaderFont = new Font("Consolas", 16, FontStyle.Bold);
e.Graphics.DrawString(selectedYear.ToString() + " - " + name, HeaderFont, Brushes.Black, 15, 15);
e.Graphics.DrawImage(Image.FromFile("diary.png"), 5, 5);
// e.Graphics.DrawImage(memoryImage, 0, 40);
PrintDoodle(e);
}
I have tried to draw the image from memory (e.Graphics.DrawImage(memoryImage, 0, 40) and also from the saved image 'e.Graphics.DrawImage(Image.FromFile("diary.png"), 5, 5);' They are both blurry in print preview.
I have tried different Pixel formats with no joy either.
I have tried saving the image as BMP, JPG, PNG with no joy either (when drawing image fromFile).
I have tried using BitBlt routine also with the same results.
Tino
This is an inevitable consequence of the dramatic difference between the device resolution of a printer vs a monitor. A printer typically can print with a resolution of 600 dots per inch. A monitor is typically set to 96 DPI. So when you print an image that's razor sharp on a monitor, each pixel of the image requires printing a blob of 6 x 6. Short from the blockiness this produces, anything that's drawn on screen with anti-aliasing will get those anti-aliasing pixels drawn 6 times larger as well. Completely ruining the effect. This is especially noticeable with any text that's drawn with ClearType anti-aliasing. The red and blue fringes become very noticeable on paper.
You can partly solve this by drawing the image one-to-one on the printer, ensuring that 1 pixel in the image becomes 1 pixel on paper. That ought to now look nice and sharp (minus the ClearType problem) but you'll be looking at a postage stamp. Growing your arms six times longer would have the same effect.
Well, this just doesn't work well. Use the PrintDocument class so you can draw stuff to the printer using its native resolution. Use the methods provided by e.Graphics in the PrintPage event handler. Avoid images unless they are photos, anything that doesn't have finely detailed line art will scale well.
I have encountered a similar "blurry font" problem, while trying to print out some custom text, which I've pre-arranged as Labels in the TableLayoutPanel.
My solution for the blurriness was as follows: I have created a panel and labels four times as big as the desired final size (by using font 44 instead of 11, and using width and height four times greater).
Then I've created a (large) bitmap, and downscaled it in the final step (DrawImage):
using (var bmp = new Bitmap(tableLayout.Width, tableLayout.Height))
{
tableLayout.DrawToBitmap(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height));
printPageEventArgs.Graphics.DrawImage(
bmp,
printPageEventArgs.MarginBounds.X,
printPageEventArgs.MarginBounds.Y,
bmp.Width / 4,
bmp.Height / 4);
}
The resulting text looks much sharper both in the preview and in the actual printed page.
Of course, such an approach can only work if you can manipulate the Control's size, for example by creating it "off screen". But it will not work if you require the actual displayed control to be printed.