web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Microsoft Dynamics AX (Archived)

Render a WPF document: "The calling thread must be STA" in CLR

(0) ShareShare
ReportReport
Posted on by 5

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

*This post is locked for comments

I have the same question (0)
  • Martin Dráb Profile Picture
    237,801 Most Valuable Professional on at

    What's the point of trying to use form controls (which are used on client) in batch (that doesn't have any client process and can't execute client-bound code)?

  • yuf Profile Picture
    5 on at

    In fact, I would like to render a FixedDocument into a XPS (or PDF) file, in order to not use SSRS.

    My System.Windows.Documents.FixedDocument has many System.Windows.Documents.PageContent. I guess that adding any System.Windows.Controls element to them would lead to the issue I pointed out.

    So how can I build the FixedDocument in order to be renderable into a file via an AX Batch ? (an automated process to generate WPF Invoices for example)

  • Martin Dráb Profile Picture
    237,801 Most Valuable Professional on at

    But it has nothing to do with ManagedHost, right? You would use this form control for rendering things in forms, while your goal (and problem) seem to be generating a file ("I would like to render a FixedDocument into a XPS").

  • yuf Profile Picture
    5 on at

    Right, it has nothing to do with ManagedHost.

    The ManagedHost was just a case where the rendering works as intended in my example, but you can forget it.

  • Martin Dráb Profile Picture
    237,801 Most Valuable Professional on at

    Let's try to simplify your code sample, so it's easier for others to reproduce your problem.

    For example, what if you remove all the code except of new MyCustomUserControl() and execute it with Synchronous execution mode? I assume it will fail in the same way, so we can ignore all the other code.

    But we don't have your MyCustomUserControl class. What if you use a standard control instead, such as TextBox? If it still fails, we've just made easy for everybody to try it, increasing your chances that somebody will actually do it. And you won't have to deal with all the irrelevant code while testing the scenario.

  • yuf Profile Picture
    5 on at

    So let's consider this sample :

    C# :

    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 System.Windows.Controls.TextBox();
        }
    }



    X++ :

    public class WPFSample extends SysOperationServiceController
    {
        [SysEntryPointAttribute(false)]
        public 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);
    info("Finished"); } catch (Exception::CLRError) { // ... } } public static void main(Args _args) { WPFSample sample = new WPFSample(); sample.parmClassName(classStr(WPFSample)); sample.parmMethodName(methodStr(WPFSample, createInvoice)); sample.parmExecutionMode(SysOperationExecutionMode::Synchronous); sample.startOperation(); } }

    Execute business operations in CIL is enabled.

    Instantiate any UserControl object like TextBox in Synchronous or Batch execution mode will result in "calling thread must be STA" error.

  • Martin Dráb Profile Picture
    237,801 Most Valuable Professional on at

    I see you left a plenty of code there. Does it mean that merely creating an instance of TextBox work without problems and all the code above is necessary to reproduce the problem? It sounds strange.

    By proving that the problem is the same in Synchronous mode, we've just proved that it's not caused by batch processing; debugging synchronous processes is also easier than debugging batch jobs.

    By the way, you don't have to implement a custom service controller just to run something in CIL. You can create a menu item pointing to SysOperationServiceController, with Parameters containing the method to execute (e.g. MyClass.myMethod) and possibly Enum Type Parameter = SysOperationExecutionMode and Enum Parameter = Synchronous. I would think that you'll need only a single line of code in the method to reproduce your problem.

  • yuf Profile Picture
    5 on at

    Thanks for the menu item tip.

    Yes, we need to execute the above code in CLR session in order to reproduce the problem.

    The error message is thrown when we reach the InitializeComponent() call in the TextBox instantiation. It's related to how the Threading is done in CLR session. Usually, it would be possible to deal with it by executing the UI part in an UI Thread, but it may be more complex than that. We have this in AX... I didn't try...

    In fact, I'm curious to know how SSRS render a report to a file from AX (from .NET perspective). The SrsReportRunController handles many cases like "isCLRSession, isRunningOnServer, print to screen, print to file, etc."

  • Martin Dráb Profile Picture
    237,801 Most Valuable Professional on at

    Yes, I understand it's about a CLR session. Can you know confirm that just calling new System.Windows.Controls.TextBox() is sufficient to reproduce the problem and all the remaining code can be ignored?

    I understand the problem with MTA vs. STA, but so far I'm merely trying to help you to get rid of everything irrelevant, so everybody can easily reproduce your problem. I thought it would have been quicker.

    When you print a report to a file, it's done by the SSRS server, not by AX.

  • yuf Profile Picture
    5 on at

    Oops, forgot to include it in my previous answer. Yes, new System.Windows.Control.TextBox() in X++ createInvoice() method is enough to throw the error.

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Responsible AI policies

As AI tools become more common, we’re introducing a Responsible AI Use…

Neeraj Kumar – Community Spotlight

We are honored to recognize Neeraj Kumar as our Community Spotlight honoree for…

Leaderboard > 🔒一 Microsoft Dynamics AX (Archived)

#1
Martin Dráb Profile Picture

Martin Dráb 4 Most Valuable Professional

#1
Priya_K Profile Picture

Priya_K 4

#3
MyDynamicsNAV Profile Picture

MyDynamicsNAV 2

Last 30 days Overall leaderboard

Featured topics

Product updates

Dynamics 365 release plans