Hello !
It's my first topic here. Glad to join the community !
I'm trying to render a WPF document from AX2012 R3.
I made a simple library in C# in order to create and to render an Invoice. The rendered Invoice can be then exported into a .XPS file.
In AX2012 R3, I have created a ManagedHost form that host a System.Windows.Controls.DocumentViewer. The DocumentViewer is populated with a System.Windows.Documents.FixedDocument which contains some System.Windows.Controls.UserControl elements.
Opening the Form from Client works well. My Invoice is correctly rendered probably because the ManagedHost object handles all the threading work.
However, when I try to render the document in Batch (so in CLR session), I've got the following message during the UserControl instanciation :
The calling thread must be STA, because many UI components require this
X++ code :
public static void createInvoice()
{
MyLib.InvoiceDocumentBuilder builder;
System.Windows.Documents.FixedDocument fixedDocument;
System.Double width, height;
;
width = 300;
height = 400;
try
{
builder = new MyLib.InvoiceDocumentBuilder();
fixedDocument = builder.CreateFixedDocument(width, height);
builder.CreateInvoicePage(fixedDocument);
}
catch (Exception::CLRError)
{
// ...
}
}
C# library code :
public class InvoiceDocumentBuilder
{
public InvoiceDocumentBuilder() {}
public FixedDocument CreateFixedDocument(double width, double height)
{
FixedDocument fixedDocument = new FixedDocument();
fixedDocument.DocumentPaginator.PageSize = new System.Windows.Size(width, height);
return fixedDocument;
}
public void CreateInvoicePage(FixedDocument fixedDocument)
{
// ...
new MyCustomUserControl(); // Error here (MyCustomUserControl extends UserControl)
// ...
}
}
The call of CreateInvoicePage() method is done in MTA but the UserControl creation must be done in the UI Thread.
So in C# library, I tried something like :
public void CreateInvoicePage(FixedDocument fixedDocument)
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
{
var t = new Thread(() =>
{
new MyCustomUserControl();
});
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = false;
t.Start();
t.Join();
}
}
No more error...
But if I add a new method :
public void AddPage(UserControl pageControl, FixedDocument fixedDocument)
{
// Create page and set the default page dimensions to match the printer
FixedPage page = new FixedPage();
page.Width = fixedDocument.DocumentPaginator.PageSize.Width;
page.Height = fixedDocument.DocumentPaginator.PageSize.Height;
page.HorizontalAlignment = HorizontalAlignment.Center;
page.VerticalAlignment = VerticalAlignment.Center;
page.Children.Add(pageControl);
// add the page to the document
PageContent pageContent = new PageContent();
(pageContent as IAddChild).AddChild(page);
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
{
fixedDocument.Pages.Add(pageContent);
}));
}
And call this new method into the Thread :
public void CreateInvoicePage(FixedDocument fixedDocument)
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
{
var t = new Thread(() =>
{
Add(new MyCustomUserControl(), fixedDocument);
});
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = false;
t.Start();
t.Join();
}
}
A crash happens on the Dispatcher in the Add() method at the instruction fixedDocument.Pages.Add(pageContent);
Exception Info: System.InvalidOperationException
Stack:
at System.Windows.DependencyObject.InvalidateProperty(System.Windows.DependencyProperty, Boolean)
at System.Windows.FrameworkContentElement.UpdateStyleProperty()
at System.Windows.FrameworkContentElement.OnInitialized(System.EventArgs)
at System.Windows.FrameworkContentElement.AddLogicalChild(System.Object)
at System.Windows.Documents.PageContentCollection.Add(System.Windows.Documents.PageContent)
at SEPPIC_Invoice.InvoiceDocumentBuilder+<>c__DisplayClass1.<AddPage>b__0()
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
at MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(System.Windows.Interop.MSG ByRef)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(System.Windows.Interop.MSG ByRef)
at System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
at System.Windows.Threading.DispatcherOperation.Wait(System.TimeSpan)
at System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherOperation, System.Threading.CancellationToken, System.TimeSpan)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)
at System.Windows.Threading.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority, System.Delegate)
at SEPPIC_Invoice.InvoiceDocumentBuilder.AddPage(System.Windows.Controls.UserControl, System.Windows.Documents.FixedDocument)
at SEPPIC_Invoice.InvoiceDocumentBuilder+<>c__DisplayClass5.<CreateInvoicePage>b__3()
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
at System.Threading.ThreadHelper.ThreadStart()
Any ideas ?
It's my first iteration on integrating WPF into AX, so I have probably missed something...