【问题标题】:Using a WPF window as a visual template before printing打印前使用 WPF 窗口作为可视模板
【发布时间】:2015-12-11 15:21:12
【问题描述】:

我正在制作一个用于打印标签的 WPF 应用程序。我想将标签模板设计为 WPF 窗口。在另一个类中,我将实例化这个“窗口模板”,在运行时填充属性并打印标签。我无法在打印前在屏幕上显示标签,因此我无法在此窗口实例上调用 .ShowDialog()。这稍后会发挥作用。

过去一周我一直在研究这个问题,我发现了两种几乎可以分别做我想做的事情的方法,如果我可以将它们结合起来,它会起作用,但我错过了一块。

可以让它工作,但按照这里的步骤 Printing in WPF Part 2

这完成了打印文档的基本操作。但是,我将无法使用我的模板,我必须将所有内容都放在后面的代码中。这是一个可行的选择,但我想更多地探索模板的想法。

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

我只是在这里展示我是如何打印条形码的。在实际程序中,我会在标签上添加所有内容并定位它。

在这个类中,我实例化了我的标签,填充了它的属性,并尝试将它添加到 FixedPage 的 Children - 但它返回了这个错误。

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

为了解决这个问题,我可以从模板中删除每个 UI 元素,然后将其添加到我的固定文档中。这似乎不是最干净的解决方案,但它是可能的。

然后我想遍历此列表中的每个 UIElement,而不是手动从模板中删除每个元素并将其添加到我的固定文档中。如果标签需要更改,我会这样做,这会让事情变得更容易。

我找到了显示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;
                }
            }
        }
    }

但是,这似乎只适用于模板本身的代码!所以这对我想要打印的课堂没有帮助。当然,我可以创建一个全局范围的变量并以这种方式传递我的 UIelements 列表,但这越来越不干净了。最重要的是,我只能在模板类的 Windows_Loaded 事件中调用此方法。 Windows_Loaded 只有在WindowInstance.ShowDialog(); 被调用时才会被调用,这当然会在屏幕上显示模板,这也是我不能拥有的。

有没有办法完成我正在尝试的事情,或者我最好放弃整个模板的想法并通过后面的代码定位所有内容。

已更新虽然这里的一些答案为我指明了正确的方向,但我需要更多的挖掘才能完成我想做的事情。解决方案:

我在 Lena 的回答和another post 的帮助下拼凑了我的解决方案,another post

要获得一个适合自己的示例,请使用 Lena 的 View.xaml、View.xaml.cs、ViewModel.cs,然后使用我自己的方法来打印 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);

        }

【问题讨论】:

  • 我使用 MVVM 方法将我的窗口打印到 xps 文件。我创建了一个 xaml 视图,其中包含一些控件和模板表,它可以包含我需要的任意数量的项目。这个 View 的 DataContext 保存了所有的数据。使用 XpsDocumentWriter 和 FixedPage 可以很容易地打印此视图,尤其是如果您不必关心文档的分页。

标签: c# wpf templates


【解决方案1】:

您可以使用您提到的文章的第一部分来实现这一点:WPF Printing Part 1

即使没有显示窗口,PrintVisual 方法似乎也可以工作。

这是一个小例子:

带有单个按钮的主窗口:

MainWindow _barcodeWindow = new MainWindow("A1b2c3D4e5");

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

“模板”窗口:

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

及其背后的代码:

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

【讨论】:

    【解决方案2】:

    如果我误解了您的需求,我很抱歉,但我希望我的示例对您有所帮助。 这是一个完整而简单的示例 - 只需创建这些文件并对其进行测试!

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

    使用下面的代码为 ViewModel 填充数据并将其打印到文件中

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

    【讨论】:

    • 这并没有完全回答问题,但你是我收到的最好的,它让我朝着正确的方向前进。 Text="{Binding propName}";是我在其他任何地方都找不到的钥匙之一。所以,非常感谢!
    【解决方案3】:

    如何打印顶层 UIElement 而不是整个窗口?

    例如,如果你有一个窗口:

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

    印刷:

     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...
    

    【讨论】:

      猜你喜欢
      • 2011-04-09
      • 2014-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-11
      • 1970-01-01
      • 2010-09-30
      相关资源
      最近更新 更多