Визуализация диаграммы в реальном времени в памяти и сохранить как изображение

Я пытаюсь сделать следующее:

  • Создать живую диаграмму декартовой диаграммы в памяти
  • Добавить диаграмму в сетку
  • Добавить метки к той же сетке
  • Добавить сетку в окно просмотра
  • Визуализация Viewbox как PNG
  • Сохранить PNG на диск

Выше следует запустить из другого потока в фоновом режиме, чтобы позволить отзывчивость пользовательского интерфейса.

Каким бы простым это ни казалось, я изо всех сил пытался найти правильное рабочее решение. Следующие вопросы актуальны:

  • Livechart (который находится внутри Viewbox) требует времени для рендеринга
  • Таким образом, диаграмме нужно дать время для завершения рендеринга, прежде чем пытаться сохранить ее как изображение
  • Я нашел код, который использует HwndSource, однако он не работает все время (работает около 95% времени). Без модификации HwndSource он НИКОГДА не работает (всегда получает диаграмму, на которой ничего нет)
  • Выполнение функции Run() в другом потоке пользовательского интерфейса не работает, так как я получаю следующее сообщение об ошибке: WPF Dispatcher {"Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку".}}

Итак, мои вопросы:

  • Как правильно подождать, пока комбинация Livechart/Grid/ViewBox завершит рендеринг, прежде чем сохранить его как изображение? Может быть, использовать событие Loaded? Обратите внимание, что я пытался заставить его работать, но не могу заставить его работать, так как столкнулся с проблемой "многопоточности".
  • Как я могу запустить весь процесс в другом потоке пользовательского интерфейса?

Смотрите код ниже

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);
)

Ниже приведена функция, которая создает Viewbox и добавляет сетку как дочерний элемент.

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();

}

Функция ниже создает и сохраняет изображение

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();   
}

Следующий код отображает окно просмотра в изображение. Обратите внимание, что это единственный код, который я могу найти, который ожидает окончания загрузки диаграммы. Я понятия не имею, как это работает, но это работает в 95% случаев. Иногда график все еще не заканчивает загрузку.

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;
      }


}

Следующий код сохраняет растровое изображение как изображение в файл

public static void SaveAsPng(BitmapSource src, Stream outputStream)
{
      PngBitmapEncoder encoder = new PngBitmapEncoder();
      encoder.Frames.Add(BitmapFrame.Create(src));

      encoder.Save(outputStream);
}

Следующий код - это то, что я использую для запуска всего процесса в другом потоке. Обратите внимание, что это не работает, так как я получаю следующее сообщение об ошибке:

Диспетчер WPF {"Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку".}.

Обратите внимание, что если я выполняю Run() нормально (без каких-либо отдельных потоков), это работает, однако иногда диаграмма не отображается должным образом (как объяснено ранее).

Thread thread = new Thread(() =>
{
       Run();

       System.Windows.Threading.Dispatcher.Run();

});

       thread.SetApartmentState(ApartmentState.STA);
       thread.Start();

1 ответ

Решение

Попробуйте вызвать эту строку для графика:

    this.chart.Model.Updater.Run(false, true);

Эта строка обновляет график и всегда видна при сохранении в изображение.

Другие вопросы по тегам