Home > Software engineering >  WPF Trying to render a control from code, using Application.CurrentDispatcher still gives System.Inv
WPF Trying to render a control from code, using Application.CurrentDispatcher still gives System.Inv

Time:10-13

I am trying to build a reporting system that builds reports on a scheduled basis with no UI. The controls I am using are Syncfusion Controls and I couldn't do this in a Worker Service (Windows Service) due to no UI thread.

I went down the avenue of building a WPF app which is minimized to the system tray and has no window.

App runs fine for the initial idea and now I am trying to create my first report which has a chart.

This is the code

await System.Windows.Application.Current.Dispatcher.InvokeAsync(() => 
{
     exporter.BuildChart();
});

private void BuildChart()
{
     SfChart chart = new SfChart();
     
     // .
     // Do stuff to build the chart
     // .

     // Need a container to do a final render of the chart before saving to image

     HwndSourceParameters sourceParameters = new HwndSourceParameters();

     sourceParameters.HwndSourceHook = ApplicationMessageFilter;
     sourceParameters.Height = 400;
     sourceParameters.Width = 800; 

     //using (HwndSource source = new HwndSource(sourceParameters))
     //{
         HwndSource source = new HwndSource(sourceParameters)
         source.RootVisual = chart;

         MemoryStream strm = new MemoryStream())
                        
         chart.Save(strm, new JpegBitmapEncoder() { QualityLevel = 100 }); // <------ Error Here
         strm.Position = 0;
     //} 
}

static IntPtr ApplicationMessageFilter(IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        return IntPtr.Zero;
    }

When it runs, on the line indicated as the error I get the following message

System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.

I don't get why I get the error if I am using the Application Dispatcher which should be the UI thread.

I have tried Invoke and BeginInvoke. I even modified the error line to like so

if (chart.Dispatcher.CheckAccess())
{
   chart.Save(MainChartStrm, new JpegBitmapEncoder() { QualityLevel = 100 });
}
else
{
   chart.Dispatcher.BeginInvoke(new Action(() =>
       {
          chart.Save(MainChartStrm, new JpegBitmapEncoder() { QualityLevel = 100 });
       }));
}

Same Error, so then tried using Source.Dispatcher. Still the same.

oh and stacktrace

at System.Windows.Threading.Dispatcher.VerifyAccess() at System.Windows.DependencyObject.GetValue(DependencyProperty dp) at System.Windows.Media.SolidColorBrush.get_Color() at Syncfusion.UI.Xaml.Charts.ColorExtension.GetContrastColor(Brush brush) at Syncfusion.UI.Xaml.Charts.ChartAdornmentInfoBase.UpdateForeground(ChartAdornment adornment) at Syncfusion.UI.Xaml.Charts.ChartAdornmentInfoBase.UpdateLabels() at Syncfusion.UI.Xaml.Charts.ChartAdornmentInfoBase.UpdateElements() at Syncfusion.UI.Xaml.Charts.AdornmentSeries.UpdateOnSeriesBoundChanged(Size size) at Syncfusion.UI.Xaml.Charts.CartesianSeries.UpdateOnSeriesBoundChanged(Size size) at Syncfusion.UI.Xaml.Charts.SfChart.RenderSeries() at Syncfusion.UI.Xaml.Charts.ChartBase.Save(Stream stream, BitmapEncoder imgEncoder)

CodePudding user response:

Whenever you get stuck like me thinking this doesn't make sense, always look elsewhere.

So, the issue to my problem was that the data that I was using to bind to my chart was created before the BuildChart() in my code. The items that make up the data was a class which had a property of type SolidColorBrush. SolidColourBrush has a Dispatcher property which, in this case, was set to the MTA background thread that created it.

The STA thread that was running to build the chart obviously didn't have access to this property, hence the error.

  • Related