打印前使用WPF窗口作为视觉模板
本文关键字:视觉 窗口 WPF 打印 | 更新日期: 2023-09-27 18:26:23
我正在制作一个用于打印标签的WPF应用程序。我想设计一个标签模板作为一个WPF窗口。在另一个类中,我将实例化这个"窗口模板",在运行时填充属性并打印标签。打印前无法在屏幕上显示标签,因此无法在此窗口实例上调用.ShowDialog()。这将在以后发挥作用。
在过去的一周里,我一直在做这方面的研究,我发现了两种几乎可以分别做我想做的事情的方法,如果我能把它们结合起来,那就行了,但我遗漏了一部分。
我可以让它发挥作用,但请遵循此处的步骤在WPF第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,而不是手动从模板中删除每个元素并将其添加到固定文档中。如果标签需要更改,我会这样做,这样会更容易。
我找到了这个链接,展示了如何迭代每个元素
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;
}
}
}
}
然而,这似乎只适用于模板本身的代码背后!所以这对我想在课堂上打印没有帮助。I、 当然,可以创建一个全局范围的变量,并以这种方式传递我的UI元素列表,但这越来越不干净了。除此之外,我只能在template类的Windows_Loaded事件中调用此方法。Windows_Loaded只有在调用WindowInstance.ShowDialog();
时才会被调用,这当然会在屏幕上显示模板,这也是我不能拥有的。
有没有办法完成我正在尝试的东西,或者我是不是更适合放弃整个模板的想法,通过后面的代码来定位一切。
更新虽然这里的一些答案为我指明了正确的方向,但我想做的事情需要更多的挖掘。下面的解决方案:
我在Lena的回答和另一篇后的文章的帮助下拼凑出了我的解决方案
要获得一个为自己工作的例子,请使用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);
}
您可以使用您提到的文章的第一部分来实现这一点:WPF打印第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");
}
}
如果我误解了您的需求,我很抱歉,但我希望我的例子能对您有所帮助。这是一个完整而简单的例子——只需创建这些文件并进行测试!
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>
查看.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();
如何打印顶级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...