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.