5

I am making a WPF application that will be used to print labels. I want to design a label template as a WPF window. In a different class I will instantiate this 'window template', fill the properties at runtime and print the label. I cannot show the label on the screen before printing, so I am unable to call .ShowDialog() on this window instance. This comes into play later.

I have been doing research on this for the past week and I have found two ways that nearly do what I want separately, and if I could combine them it would work, but I am missing a piece.

I could get this to work but following the steps here Printing in WPF Part 2

This accomplishes the basics of printing a document. However, I would not be able to use my template, and I would have to position everything in the code behind. This is a viable option but I would like to explore the template idea more.

        PrintDialog pd = new PrintDialog();
        FixedDocument document = new FixedDocument();
        document.DocumentPaginator.PageSize = new System.Windows.Size(pd.PrintableAreaWidth, pd.PrintableAreaHeight);

        FixedPage page1 = new FixedPage();
        page1.Width = document.DocumentPaginator.PageSize.Width;
        page1.Height = document.DocumentPaginator.PageSize.Height;


        // add some text to the page
        Label _lblBarcode = new Label();
        _lblBarcode.Content = BarcodeConverter128.StringToBarcode(palletID);
        _lblBarcode.FontFamily = new System.Windows.Media.FontFamily("Code 128");
        _lblBarcode.FontSize = 40;
        _lblBarcode.Margin = new Thickness(96);
        page1.Children.Add(_lblBarcode);


        // add the page to the document
        PageContent page1Content = new PageContent();
        ((IAddChild)page1Content).AddChild(page1);
        document.Pages.Add(page1Content);


        pd.PrintQueue = new System.Printing.PrintQueue(new System.Printing.PrintServer(), "CutePDF Writer");
        pd.PrintDocument(document.DocumentPaginator, "PalletID");

I just show how I am printing a barcode here. In the actual program I would add everything on the label and position it as well.

In this class I instantiate my label, fill its properties, and try to add it to the FixedPage's Children - but it returns this error.

Specified element is already the logical child of another element. Disconnect it first.

To get around this, I could remove each UI element from the template then add it to my fixed document. This does not seem like the cleanest solution, but it is possible.

I then wanted to iterate through each UIElement in this list instead of manually removing each element from my template and adding it to my fixed document. I would do this in the case that the label ever needs to change, this would make that easier.

I was able to find this link that shows how to iterate through each element

        public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
    {
        if (depObj != null)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                if (child != null && child is T)
                {
                    yield return (T)child;
                }

                foreach (T childOfChild in FindVisualChildren<T>(child))
                {
                    yield return childOfChild;
                }
            }
        }
    }

However, this only seems to work in the code behind of the template itself! So that doesn't help me in my class where I want to do the printing. I, of course, could make a global scoped variable and pass my list of UIelements that way, but that is getting less and less clean. On top of that, I can only call this method, in the Windows_Loaded event of the template class. Windows_Loaded only gets called when the WindowInstance.ShowDialog(); gets called, which of course shows the template on the screen which is also what I can't have.

Is there anyway to accomplish what I am trying, or am I better of scrapping the whole template idea and positioning everything via the code behind.

UPDATED While some of the answers here pointed me in the right direction, it took a little more digging to do what I wanted to do. Solution to follow:

I pieced my solution together with the help of Lena's answer which I accepted below and another post

To get an example of this working for yourself take Lena's View.xaml, View.xaml.cs, ViewModel.cs, and then my own method to print the View.xaml

    private void StackOverFlow()
        {
            string palletID = "00801004018000020631";
            PrintDialog pd = new PrintDialog();
            FixedDocument fixedDoc = new FixedDocument();
            PageContent pageContent = new PageContent();
            FixedPage fixedPage = new FixedPage();

            fixedDoc.DocumentPaginator.PageSize = new System.Windows.Size(pd.PrintableAreaWidth, pd.PrintableAreaHeight);
            fixedPage.Width = fixedDoc.DocumentPaginator.PageSize.Width;
            fixedPage.Height = fixedDoc.DocumentPaginator.PageSize.Height;

            fixedPage.Width = 4.0 * 96;
            fixedPage.Height = 3.0 * 96;
            var pageSize = new System.Windows.Size(4.0 * 96.0, 3.0 * 96.0);


            View v = new View();
            ViewModel vm = new ViewModel(); //This would be the label object with

            //set all ViewModel.cs Props here
vm.Text1 = "MyText1";
vm.Text2 = "MyText2";

            v.DataContext = vm;
            v.Height = pageSize.Height;
            v.Width = pageSize.Width;
            v.UpdateLayout();


            fixedPage.Children.Add(v);
            ((System.Windows.Markup.IAddChild)pageContent).AddChild(fixedPage);
            fixedDoc.Pages.Add(pageContent);

            //Use the XpsDocumentWriter to "Write" to a specific Printers Queue to Print the document
            XpsDocumentWriter dw1 = PrintQueue.CreateXpsDocumentWriter(new System.Printing.PrintQueue(new System.Printing.PrintServer(), "CutePDF Writer"));

            dw1.Write(fixedDoc);

        }
Community
  • 1
  • 1
Brandon
  • 915
  • 4
  • 23
  • 44
  • I used MVVM approach to print my window to a xps file. I created a xaml View containing some controls and templated table which can have as many items as I need. DataContext of this View holds all the data. Using XpsDocumentWriter and FixedPage this View then can be printed pretty easy especially if you don't have to care about paging of the document. – lena Dec 15 '15 at 13:14

3 Answers3

3

You could achieve this using the first part of the article you mentioned: WPF Printing Part 1

The PrintVisual method seems to work even if the window has not been shown.

Here is a small example :

The main windows with a single button:

MainWindow _barcodeWindow = new MainWindow("A1b2c3D4e5");

private void Button_Click(object sender, RoutedEventArgs e)
{
    _barcodeWindow.Print();
    _barcodeWindow.ShowDialog();
}

The "template" window:

<StackPanel Name="StuffToPrint">
    <Label>Some sample text</Label>
    <Label Name="BarCodeLabel" FontSize="40" Margin="96" FontFamily="Code 128"/>
    <Label>More sample text</Label>
</StackPanel>

and its code behind:

public MainWindow(string code)
{
    InitializeComponent();
    BarCodeLabel.Content = BarcodeConverter128.StringToBarcode(code);
}

public void Print()
{
    PrintDialog dlg = new PrintDialog();
    if (dlg.ShowDialog() == true)
    {
        dlg.PrintVisual(StuffToPrint, "Barcode");
    }
}
aegar
  • 764
  • 9
  • 17
2

I'm sorry if I misunderstood what you need but I hope my example can help you just a little. This is full and simple example - just create these files and test it!

View.xaml

<DockPanel x:Class="WpfApplication1.View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             Height="300" Width="400">
    <StackPanel Orientation="Vertical">
        <StackPanel Orientation="Horizontal">
            <TextBlock Margin="10" Text="Static Text"/>
            <Button Margin="10">Any control can be here</Button>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Margin="10" Width="100"  Text="{Binding Text1}"/>
            <TextBox Width="100" Text="{Binding Text2}"/>
        </StackPanel>
    </StackPanel>
</DockPanel>

View.xaml.cs

using System.Windows.Controls;

namespace WpfApplication1
{
    public partial class View : DockPanel
    {
        public View()
        {
            InitializeComponent();
        }
    }
}

ViewModel.cs

namespace WpfApplication1
{
    public  class ViewModel
    {
        public string Text1 { get; set; }
        public string Text2 { get; set; }
    }
}

Use the code below to fill ViewModel with data and print it to file

var path = "newdoc.xps";
FixedDocument fixedDoc = new FixedDocument();
PageContent pageContent = new PageContent();
FixedPage fixedPage = new FixedPage();
fixedPage.Width = 11.69 * 96;
fixedPage.Height = 8.27 * 96;
var pageSize = new System.Windows.Size(11.0 * 96.0, 8.5 * 96.0);
View v = new View();
ViewModel vm = new ViewModel();
vm.Text1 = "MyText1";
vm.Text2 = "MyText2";
v.DataContext = vm;
v.UpdateLayout();
v.Height = pageSize.Height;
v.Width = pageSize.Width;
v.UpdateLayout();
fixedPage.Children.Add(v);
((System.Windows.Markup.IAddChild)pageContent).AddChild(fixedPage);
fixedDoc.Pages.Add(pageContent);
if (File.Exists(path))
    File.Delete(path);
XpsDocument xpsd = new XpsDocument(path, FileAccess.ReadWrite);
XpsDocumentWriter xw = XpsDocument.CreateXpsDocumentWriter(xpsd);
xw.Write(fixedDoc);
xpsd.Close();
lena
  • 1,181
  • 12
  • 36
  • This didn't answer the question fully, but you were the best one that I recievied and it got me going in the right direction. the Text="{Binding propName}"; was one of the keys that I didn't find anywhere else. So, thanks a lot! – Brandon Dec 17 '15 at 18:48
2

How abut printing the top level UIElement instead of the whole window?

For example, if you have a Window:

<Window x:Class="WpfApplicationGrid.Window_Template1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window_Template1" Height="300" Width="300">
    <Grid Name="MainGrid" >
        <!--some content....-->
    </Grid>
</Window>

//-------------------------
public Grid GetMyGrid()
{
   return MainGrid;
}

printing:

 Window_Template1 myWindow = new Window_Template1();
// clone the grid you want to print, so the exception you mentioned won't happen:
Grid clonedGrid = null;
string uiToSave = XamlWriter.Save(myWindow.GetMyGrid());
using (StringReader stringReader = new StringReader(uiToSave))
{
      using (XmlReader xmlReader = XmlReader.Create(stringReader))
      {
          clonedGrid = (Grid)XamlReader.Load(xmlReader);
      }
}

// for some reason you have to close the window even if there was no Show() called
// to properly dispose of it
// otherwise there may be some unpredictable behaviour (maybe I have something odd in my project settings...)
myWindow.Close();

PrintDialog pd = new PrintDialog();
FixedDocument document = new FixedDocument();
document.DocumentPaginator.PageSize = new System.Windows.Size(pd.PrintableAreaWidth, pd.PrintableAreaHeight);

// remember to set sizes again, if they're not set in xaml, for example:
clonedGrid.Height = document.DocumentPaginator.PageSize.Height;
clonedGrid.Width = document.DocumentPaginator.PageSize.Width;
clonedGrid.UpdateLayout();

FixedPage page1 = new FixedPage();
page1.Width = document.DocumentPaginator.PageSize.Width;
page1.Height = document.DocumentPaginator.PageSize.Height;

// this will add the content of the whole grid to the page without problem
page1.Children.Add(clonedGrid);

PageContent page1Content = new PageContent();
((IAddChild)page1Content).AddChild(page1);
document.Pages.Add(page1Content);

// then print...
Arie
  • 5,251
  • 2
  • 33
  • 54