Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a WPF window as a visual template before printing

Tags:

c#

templates

wpf

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);

        }
like image 738
Brandon Avatar asked Dec 11 '15 15:12

Brandon


People also ask

How do I display a WPF window?

When a Window is created at run-time using the Window object, it is not visible by default. To make it visible, we can use Show or ShowDialog method. Show method of Window class is responsible for displaying a window.

What is the difference between a page and a window in WPF when you are adding a new file in the Solution Explorer?

Window is the root control that must be used to hold/host other controls (e.g. Button) as container. Page is a control which can be hosted in other container controls like NavigationWindow or Frame. Page control has its own goal to serve like other controls (e.g. Button). Page is to create browser like applications.

How do I print a page in WPF?

If you want to print in WPF, what you actually need to do is, create a FlowDocument object, add your contents that you would like to print to this FlowDocument and send this FlowDocument object to the printer. Before you write your printing code, you must make sure you import these two namespaces in your project.

How do I open a WPF window in full screen?

Just set the WindowState to Maximized , and the WindowStyle to None . Also setting the Window as topmost will make sure no other Window shows up over your window.


1 Answers

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");
    }
}
like image 117
aegar Avatar answered Oct 11 '22 12:10

aegar