Building a smart DocumentPaginator that is able to cut Compex UIElement in Pages for printing
I am currently working with DocumentPaginator which allows me to do:
PrintDialog printDialog = new PrintDialog();
if(printDialog.ShowDialog()== true)
{
var cp = new ControlPaginator(myUIElement, new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight));
printDialog.PrintDocument(cp,"Test");
}
because every time you want to print a visual code that takes multiple pages is a pain in the ass. So I decided to create a smart control that will do it for me 90% of the time.
but it seems that I am not smart enough ...
so here is my code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace WpfUIPrinter
{
public class ControlPaginator : DocumentPaginator
{
private List<ControlDocumentPage> Pages { get; set; }
private Rect PageRect;
private const double Pagebegin = 0;
#region cTor
public ControlPaginator(UIElement VisualElement, Size PageSize)
{
if (VisualElement == null)
throw new ArgumentNullException("Das UIElement VisualElement darf nicht Null sein");
if (PageSize == null)
throw new ArgumentNullException("Die Size PageSize darf nicht Null sein ");
if (PageSize == null || PageSize.Width < 1 || PageSize.Height < 1)
throw new ArgumentException("Die Size PageSize muss mindestens eine Width > 0 und Height > 0");
this.Pages = new List<ControlDocumentPage>();
this.PageSize = PageSize;
this.VisualElement = VisualElement;
this.PageRect = new Rect(Pagebegin, Pagebegin, Math.Floor(PageSize.Width), Math.Floor(PageSize.Height));
createPages();
}
#region Pages
/// <summary>
///
/// </summary>
private void createPages()
{
// if the print visual is not displayed we have to measure and arrange it
// if it is displayed only the parts that are visible will be rendered for print
VisualElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
VisualElement.Arrange(new Rect(new Point(0, 0), VisualElement.DesiredSize));
var rightPageBegin = Pagebegin;
var bottomPageBegin = Pagebegin;
while ((double)VisualElement.GetValue(FrameworkElement.ActualHeightProperty) > bottomPageBegin)
{
while ((double)VisualElement.GetValue(FrameworkElement.ActualWidthProperty) > rightPageBegin)
{
// just for debugging
Console.WriteLine(rightPageBegin + "|" + bottomPageBegin);
// reposition of the Printable location
this.PageRect.Location = new Point(rightPageBegin, bottomPageBegin);
this.Pages.Add(createPage());
rightPageBegin = Pages.Count == 0 ? 0 : Pages.LastOrDefault().RightPageBegin.Value;
}
var sl = Pages.Where(page => page.BottomPageBegin > bottomPageBegin).ToList();
bottomPageBegin = sl.Count == 0 ? 0 : sl.Min(page => page.BottomPageBegin).Value >= bottomPageBegin ? bottomPageBegin + PageRect.Height : sl.Min(page => page.BottomPageBegin).Value;
rightPageBegin = 0;
}
}
#endregion Pages
private ControlDocumentPage createPage()
{
////clonen des VisualElement
var VisualtoPrint = VisualElement.Clone();
// if the print visual is not displayed we have to measure and arrange it
// if it is displayed only the parts that are visible will be rendered for print
var w = (double)VisualElement.GetValue(FrameworkElement.ActualWidthProperty);
var h = (double)VisualElement.GetValue(FrameworkElement.ActualHeightProperty);
VisualtoPrint.Measure(new Size(w, h));
VisualtoPrint.Arrange(new Rect(new Point(PageRect.Left * -1, PageRect.Top * -1), VisualElement.DesiredSize));
VisualtoPrint.UpdateLayout();
var list = ((DependencyObject)VisualtoPrint).getChilds().ToList();
ForeachChildIn(VisualtoPrint, list);
double RightPageBegin;
double BottomPageBegin;
FindPageBeginns(VisualtoPrint, out RightPageBegin, out BottomPageBegin);
LookupDependencyObject.Clear();
return new ControlDocumentPage(VisualtoPrint, RightPageBegin, BottomPageBegin);
}
/// <summary>
/// Tryed to find the beginn of the following pages
/// </summary>
/// <param name="VisualtoPrint"></param>
/// <param name="RightPageBegin"></param>
/// <param name="BottomPageBegin"></param>
private void FindPageBeginns(UIElement VisualtoPrint, out double RightPageBegin, out double BottomPageBegin)
{
RightPageBegin = PageRect.Right;
BottomPageBegin = PageRect.Bottom;
bool gotright = false, gotbottom = false;
//tryes to find the begin of the next page on the right
foreach (var item in LookupDependencyObject.Where(kv => kv.Width <= PageRect.Width && kv.Start == Position.Center && kv.End == Position.MidRight).ToList())
{
if (PageRect.Left < item.StartPoint.X && RightPageBegin > item.StartPoint.X)
{
RightPageBegin = item.StartPoint.X;
gotright = true;
}
}
// tryes to fin the begin of the next page on the bottom
foreach (var item in LookupDependencyObject.Where(kv => kv.Height <= PageRect.Height && kv.Start == Position.Center && kv.End == Position.BottomMid).ToList())
{
if (PageRect.Top < item.StartPoint.Y && BottomPageBegin > item.StartPoint.Y)
{
BottomPageBegin = item.StartPoint.Y;
gotbottom = true;
}
}
//tryes to find the begin of the next page on the right and on the bottom
foreach (var item in LookupDependencyObject.Where(kv => kv.Width <= PageRect.Width && kv.Height <= PageRect.Height && kv.Start == Position.Center && kv.End == Position.BottomRight).ToList())
{
if (RightPageBegin > item.StartPoint.X)
{
RightPageBegin = item.StartPoint.X;
gotright = true;
}
if (BottomPageBegin > item.StartPoint.Y)
{
BottomPageBegin = item.StartPoint.Y;
gotbottom = true;
}
}
//if no page to the right was set, last try to find the startpoint for the next Page on the right
if (!gotright)
{
var where = LookupDependencyObject.Where(kv => kv.Start == Position.Center && kv.EndPoint.X > PageRect.Width);
RightPageBegin = where.Min(kv => kv.EndPoint.X);
}
//if no page to the bottom was set, last try to find the startpoint for the next Page on the bottom
if (!gotbottom)
{
var where = LookupDependencyObject.Where(kv => kv.Start == Position.Center && kv.EndPoint.Y > PageRect.Height);
BottomPageBegin = where.Min(kv => kv.EndPoint.Y);
}
}
/// <summary>
/// Visual/LogicalTree Crawler which hides Elements if the are overlap and NOT bigger than the Page
/// </summary>
/// <param name="parent"></param>
/// <param name="childList"></param>
/// <returns></returns>
private bool ForeachChildIn(DependencyObject parent, IList<DependencyObject> childList)
{
var result = false;
foreach (var item in childList)
{
if (item is Visual && parent is Visual)
{
if (VisualChild(parent, item))
{
result = true;
continue;
}
}
if (ForeachChildIn(item, item.getChilds().ToList()))
result = true;
else if (!(LookupDependencyObject.Last().Height > PageRect.Height || LookupDependencyObject.Last().Width > PageRect.Width))
item.SetValue(FrameworkElement.VisibilityProperty, Visibility.Hidden);
}
return result;
}
private bool VisualChild(DependencyObject parent, DependencyObject child)
{
var visualChild = child as Visual;
double ActualWidth,
ActualHeight;
//relative Position des myVisual zu VisualElement
Point relativeSartPoint = visualChild.TransformToAncestor((Visual)parent)
.Transform(new Point(0, 0));
Point relativeEndPoint = createRelativEndPoint(visualChild, relativeSartPoint, out ActualWidth, out ActualHeight);
Position start = findDOPosition(PageRect, relativeSartPoint);
Position end = findDOPosition(PageRect, relativeEndPoint);
var DOContainer = new DependencyObjectContainer
{
DO = child,
BeginVisbility = (Visibility)child.GetValue(FrameworkElement.VisibilityProperty),
StartPoint = relativeSartPoint,
EndPoint = relativeEndPoint,
Start = start,
End = end,
Width = ActualWidth,
Height = ActualHeight
};
LookupDependencyObject.Add(DOContainer);
if (start == Position.Center && end == Position.Center)
return true;
return false;
}
/// <summary>
/// Prüft die Positon des Punktes im verhältnis zum Rechteck
/// </summary>
/// <param name="PageRect"></param>
/// <param name="relativePoint"></param>
/// <returns></returns>
private Position findDOPosition(Rect PageRect, Point relativePoint)
{
//Top
if (PageRect.Top > relativePoint.Y)
if (PageRect.Left > relativePoint.X)
return Position.TopLeft;
else if (PageRect.Right < relativePoint.X)
return Position.TopRight;
else
return Position.TopMid;
//Bottom
else if (PageRect.Bottom < relativePoint.Y)
if (PageRect.Left > relativePoint.X)
return Position.BottomLeft;
else if (PageRect.Right < relativePoint.X)
return Position.BottomRight;
else
return Position.BottomMid;
//Mid
if (PageRect.Left > relativePoint.X)
return Position.MidLeft;
else if (PageRect.Right < relativePoint.X)
return Position.MidRight;
else
return Position.Center;
}
/// <summary>
/// Hier wierd der Endpunkt des Objektes berechnet
/// da hier für die weite und höhe des Visual ermittelt wird
/// werden diese Werte zusätzlich via out zurückgegeben
/// </summary>
/// <param name="myVisual"></param>
/// <param name="relativeSartPoint"></param>
/// <param name="ActualWidth"></param>
/// <param name="ActualHeight"></param>
/// <returns></returns>
private static Point createRelativEndPoint(Visual myVisual, Point relativeSartPoint, out double ActualWidth, out double ActualHeight)
{
double relativeEndHorizontal;
double relativeEndVertikal;
ActualWidth = (double)myVisual.GetValue(FrameworkElement.ActualWidthProperty);
ActualHeight = (double)myVisual.GetValue(FrameworkElement.ActualHeightProperty);
relativeEndHorizontal = relativeSartPoint.X + ActualWidth;
relativeEndVertikal = relativeSartPoint.Y + ActualHeight;
return new Point(relativeEndHorizontal, relativeEndVertikal);
}
private List<DependencyObjectContainer> LookupDependencyObject = new List<DependencyObjectContainer>();
#endregion cTor
public UIElement VisualElement
{ get; set; }
#region DocumentPaginator
#region Properties
public override bool IsPageCountValid
{
get
{
return true;
//throw new NotImplementedException();
}
}
public override int PageCount
{
get { return Pages.Count; }
}
public override System.Windows.Size PageSize { get; set; }
public override IDocumentPaginatorSource Source
{
get { return null; }
}
#endregion Properties
public override DocumentPage GetPage(int pageNumber)
{
return Pages[pageNumber];
}
private int CountPages()
{
if (VisualElement == null)
return -1;
return Pages.Count;
}
#endregion DocumentPaginator
}
/// <summary>
/// Used as Information Container
/// </summary>
internal class DependencyObjectContainer
{
public DependencyObject DO { get; set; }
public Visibility BeginVisbility { get; set; }
public Point StartPoint { get; set; }
public Point EndPoint { get; set; }
public Position Start { get; set; }
public Position End { get; set; }
public double Width { get; set; }
public double Height { get; set; }
}
}
ControlDocumentPage
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
namespace WpfUIPrinter
{
public class ControlDocumentPage : DocumentPage
{
public double? RightPageBegin { get; set; }
public double? BottomPageBegin { get; set; }
public ControlDocumentPage(Visual visual, double? rightPageBegin, double? bottomPageBegin)
: base(visual)
{
this.RightPageBegin = rightPageBegin;
this.BottomPageBegin = bottomPageBegin;
}
public ControlDocumentPage(Visual visual, double? rightPageBegin, double? bottomPageBegin, Size pageSize, Rect bleedBox, Rect contentBox)
: base(visual, pageSize, bleedBox, contentBox)
{
this.RightPageBegin = rightPageBegin;
this.BottomPageBegin = bottomPageBegin;
}
}
}
enumeration position
namespace WpfUIPrinter
{
public enum Position
{
TopLeft = 7, TopMid = 8, TopRight = 9,
MidLeft = 4, Center = 5, MidRight = 6,
BottomLeft = 1, BottomMid = 2, BottomRight = 3,
FullOut = 0
}
}
UIElementExtension
using System.IO;
using System.Windows;
using System.Windows.Markup;
using System.Xml;
namespace WpfUIPrinter
{
public static class UIElementExtension
{
public static UIElement Clone(this UIElement uielement)
{
string xamlString = XamlWriter.Save(uielement);
StringReader stringReader = new StringReader(xamlString);
XmlReader xmlReader = XmlReader.Create(stringReader);
return (UIElement)XamlReader.Load(xmlReader);
}
}
}
DependencyObjectExtension
///Basiert auf Josh Smith Tutorial
///Understanding the Visual Tree and Logical Tree in WPF
///http://www.codeproject.com/Articles/21495/Understanding-the-Visual-Tree-and-Logical-Tree-in
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace WpfUIPrinter
{
public static class DependencyObjectExtension
{
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetChild"/> method, which also
/// supports content elements. Do note, that for content elements,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="parent">The item to be processed.</param>
/// <returns>The submitted item child elements, if available.</returns>
public static IEnumerable<DependencyObject> getChilds(this DependencyObject parent)
{
if (parent == null) yield break;
//use the logical tree for content / framework elements
foreach (object obj in LogicalTreeHelper.GetChildren(parent))
{
var depObj = obj as DependencyObject;
if (depObj != null)
yield return depObj;
}
//use the visual tree per default
if (parent is Visual || parent is Visual3D)
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
yield return VisualTreeHelper.GetChild(parent, i);
}
}
}
}
}
Known Issues:
- Cannot reprint the binding at the moment (there is something that can be done with the method i i Clone my UIElemnt)
- Start page search (sometimes it cannot find the right point to start on a new page)
- hidding Elements to be displayed (here is the part where I'm not smart enough)
+3
source to share
No one has answered this question yet
Check out similar questions: