Reputation: 2185
I'm trying to do the following:
The above should be run from a different thread in the background in order to allow UI reponsiveness.
However simple this may seem, I've been struggling to get a proper working solution. The following issues are relevant:
So my questions are:
See below for code
public void Run()
(
//Create Livechart which is a child of a Grid control
Grid gridChart = Charts.CreateChart();
//Creates a ViewBox control which has the grid as its child
Viewbox viewBox = WrapChart(gridChart,1400,700);
//Creates and saves the image
CreateAndSaveImage(viewBox ,path,name);
)
Below is the function which creates the Viewbox and add the grid as a child
public Viewbox viewBox WrapChart(Grid grid,int width,int height)
{
chart.grid.Width = width;
chart.grid.Height = height;
viewbox.Child = chart.grid;
viewbox.Width = width;
viewbox.Height = height;
viewbox.Measure(new System.Windows.Size(width, height));
viewbox.Arrange(new Rect(0, 0, width, height));
viewbox.UpdateLayout();
}
Function below creates and saves the image
public void CreateAndSaveImage(Viewbox viewbox,string folderPath,string fileName)
{
var x = HelperFunctions.GetImage(viewbox);
System.IO.FileStream stream = System.IO.File.Create(folderPath + fileName);
HelperFunctions.SaveAsPng(x, stream);
stream.Close();
}
The following code renders the viewbox to an image. Note that this is the only code that I could find which waits for the chart to finish loading. I have no idea how it works, but it works 95% of the time. Sometimes a chart still does not finish loading.
public static RenderTargetBitmap GetImage(Viewbox view)
{
using (new HwndSource(new HwndSourceParameters())
{
RootVisual =
(VisualTreeHelper.GetParent(view) == null
? view
: null)
})
{
Size size = new Size(view.ActualWidth, view.ActualHeight);
if (size.IsEmpty)
return null;
int actualWidth = Convert.ToInt32(size.Width);
int requiredWidth = Convert.ToInt32(size.Width * 1);
int actualHeight = Convert.ToInt32(size.Height);
int requiredHeight = Convert.ToInt32(size.Height * 1);
// Flush the dispatcher queue
view.Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() => { }));
var renderBitmap = new RenderTargetBitmap(requiredWidth, requiredHeight,
96d * requiredWidth / actualWidth, 96d * requiredHeight / actualHeight,
PixelFormats.Pbgra32);
DrawingVisual drawingvisual = new DrawingVisual();
using (DrawingContext context = drawingvisual.RenderOpen())
{
context.DrawRectangle(new VisualBrush(view), null, new Rect(new Point(), size));
context.Close();
}
renderBitmap.Render(view);
renderBitmap.Freeze();
return renderBitmap;
}
}
The following code saves the bitmap as a picture to file
public static void SaveAsPng(BitmapSource src, Stream outputStream)
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(src));
encoder.Save(outputStream);
}
The following code is what I use to run the entire thing in a different thread. Note that it is not working, as I get the following error message:
WPF Dispatcher {“The calling thread cannot access this object because a different thread owns it.”}.
Note that if I execute Run() normally (without any separate threads) it works, however sometimes the chart does not render properly (as explained previously).
Thread thread = new Thread(() =>
{
Run();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Upvotes: 2
Views: 3084
Reputation: 56
Try call this line for the chart:
this.chart.Model.Updater.Run(false, true);
This line updates the chart and always is visible when save to image.
Upvotes: 2