Dynamic printable content area - wpf

I am using WPF Flowdocument to print content in a Table with Header and Footer.
However, when the data occupies only couple of rows in the table, footer section still reflects at the last of the page and the whole page gets printed, leaving half of the page blank.
Can I expect the content (Header + Content + Footer) to occupy only half of the page if the data is less and one full page (A4/Letter page) if the data is more than half page? And if the data is more than a full page, it should span to the second page too.
Thanks..

I am able to solve this by overriding OnPrintCommand for DocumentViewer
public class CustomDocumentViewer : DocumentViewer
{
protected override void OnPrintCommand()
{
// get a print dialog, defaulted to default printer and default printer's preferences.
PrintDialog printDialog = new PrintDialog();
printDialog.PrintQueue = LocalPrintServer.GetDefaultPrintQueue();
printDialog.PrintTicket = printDialog.PrintQueue.DefaultPrintTicket;
// get a reference to the FixedDocumentSequence for the viewer.
FixedDocumentSequence docSeq = this.Document as FixedDocumentSequence;
if (Globals.PRINT_COMMAND_PAGEHEIGHT == "FULL")
printDialog.PrintTicket.PageMediaSize = new PageMediaSize(960, 1152);
else
printDialog.PrintTicket.PageMediaSize = new PageMediaSize(960, 576);
if (printDialog.ShowDialog() == true)
{
// set the print ticket for the document sequence and write it to the printer.
docSeq.PrintTicket = printDialog.PrintTicket;
XpsDocumentWriter writer = PrintQueue.CreateXpsDocumentWriter(printDialog.PrintQueue);
writer.WriteAsync(docSeq, printDialog.PrintTicket);
}
}
}
Window_Activated function:
//Set Page Height and Width dynamically
MemoryStream mem = new MemoryStream();
byte[] buf = Encoding.UTF8.GetBytes(strXmlData);
mem.Write(buf, 0, buf.Length);
mem.Position = 0;
FlowDocument res = XamlReader.Load(mem) as FlowDocument;
//1152 = 12in * 96 DPI
if (Utils.GetPageHeight(strOrderNo.Substring(0, 2)) == "FULL")
{
res.PageHeight = Globals.FULL_PAGE_HEIGHT;
Globals.PRINT_COMMAND_PAGEHEIGHT = "FULL";
}
else
{
res.PageHeight = Globals.HALF_PAGE_HEIGHT;
Globals.PRINT_COMMAND_PAGEHEIGHT = "HALF";
}
res.PageWidth = Globals.PAGE_WIDTH;
reportDocument.XamlData = XamlWriter.Save(res);
in the DocumentViewer xaml file:
<Grid>
<spu:CustomDocumentViewer x:Name="documentViewer" />
</Grid>
and in the Flowdocument Template:
PageHeight="0.0cm" PageWidth="0.0cm" ColumnWidth="21.0cm"
Thanks,
~ Ravi

Related

WPF event within a frame stored on a stackpanel

I need to recreate a program similar to whatsapp that can send and receive messages, images videos and audio. I have created a WPF form to show messages that looks like this:
I have a stack panel that contains text bubbles on them. Text messages work fine but if I send an image I want the user to be able to click on the image text bubble and it must become full screen. The image text bubble consists of a label that has a frame in it and then within that frame the image is stored. The label was used since we could resize the label.
However, because the image is done like this dynamically, we cannot seem to register an on-click event on this image bubble. If you have any better ways that we can display the image or how to log this event it would be much appreciated. Here is the method used to add the image.
public void AddMessage_Image(string path, string displayName, int role, string date = "")
{
//Create an image from the path
ImageBrush image = new ImageBrush();
image.ImageSource = new BitmapImage(new Uri(path, UriKind.Absolute));
image.Stretch = Stretch.Uniform;
//Create a frame in which to place the image
Frame fr = new Frame();
fr.Background = image;
fr.MinHeight = 120;
fr.MinWidth = 160;
//Ensure scalabilty of the image
Viewbox vb = new Viewbox();
vb.Child = fr;
vb.Stretch = Stretch.Uniform;
//Place the image in a sizable container
Label lbl = new Label();
lbl.MinHeight = 10;
lbl.MinWidth = 10;
lbl.MaxHeight = 300;
lbl.MaxWidth = 400;
lbl.Content = vb;
if (role == (int) Role.Sender)
lbl.HorizontalAlignment = HorizontalAlignment.Right;
else
lbl.HorizontalAlignment = HorizontalAlignment.Left;
lbl.Background = Brushes.Black;
//Place the image in the chat
chatbox.Children.Add(lbl);
}

WPF multipage printing for FixedDocument (Visual C# 2010)

I have a question about multipage FixedPage. I have a Grid created programmatically and the Grid exceeds one A4 page. Now I want to print the Grid in several FixedPage with print margin. But on my way, I create the Grid repeatedly and offset the LeftTop point in the fixedPage Arrange function. I meet a problem that I cannot set print margin in fixedPage, because I set the print margin to the fixedPage and then the first page will have print margin and the next pages will be blank.
How do print multipage of FixedDocument for a large grid want to print?
PrintDialog pd = new System.Windows.Controls.PrintDialog();
if (pd.ShowDialog() == false)
{
return;
}
var pageSize = new Size(pd.PrintableAreaWidth, pd.PrintableAreaHeight);
var document = new FixedDocument();
document.DocumentPaginator.PageSize = pageSize;
for (int nPage = 0; nPage < MaxPage; nPage++)
{
Grid tempGrid = LoadControlMotherInit();
tempGrid.Width = GridWidth;
tempGrid.Height = GridActualHeight;
Point leftTop = new Point();
leftTop.X = 10;
leftTop.Y = -nPage * pageSize.Height;
// Create FixedPage
var fixedPage = new FixedPage();
fixedPage.Width = pageSize.Width;
fixedPage.Height = pageSize.Height;
fixedPage.Margin = new Thickness(0, 0, 0, 96);
fixedPage.Children.Add((UIElement)tempGrid);
fixedPage.Measure(pageSize);
fixedPage.Arrange(new Rect(leftTop, pageSize));
fixedPage.UpdateLayout();
// Add page to document
var pageContent = new PageContent();
((System.Windows.Markup.IAddChild)pageContent).AddChild(fixedPage);
document.Pages.Add(pageContent);
}
pd.PrintDocument(document.DocumentPaginator, "My Document");
From looking at your example,
PrintDialog.PrintDocument method takes in a DocumentPaginator, which could come from a multitude of source.
that being said, you can inherit from DocumentPaginator and take control of everything from PageSize, PageCount to the actual DocumentPage being returned.
Imagine your DocumentPage as a sliding window over your UIElement; but instead of sliding your DocumentPage, you slide your UIElement using its RenderTransform.

Content not rotated when printing in wpf

I am printing an XPS document using the System.Windows.Controls.PrintDialog. When I choose landscape orientation in the print dialog the resulting page is rotated to landscape but the actual content stays in portrait mode and is clipped.
This is the way I print. I also tried to use the AddJob method on PrintDialog.PrintQueue and the overloads on PrintQueue.CreateXpsDocumentWriter(...).Write(...) all with the same or worse result. And I tried to set DocumentPaginator.PageSize, the printDialog.PrintTicket.PageMediaSize and the width and height of th first FixedPage to the correct lanscape size with no result. PrintDialog.PrintTicket.PageOrientation is on landscape and PrintDialog.PrintableAreaWidth and PrintDialog.PrintableAreaHeight is as it should be when lanscape is selected after the PrintDialog was shown.
var printDialog = new PrintDialog
{
MaxPage = (uint)pageCount,
MinPage = 1,
PageRange = new PageRange(1, pageCount),
UserPageRangeEnabled = true
};
if (printDialog.ShowDialog() != true) return;
using (var doc = new XpsDocument(filename, FileAccess.Read))
{
var paginator = doc.GetFixedDocumentSequence().DocumentPaginator;
printDialog.PrintDocument(fds.paginator , "myPrintJob");
}

VisualBrush does "lazy evaluation"?

I create FixedDocument in more iterations (one page per iteration) like this:
PrintDialog pr = new PrintDialog();
FixedDocument doc = new FixedDocument();
foreach(var i in a)
{
// some changes of MaingGrid here
...
//
VisualBrush vb = new VisualBrush(this.MainGrid);
FixedPage page = new FixedPage();
page.Width = doc.DocumentPaginator.PageSize.Width;
page.Height = doc.DocumentPaginator.PageSize.Height;
Rectangle rec = new Rectangle();
rec.Width = this.MainGrid.ActualWidth;
rec.Height = this.MainGrid.ActualHeight;
rec.Fill = vb;
page.Children.Add(rec);
PageContent content = new PageContent();
((IAddChild)content).AddChild(page);
doc.Pages.Add(content);
}
pr.PrintDocument(doc.DocumentPaginator, "test");
In each iteration I change the MainGrid a little. So each page should contain the actual state of MainGrid. But the printed document contains pages with same content of last iteration (in other words - the last state is on all pages in document). Is there any "lazy evaluation" of VisualBrush or something?
Call .Freeze() on the VisualBrush in each iteration. Otherwise, it will always be a live view of whatever visual you pointed it at.
EDIT: Freeze doesn't work but you can render the brush into a static bitmap. See http://blog.avanadeadvisor.com/blogs/markti/archive/2008/04/14/10888.aspx

Printing a WPF FlowDocument

I'm building a demo app in WPF, which is new to me. I'm currently displaying text in a FlowDocument, and need to print it.
The code I'm using looks like this:
PrintDialog pd = new PrintDialog();
fd.PageHeight = pd.PrintableAreaHeight;
fd.PageWidth = pd.PrintableAreaWidth;
fd.PagePadding = new Thickness(50);
fd.ColumnGap = 0;
fd.ColumnWidth = pd.PrintableAreaWidth;
IDocumentPaginatorSource dps = fd;
pd.PrintDocument(dps.DocumentPaginator, "flow doc");
fd is my FlowDocument, and for now I'm using the default printer instead of allowing the user to specify print options. It works OK, except that after the document prints, the FlowDocument displayed on screen has changed to to use the settings I specified for printing.
I can fix this by manually resetting everything after I print, but is this the best way? Should I make a copy of the FlowDocument before I print it? Or is there another approach I should consider?
yes, make a copy of the FlowDocument before printing it. This is because the pagination and margins will be different. This works for me.
private void DoThePrint(System.Windows.Documents.FlowDocument document)
{
// Clone the source document's content into a new FlowDocument.
// This is because the pagination for the printer needs to be
// done differently than the pagination for the displayed page.
// We print the copy, rather that the original FlowDocument.
System.IO.MemoryStream s = new System.IO.MemoryStream();
TextRange source = new TextRange(document.ContentStart, document.ContentEnd);
source.Save(s, DataFormats.Xaml);
FlowDocument copy = new FlowDocument();
TextRange dest = new TextRange(copy.ContentStart, copy.ContentEnd);
dest.Load(s, DataFormats.Xaml);
// Create a XpsDocumentWriter object, implicitly opening a Windows common print dialog,
// and allowing the user to select a printer.
// get information about the dimensions of the seleted printer+media.
System.Printing.PrintDocumentImageableArea ia = null;
System.Windows.Xps.XpsDocumentWriter docWriter = System.Printing.PrintQueue.CreateXpsDocumentWriter(ref ia);
if (docWriter != null && ia != null)
{
DocumentPaginator paginator = ((IDocumentPaginatorSource)copy).DocumentPaginator;
// Change the PageSize and PagePadding for the document to match the CanvasSize for the printer device.
paginator.PageSize = new Size(ia.MediaSizeWidth, ia.MediaSizeHeight);
Thickness t = new Thickness(72); // copy.PagePadding;
copy.PagePadding = new Thickness(
Math.Max(ia.OriginWidth, t.Left),
Math.Max(ia.OriginHeight, t.Top),
Math.Max(ia.MediaSizeWidth - (ia.OriginWidth + ia.ExtentWidth), t.Right),
Math.Max(ia.MediaSizeHeight - (ia.OriginHeight + ia.ExtentHeight), t.Bottom));
copy.ColumnWidth = double.PositiveInfinity;
//copy.PageWidth = 528; // allow the page to be the natural with of the output device
// Send content to the printer.
docWriter.Write(paginator);
}
}
You can use the code from the URL below, it wraps the flow document in a fixed document and prints that, the big advantage is that you can use it to add margin, headers and footers.
https://web.archive.org/web/20150502085246/http://blogs.msdn.com:80/b/fyuan/archive/2007/03/10/convert-xaml-flow-document-to-xps-with-style-multiple-page-page-size-header-margin.aspx
The following works with both text and non-text visuals:
//Clone the source document
var str = XamlWriter.Save(FlowDoc);
var stringReader = new System.IO.StringReader(str);
var xmlReader = XmlReader.Create(stringReader);
var CloneDoc = XamlReader.Load(xmlReader) as FlowDocument;
//Now print using PrintDialog
var pd = new PrintDialog();
if (pd.ShowDialog().Value)
{
CloneDoc.PageHeight = pd.PrintableAreaHeight;
CloneDoc.PageWidth = pd.PrintableAreaWidth;
IDocumentPaginatorSource idocument = CloneDoc as IDocumentPaginatorSource;
pd.PrintDocument(idocument.DocumentPaginator, "Printing FlowDocument");
}
I am also generating a WPF report off a Flow document, but I am purposely using the flow document as a print preview screen. I there for want the margins to be the same. You can read about how I did this here.
In your scenario I'm thinking why not just make a copy of your settings, instead of the entire flow document. You can then re-apply the settings if you wish to return the document back to it's original state.

Resources