Running on Empty

The few things I know, I like to share.

Using FlowDocuments XAML to print XPS Documents. (Part 6)

Indroduction

This one is for you Linda and Cow-Killer.

Welcome to Part 6 of the Using FlowDocuments XAML to print XPS Documents series.  Up until now it has been impossible to use this series to create an XPS document file or display the XAML text in a document viewer.  It would be nice to be able to create a XAML template and easily insert text into the template using databinding.  Here is my solution, hope to hear your feedback.

Creating a new method to Load Xaml Strings

This method simply takes a raw XAML string and loads it into a FlowDocument, you may have seen this before in previous samples.

        public static IDocumentPaginatorSource RenderFlowDocumentTemplate(string templatePath, 

Dictionary<string, string> parameters)
        {
            string rawXamlText = "";
            using (StreamReader streamReader = File.OpenText(templatePath))
            {
                rawXamlText = streamReader.ReadToEnd();
            }

            rawXamlText = MergeParameters(parameters, rawXamlText);

            FlowDocument document = XamlReader.Load(new XmlTextReader(new StringReader(rawXamlText))) as 

FlowDocument;
           
            return document;
        }

The new parts of this method are the addition of a Dictionary of string key/value pairs and the MergeParameters method.

Merging Parameters

Since XAML is nothing more than a jazzed up XML file, we can use simple string replacements to replace items in our template with strings from our objects.

        private static string MergeParameters(Dictionary<string, string> parameters, string template)
        {
            foreach (KeyValuePair<string, string> kvp in parameters)
            {
                template = template.Replace(string.Format("[{0}]", kvp.Key), kvp.Value);
            }
            return template;
        }

Now we have a nearly completed solution to XAML template rendering, all that is left to do is create a template.

The new XAML Template

<FlowDocument xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
     xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml>
    <Paragraph FontFamily="Arial" Margin="20">
        <TextBlock Text="[TestString]" FontSize="12"/>
    </Paragraph>
</FlowDocument>

Notice the inclusion of [TestString], this is essentially the “databind” we will use in the string Key/Value pair dictionary.

Using the new Key/Value pair databinding

            Dictionary<string, string> strings = new Dictionary<string,string>();
            strings.Add("TestString", "Tester");
            IDocumentPaginatorSource flowDocument =
                

XamlTemplatePrinter.RenderFlowDocumentTemplate(Path.Combine(Environment.CurrentDirectory, 

"TestTemplate.xaml"), strings);
            flowDocument.DocumentPaginator.PageSize = new Size(96 * 8.5, 96 * 11);
            PrintDialog flowPrintDialog = XamlTemplatePrinter.GetPrintDialog();
            if (flowPrintDialog == null)
                return;
            PrintQueue flowPrintQueue = flowPrintDialog.PrintQueue;
            XamlTemplatePrinter.PrintFlowDocument(flowPrintQueue, 

flowDocument.DocumentPaginator);

Conclusion

Thank you all for your comments and suggestions.  Your questions and comments are fuel for the continued success of this blog.  Please feel free to leave a comment or contact me if you have suggestions or questions.

May 28, 2008 Posted by | C#, WPF, XAML, XPS | 15 Comments

Using FlowDocuments XAML to print XPS Documents. (Part 5)

Introduction

Welcome to Part 5 of the Using FlowDocument XAML to print XPS Documents series.  This article has been a long time coming.  In this article I will focus on creating dynamic XAML content.

Creating a method to Load Xaml Strings

This method simply takes a raw XAML string and loads it into a FlowDocument.

        public static IDocumentPaginatorSource RenderFlowDocumentString(string rawXamlString, object dataContextObject)
        {
            FlowDocument document = XamlReader.Load(new XmlTextReader(new StringReader(rawXamlString))) as FlowDocument;
            if (dataContextObject != null)
            {
                document.DataContext = dataContextObject;
            }
            return document;
        }

Now all we need to do is create some XAML strings.

Using the new Xaml string loader plus some.

        public void PrintGenericList(List<Awards> awardList)
        {
            PrintDialog flowPrintDialog = XamlTemplatePrinter.GetPrintDialog();
            if (flowPrintDialog == null)
                return;


                StringBuilder xamlString = new StringBuilder();
                
                xamlString.Append("<FlowDocument xmlns=\"<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation\">http://schemas.microsoft.com/winfx/2006/xaml/presentation\</a>" \nxmlns:x=\"<a href="http://schemas.microsoft.com/winfx/2006/xaml\">http://schemas.microsoft.com/winfx/2006/xaml\</a>">");
                
                foreach (Awards award in awardList)
                {
                    xamlString.Append("<Paragraph FontFamily=\"Arial\" Margin=\"20\">");
                    xamlString.AppendFormat("<TextBlock Text=\"{0}  {1}\" Margin=\"170,0,0,0\" />", award.SerialNumber, award.ControlNumber);
                    xamlString.Append("<TextBlock Text=\"\" Margin=\"170,0,0,0\" />");
                    xamlString.Append("</Paragraph>");
                }

                xamlString.Append("</FlowDocument>");

                IDocumentPaginatorSource flowDocument =
                       XamlTemplatePrinter.RenderFlowDocumentString(xamlString.ToString(), null);

                flowDocument.DocumentPaginator.PageSize = new Size(96 * 5, 96 * 2);

                PrintQueue flowPrintQueue = flowPrintDialog.PrintQueue;
                XamlTemplatePrinter.PrintFlowDocument(flowPrintQueue, flowDocument.DocumentPaginator);            
        }

Conclusion

In this article I demonstrated building dynamic XAML to print or same as an XPS document.  I am personally using this in an application to format labels to be sent to a printer.

April 30, 2008 Posted by | C#, WPF, XAML, XPS | 4 Comments

Using FlowDocuments XAML to print XPS Documents. (Part 4)

Introduction

Welcome to Part 4 of the Using FlowDocument XAML to print XPS Documents series. This article will focus on saving XAML to an XPS Document file… finally.

In previous parts of this series I demonstrated creating DocumentPaginator, adjusting PageSize, and printing.  Saving the XPS document is actually quite easy at this point. I will be building upon the classes discussed in Part 1, 2 and 3 of this series.

Creating a method to save an XPS document from a DocumentPaginator.

This method accepts a DocumentPaginator source and saves it to test.xps file, clean and simple.  Obvious improvments would be to use a save file dialogue.

public static void CreateXPSDocument(DocumentPaginator paginator)
{
    using (Package container = Package.Open("test" + ".xps", FileMode.Create))
    {
        using (XpsDocument xpsDoc = new XpsDocument(container, CompressionOption.Maximum))
        {
            XpsSerializationManager xpsSM = new XpsSerializationManager(new XpsPackagingPolicy(xpsDoc), false);
            xpsSM.SaveAsXaml(paginator);
        }
    }
}

Using the CreateXPSDocument method.

private void SaveXPSButton_Click(object sender, RoutedEventArgs e)
{
    SampleClass sample = new SampleClass();
    sample.Sample = "FlowDocument DataBinding Sample";   

    IDocumentPaginatorSource flowDocument =
        XamlTemplatePrinter.RenderFlowDocumentTemplate(Path.Combine(Environment.CurrentDirectory, "XamlDocumentTemplate.xaml"), sample);      

    flowDocument.DocumentPaginator.PageSize = new Size(96 * 8, 96 * 11);       

    XamlTemplatePrinter.CreateXPSDocument(flowDocument.DocumentPaginator);
}

Yes, it really was that easy. The XamlTemplatePrinter class we have created during this series is now actually becoming quite useful. This article will conclude this series for now, unless I think of something I might have forgotten.Please feel free to comment or leave suggestions for future articles.

January 4, 2008 Posted by | C#, WPF, XAML, XPS | 12 Comments

Using FlowDocument XAML to print XPS Documents. (Part 3)

Introduction

Welcome to Part 3 of the Using FlowDocument XAML to print XPS Documents.  This article will demonstrate altering the page size of the FlowDocument.  I will be building off of the classes used in Part 2 of this series.

Altering PageSize of a FlowDocument.

First, we need to extend our XPS creation class by adding a page size adjustment method.

public static DocumentPaginator AdjustFlowDocumentToPage(DocumentPaginator document, PrintQueue printQueue)
{
    const double inch = 96;

    PrintTicket printTicket = printQueue.UserPrintTicket;

    // Get the media size.
    double width = printTicket.PageMediaSize.Width.Value;
    double height = printTicket.PageMediaSize.Height.Value; 

    // Set the margins.
    double leftmargin = 1.25 * inch;
    double rightmargin = 1.25 * inch;
    double topmargin = 1 * inch;
    double bottommargin = 1 * inch;

    // Calculate the content size.
    double contentwidth = width - leftmargin - rightmargin;
    double contentheight = height - topmargin - bottommargin;
    document.PageSize = new Size(contentwidth, contentheight); 

    return document;
}

This method pretty much identifies the page size of the print queue source you are using and adjusts the DocumentPaginator page size to fit.  Nothing too special here.

**One warning though, you will want to make certain the contentwidth and the contentheight are both positive numbers.  I excluded the tests here for simplicity **

Using the new AdjustFlowDocumentToPage method.

Now all that is left to do is to use the new method when we print.

private void PrintFuelingButton_Click(object sender, RoutedEventArgs e)
{
    SampleClass sample = new SampleClass();
    sample.Sample = "FlowDocument DataBinding Sample";

    IDocumentPaginatorSource flowDocument =
        XamlTemplatePrinter.RenderFlowDocumentTemplate(Path.Combine(Environment.CurrentDirectory, "XamlDocumentTemplate.xaml"), sample);
    PrintDialog flowPrintDialog = XamlTemplatePrinter.GetPrintDialog();
    if (flowPrintDialog == null)
        return;

    PrintQueue flowPrintQueue = flowPrintDialog.PrintQueue;   

    // This line is new and is an easy way to resize the PageSize.
    // However, it isn't enough just to change the PageSize, you also want to account for margin.
    flowDocument.DocumentPaginator.PageSize = new Size(96 * 8, 96 * 11);    

    // Print the FlowDocument, but check for the PrintQueue PageSize and adjust the content.
    XamlTemplatePrinter.PrintFlowDocument(flowPrintQueue,
                                          XamlTemplatePrinter.AdjustFlowDocumentToPage(flowDocument.DocumentPaginator, flowPrintQueue));
}

Please feel free to comment or leave suggestions for future articles.


				
                

January 3, 2008 Posted by | C#, WPF, XAML, XPS | 2 Comments

Using FlowDocument XAML to print XPS Documents. (Part 2)

Introduction.

Welcome to Part 2 of the Using FlowDocument XAML to print XPS Documents.  This article will demonstrate using DataContext to add usefulness to Part 1 of this series.

Creating a “useful” XAML FlowDocument.

First, we need to create a FlowDocument that implements element Binding.  For lack of a better word, I will use “element” to describe a C# class that is used to hold data.

<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Paragraph FontFamily="Arial">
    <StackPanel>
      <TextBlock Text="{Binding Path=Sample}"/>
    </StackPanel>
  </Paragraph>
</FlowDocument>

Okay, again not an extremely useful example, trust me, keep reading it gets better.  DataBinding a XAML FlowDocumentFinally, something useful, we get to actually see some data in our FlowDocument. 

There are several ways to bind data in WPF, some declarative, some programmatic and some that are a cross between the two.  Knowing which DataBinding method to use is the trick.  In this sample, since we don’t have a code behind we are going to use DataContext, which is a cross declarative/programmatic mix.  You have already done the declarative piece in the line.

<TextBlock Text="{Binding Path=Sample}"/>

First, create a class to hold some data, this will be our binding element.

namespace Part2
{
    public class SampleClass
    {
        private string _sample;
        public string Sample
        {
            get { return _sample; }
            set { _sample = value; }
        }
    }
}

Next, create or refactor the RenderFlowDocumentTemplate method from Part 1.

public static IDocumentPaginatorSource RenderFlowDocumentTemplate(string templatePath, object dataContextObject)
{
    string rawXamlText = "";
    using (StreamReader streamReader = File.OpenText(templatePath))
    {
        rawXamlText = streamReader.ReadToEnd();
    }

    FlowDocument document = XamlReader.Load(new XmlTextReader(new StringReader(rawXamlText))) as FlowDocument;
    if (dataContextObject != null)
    {
        document.DataContext = dataContextObject;
    }
    return document;
}

The main addition is the dataContextObject.  I used object base class to make this example more generic, checking for null is simply a precaution, it is not really necessary.

Bringing it together… Again.

Simply create an instance of your element class and pass it to your new and improved RenderFlowDocumentTemplate method.

private void PrintFuelingButton_Click(object sender, RoutedEventArgs e)
{
    SampleClass sample = new SampleClass();
    sample.Sample = "FlowDocument DataBinding Sample";

    IDocumentPaginatorSource flowDocument =
        XamlTemplatePrinter.RenderFlowDocumentTemplate(Path.Combine(Environment.CurrentDirectory, "XamlDocumentTemplate.xaml"), sample);
    PrintDialog flowPrintDialog = XamlTemplatePrinter.GetPrintDialog();
    if (flowPrintDialog == null)
        return;
    PrintQueue flowPrintQueue = flowPrintDialog.PrintQueue;
    XamlTemplatePrinter.PrintFlowDocument(flowPrintQueue, flowDocument.DocumentPaginator);
}

Obviously, this is a great improvement to Part 1, future articles will focus on dynamic XAML uses and FixedDocument.  As always, I look forward to reading comments and suggestions.

January 2, 2008 Posted by | C#, WPF, XAML, XPS | 5 Comments

Using FlowDocument XAML to print XPS Documents. (Part 1)

Introduction
In this series of articles I will demonstrate XAML techniques for creating XPS documents and FlowDocuments for reporting and printing in WPF applications. This first article will focus on creating a FlowDocument template using XAML.
XPS documents are the new XML paper specification for electronic paper. XPS will most likely not supplant PDF document formats for electronic media, but if you are already using WPF it makes sense to use the built in functionality provided by the .Net framework for XPS document viewing.
 

Creating a XAML FlowDocument.
The first step is to insert a new XML document into your WPF solution. Save the document with the XAML extension and replace the normal XML headers with the following.

<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Paragraph FontFamily="Arial">
    Hello World!
  </Paragraph>
</FlowDocument>

Nice and simple, we now have a useful start to the XAML template.
Now, you will want to make certain the build properties are set up correctly. Right click on your new XAML document and go to properties. Make the following changes.• Build Action-> Content

• Copy to Output Directory -> Copy Always

Your XAML template will now build to the output /Bin directory along with your executable.

 Creating a XAML FlowDocument printer.

First, create a method that will take our FlowDocument XAML and convert it into a DocumentPaginator.

public static IDocumentPaginatorSource RenderFlowDocumentTemplate(string templatePath)
{
    string rawXamlText = "";

    //Create a StreamReader that will read from the document template.
    using (StreamReader streamReader = File.OpenText(templatePath))
    {
        rawXamlText = streamReader.ReadToEnd();
    }
    //Use the XAML reader to create a FlowDocument from the XAML string.
    FlowDocument document = XamlReader.Load(new XmlTextReader(new StringReader(rawXamlText))) as FlowDocument;
    return document;
}

Next, create a method that will open the standard print dialog and return to us a valid printer.

public static PrintDialog GetPrintDialog()
{
    PrintDialog printDialog = null;

    // Create a Print dialog.
    PrintDialog dlg = new PrintDialog();

    // Show the printer dialog.  If the return is "true",
    // the user made a valid selection and clicked "Ok".
    if (dlg.ShowDialog() == true)
        printDialog = dlg;  // return the dialog the user selections.
    return printDialog;
}

Next, create a method that will write an XPS document.

private static void PrintDocumentPaginator(XpsDocumentWriter xpsDocumentWriter, DocumentPaginator document)
{
    xpsDocumentWriter.Write(document);
}

Finally, create a pair of methods that will get and print the FlowDocument.

private static XpsDocumentWriter GetPrintXpsDocumentWriter(PrintQueue printQueue)
{
    XpsDocumentWriter xpsWriter = PrintQueue.CreateXpsDocumentWriter(printQueue);
    return xpsWriter;
}

public static void PrintFlowDocument(PrintQueue printQueue, DocumentPaginator document)
{
    XpsDocumentWriter xpsDocumentWriter = GetPrintXpsDocumentWriter(printQueue);
    PrintDocumentPaginator(xpsDocumentWriter, document);
}

Bringing it together.
Create a print button on your application, place the following code in the Click method.

private void PrintButton_Click(object sender, RoutedEventArgs e)
{
    IDocumentPaginatorSource flowDocument = XamlTemplatePrinter.RenderFlowDocumentTemplate(Path.Combine(Environment.CurrentDirectory, "XamlDocumentTemplate.xaml"));

    PrintDialog flowPrintDialog = XamlTemplatePrinter.GetPrintDialog();
    if (flowPrintDialog == null)
        return;

    PrintQueue flowPrintQueue = flowPrintDialog.PrintQueue;
    XamlTemplatePrinter.PrintFlowDocument(flowPrintQueue, flowDocument.DocumentPaginator);
}

Run the application and press the print button, you will now be able to print Hello World to the selected printer. Not exactly very special, but in the few articles I will build upon the printing class to include dynamic templates, which will greatly increase the usefulness of this class.
As always feel free to comment or leave suggestions.

December 21, 2007 Posted by | C#, WPF, XAML, XPS | 22 Comments

   

Follow

Get every new post delivered to your Inbox.