VisualBrush does "lazy evaluation"? - wpf

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

Related

WPF print - scale content to page visible area

I have to implement printing in a WPF application and I have to scale printable content to page visible area.
This is my code:
PrintDialog dialog = new PrintDialog();
if (dialog.ShowDialog().Value)
{
FixedDocument fixedDocument = new FixedDocument();
Canvas canvas = new Canvas();
PrintCapabilities pc = dialog.PrintQueue.GetPrintCapabilities();
System.Windows.Shapes.Rectangle rc = new System.Windows.Shapes.Rectangle();
rc.Width = pc.PageImageableArea.ExtentWidth;
rc.Height = pc.PageImageableArea.ExtentHeight;
rc.Fill = new SolidColorBrush(System.Windows.Media.Color.FromRgb(255, 0, 0));
canvas.Children.Add(rc);
Canvas.SetLeft(rc, pc.PageImageableArea.OriginWidth);
Canvas.SetTop(rc, pc.PageImageableArea.OriginHeight);
FixedPage fixedPage = new FixedPage();
fixedPage.Width = dialog.PrintableAreaWidth;
fixedPage.Height = dialog.PrintableAreaHeight;
fixedPage.Children.Add(canvas);
PageContent pageContent = new PageContent();
((IAddChild)pageContent).AddChild(fixedPage);
fixedDocument.Pages.Add(pageContent);
dialog.PrintDocument(fixedDocument.DocumentPaginator, "Demo");
}
The red rectangle is set to fill the page printable area.
The problem is the PrintCapabilities.PageImageableArea always returns the same values no matter what paper (Letter, Legal, etc) is selected.
If I run the code and don't change anything in the print dialog (default paper size is Letter) I get the print below:
The red rectangle fills the entire Letter page.
If I run the code and select Legal paper size in the print dialog then I get the print below:
The PrintCapabilities.PageImageableArea property still returns a Letter sized area no matter I selected Legal paper in print dialog.
I tested the code above with "Microsoft Print to PDF" printer which does not have unprintable margins (this is why the red rectangle fills the entire page) but the problem persists also with a physical printer.
What am I doing wrong?

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.

Dynamic printable content area

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

Programmatically creating a toolbar in WPF

I'm trying to create a simple toolbar in WPF, but the toolbar shows up with no corresponding buttons on it, just a very thin blank white strip. Any idea what I'm doing wrong, or what the recommended procedure is? Relevant code fragments so far:
var tb = new ToolBar();
var b = new Button();
b.Command = comback;
Image myImage = new Image();
myImage.Source = new BitmapImage(new Uri("back.png", UriKind.Relative));
b.Content = myImage;
tb.Items.Add(b);
var p = new DockPanel();
//DockPanel.SetDock(mainmenu, Dock.Top);
DockPanel.SetDock(tb, Dock.Top);
DockPanel.SetDock(sb, Dock.Bottom);
//p.Children.Add(mainmenu);
p.Children.Add(tb);
p.Children.Add(sb);
Content = p;
Without a third child-element for the Dockpanel p, the 'sb' element will fill everything except for the area of tb. The ToolBar will autoSize (its Height) according to its contents.
I suggest adding a simple text button first, to check the overall layout:
var b2 = new Button();
b2.Content = "B2";
tb.Items.Add(b2);
And then investigate what is wrong with the "back.png" image.

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