WPF MVVM background printing databinding issue

2019-05-14 09:46发布

问题:

I am developing a point of sale application using wpf/mvvm. There will be receipts printed in the background during many parts of the transaction lifecycle. I have used other examples to generate and print receipts just fine in the background. I am printing a UserControl in the background, and everything looks great.

I then created the ViewModel for the control, so it can load up a transaction to print. I started with 2 basic textboxes - a Text header "Client Receipt", and top header "Printed On: MM/dd/yy hh:mm tt".

This is how the debugger is showing the lifecycle when I push a print button:

  • Creates new instance of control.
  • Creates new instance of ViewModel
  • Sets the text for the INPC properties
  • Runs a measure on the Control to get the desired pre-print size
  • Loads the print dialog & retrieves the names print queue
  • Scales the control's size to fit the receipt paper (should not have to scale for my currently selected printer, as the ImageArea width is set the same as my UC)
  • Re-measures and Arranges the grid based on the "new" scaled image size
  • calls PrintDialog.PrintVisual - sending the UserControl
  • Printer spits out the receipt - with no databound text
  • Then, my debugger steps into the get { return _HeaderText; } part of the code - after the control has already been printed.

Is there a way to force the databinding of the control before it gets sent to the printer without having to load the control in a panel and make it visible? I want to be able to for example, swipe the credit card and have it print the receipt with the signature line in the background, from a small popup window that asks for the CC Details (or swipe).

Is there a better way to achieve this?

PrintReceipt method:

ClientReceiptView control = new ClientReceiptView(Message);       
System.Windows.Controls.PrintDialog printDlg = new System.Windows.Controls.PrintDialog();
PrintQueue ReceiptPrinter = new LocalPrintServer().GetPrintQueue("CITIZEN CT-S310");
PrintCapabilities PC = ReceiptPrinter.GetPrintCapabilities();
printDlg.PrintQueue = ReceiptPrinter;
control.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
Size PreGridSize = control.DesiredSize;
double Scale = Math.Min(PC.PageImageableArea.ExtentWidth / PreGridSize.Width, PC.PageImageableArea.ExtentHeight / PreGridSize.Height);
control.LayoutTransform = new System.Windows.Media.ScaleTransform(Scale, Scale);
Size PaperSize = new Size(PC.PageImageableArea.ExtentWidth, Math.Min(PC.PageImageableArea.ExtentHeight, PreGridSize.Height));
control.Measure(PaperSize);
Point ptGrid = new Point(PC.PageImageableArea.OriginWidth, PC.PageImageableArea.OriginHeight);
control.Arrange(new Rect(ptGrid, PaperSize));
printDlg.PrintVisual(control, "My App");

And the View Constructor (with arguments)

public ClientReceiptView(string Header)
{
    InitializeComponent();
    vm = new ClientReceiptViewModel(Header);
    this.DataContext = vm;
}

And the ViewModel:

public class ClientReceiptViewModel : ViewModelBase
{
    #region Properties

    private string _HeaderText;

    public string HeaderText
    {
        get { return _HeaderText; }
        set { _HeaderText = value; OnPropertyChanged("HeaderText"); }
    }

    private int _TripID;

    public int TripID
    {
        get { return _TripID; }
        set { _TripID = value; OnPropertyChanged("TripID"); }
    }

    private string _PrintedOn;

    public string PrintedOn
    {
        get { return _PrintedOn; }
        set { _PrintedOn = value; OnPropertyChanged("PrintedOn"); }
    }

    #endregion

    public ClientReceiptViewModel(string Header)
    {
        HeaderText = Header;
        TripID = 2220013;
        PrintedOn = "Printed: " + DateTime.Now.ToString("MM/dd/yy hh:mm tt");
    }
}

Edit: Call Stack from Binding/Creation with a break on OnPropertyChanged("HeaderText"):

Namespace.Main.POS.Receipts.ClientReceiptViewModel.PrintedOn.set(string value) Line 38  C#
Namespace.Main.POS.Receipts.ClientReceiptViewModel.SetData(string Header) Line 79 + 0x4f bytes  C#
Namespace.Main.POS.Receipts.ClientReceiptView.ClientReceiptView(string Header) Line 28 + 0x13 bytes C#
Namespace.Main.POS.Receipts.PrintReceipt.PrintClientReceipt(string Message) Line 12 + 0x1e bytes    C#
Namespace.Main.MainWindowView.button1_Click(object sender, System.Windows.RoutedEventArgs e) Line 23 + 0xb bytes    C#
PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) + 0x78 bytes    
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) + 0xbe bytes   
PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args) + 0x79 bytes  
PresentationCore.dll!System.Windows.UIElement.RaiseEvent(System.Windows.RoutedEventArgs e) + 0x17 bytes 
PresentationFramework.dll!System.Windows.Controls.Primitives.ButtonBase.OnClick() + 0x4b bytes  
PresentationFramework.dll!System.Windows.Controls.Button.OnClick() + 0x4d bytes 
PresentationFramework.dll!System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e) + 0x9e bytes   
PresentationCore.dll!System.Windows.UIElement.OnMouseLeftButtonUpThunk(object sender, System.Windows.Input.MouseButtonEventArgs e) + 0x6c bytes 
PresentationCore.dll!System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(System.Delegate genericHandler, object genericTarget) + 0x31 bytes    
PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target) + 0x29 bytes  
PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) + 0x3e bytes    
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) + 0xbe bytes   
PresentationCore.dll!System.Windows.UIElement.ReRaiseEventAs(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args, System.Windows.RoutedEvent newEvent) + 0x114 bytes    
PresentationCore.dll!System.Windows.UIElement.OnMouseUpThunk(object sender, System.Windows.Input.MouseButtonEventArgs e) + 0xc5 bytes   
PresentationCore.dll!System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(System.Delegate genericHandler, object genericTarget) + 0x31 bytes    
PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target) + 0x29 bytes  
PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) + 0x3e bytes    
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) + 0xbe bytes   
PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args) + 0x79 bytes  
PresentationCore.dll!System.Windows.UIElement.RaiseTrustedEvent(System.Windows.RoutedEventArgs args) + 0x41 bytes   
PresentationCore.dll!System.Windows.UIElement.RaiseEvent(System.Windows.RoutedEventArgs args, bool trusted) + 0x2c bytes    
PresentationCore.dll!System.Windows.Input.InputManager.ProcessStagingArea() + 0x1ff bytes   
PresentationCore.dll!System.Windows.Input.InputManager.ProcessInput(System.Windows.Input.InputEventArgs input) + 0x45 bytes 
PresentationCore.dll!System.Windows.Input.InputProviderSite.ReportInput(System.Windows.Input.InputReport inputReport) + 0x62 bytes  
PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.ReportInput(System.IntPtr hwnd, System.Windows.Input.InputMode mode, int timestamp, System.Windows.Input.RawMouseActions actions, int x, int y, int wheel) + 0x2c2 bytes 
PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.FilterMessage(System.IntPtr hwnd, MS.Internal.Interop.WindowMessage msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x67d bytes 
PresentationCore.dll!System.Windows.Interop.HwndSource.InputFilterMessage(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x75 bytes   
WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0xbe bytes    
WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) + 0x7d bytes    
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0x53 bytes 
WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(object source, System.Delegate method, object args, int numArgs, System.Delegate catchHandler) + 0x42 bytes    
WindowsBase.dll!System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) + 0xb4 bytes    
WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam) + 0x104 bytes    
[Native to Managed Transition]  
[Managed to Native Transition]  
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) + 0xc1 bytes  
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x49 bytes  
PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x5b bytes  
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x74 bytes 
PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x2b bytes 
PresentationFramework.dll!System.Windows.Application.Run() + 0x1b bytes 
Namespace.App.Main() + 0x5e bytes   C#
[Native to Managed Transition]  
[Managed to Native Transition]  
mscorlib.dll!System.AppDomain.nExecuteAssembly(System.Reflection.RuntimeAssembly assembly, string[] args) + 0x9 bytes   
mscorlib.dll!System.Runtime.Hosting.ManifestRunner.Run(bool checkAptModel) + 0x6e bytes 
mscorlib.dll!System.Runtime.Hosting.ManifestRunner.ExecuteAsAssembly() + 0x90 bytes 
mscorlib.dll!System.Runtime.Hosting.ApplicationActivator.CreateInstance(System.ActivationContext activationContext, string[] activationCustomData) + 0x65 bytes 
mscorlib.dll!System.Runtime.Hosting.ApplicationActivator.CreateInstance(System.ActivationContext activationContext) + 0xd bytes 
mscorlib.dll!System.Activator.CreateInstance(System.ActivationContext activationContext) + 0x44 bytes   
Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssemblyDebugInZone() + 0x23 bytes   
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) + 0x63 bytes   
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool ignoreSyncCtx) + 0xb0 bytes    
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x2c bytes    
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() + 0x44 bytes   
[Native to Managed Transition]  

Then after printDlg.PrintVisual(control, "My App"); is issued, the receipt prints out and then the call stack on the Breakpoint for get { return _PrintedOn; }:

Namespace.Main.POS.Receipts.ClientReceiptViewModel.PrintedOn.get() Line 37  C#
[Native to Managed Transition]  
PresentationFramework.dll!MS.Internal.Data.PropertyPathWorker.GetValue(object item, int level) + 0x108 bytes    
PresentationFramework.dll!MS.Internal.Data.PropertyPathWorker.RawValue(int k) + 0x4a bytes  
PresentationFramework.dll!MS.Internal.Data.PropertyPathWorker.RawValue() + 0x12 bytes   
PresentationFramework.dll!MS.Internal.Data.ClrBindingWorker.RawValue() + 0x11 bytes 
PresentationFramework.dll!System.Windows.Data.BindingExpression.TransferValue(object newValue, bool isASubPropertyChange) + 0xa7 bytes  
PresentationFramework.dll!System.Windows.Data.BindingExpression.Activate(object item) + 0x194 bytes 
PresentationFramework.dll!System.Windows.Data.BindingExpression.AttachToContext(System.Windows.Data.BindingExpression.AttachAttempt attempt) + 0x38d bytes  
PresentationFramework.dll!System.Windows.Data.BindingExpression.MS.Internal.Data.IDataBindEngineClient.AttachToContext(bool lastChance) + 0x19 bytes    
PresentationFramework.dll!MS.Internal.Data.DataBindEngine.Task.Run(bool lastChance) + 0x31 bytes    
PresentationFramework.dll!MS.Internal.Data.DataBindEngine.Run(object arg) + 0xb6 bytes  
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0x53 bytes 
WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(object source, System.Delegate method, object args, int numArgs, System.Delegate catchHandler) + 0x42 bytes    
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl() + 0x8d bytes  
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(object state) + 0x38 bytes 
mscorlib.dll!System.Threading.ExecutionContext.runTryCode(object userData) + 0x51 bytes 
[Native to Managed Transition]  
[Managed to Native Transition]  
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x6a bytes    
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool ignoreSyncCtx) + 0x7e bytes    
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x2c bytes    
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke() + 0x68 bytes  
WindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue() + 0x15e bytes    
WindowsBase.dll!System.Windows.Threading.Dispatcher.WndProcHook(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x63 bytes 
WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0xbe bytes    
WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) + 0x7d bytes    
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0x53 bytes 
WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(object source, System.Delegate method, object args, int numArgs, System.Delegate catchHandler) + 0x42 bytes    
WindowsBase.dll!System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) + 0xb4 bytes    
WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam) + 0x104 bytes    
[Native to Managed Transition]  
[Managed to Native Transition]  
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) + 0xc1 bytes  
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x49 bytes  
PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x5b bytes  
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x74 bytes 
PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x2b bytes 
PresentationFramework.dll!System.Windows.Application.Run() + 0x1b bytes 
Namespace.App.Main() + 0x5e bytes   C#
[Native to Managed Transition]  
[Managed to Native Transition]  
mscorlib.dll!System.AppDomain.nExecuteAssembly(System.Reflection.RuntimeAssembly assembly, string[] args) + 0x9 bytes   
mscorlib.dll!System.Runtime.Hosting.ManifestRunner.Run(bool checkAptModel) + 0x6e bytes 
mscorlib.dll!System.Runtime.Hosting.ManifestRunner.ExecuteAsAssembly() + 0x90 bytes 
mscorlib.dll!System.Runtime.Hosting.ApplicationActivator.CreateInstance(System.ActivationContext activationContext, string[] activationCustomData) + 0x65 bytes 
mscorlib.dll!System.Runtime.Hosting.ApplicationActivator.CreateInstance(System.ActivationContext activationContext) + 0xd bytes 
mscorlib.dll!System.Activator.CreateInstance(System.ActivationContext activationContext) + 0x44 bytes   
Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssemblyDebugInZone() + 0x23 bytes   
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) + 0x63 bytes   
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool ignoreSyncCtx) + 0xb0 bytes    
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x2c bytes    
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() + 0x44 bytes   
[Native to Managed Transition]  

回答1:

Use the Dispatcher to queue the print job at a later DispatcherPriority than DataBind. This way all your databinding gets processed before the print job does.

Dispatcher.BeginInvoke(DispatcherPriority.Loaded,
    new Action(delegate() { printDlg.PrintVisual(control, "My App"); } ));


回答2:

I don't know what triggers the initial binding update (can you tell from your call stack?). I'd be interested to find out too.

One way around it might be to set the values in your ViewModel after it has been set as the view context. Maybe the PropertyChanged notification will force the binding to update eg:

public ClientReceiptView(string Header)
{
    InitializeComponent();
    vm = new ClientReceiptViewModel();
    this.DataContext = vm;
    vm.SetHeader(Header);
}

Only guessing though. :/