SaveToStream in code behind without window

A forum dedicated to WPF version of LightningChart Ultimate.

Moderator: Queue Moderators

Post Reply
Salto
Posts: 27
Joined: Mon Jan 13, 2014 5:43 pm

SaveToStream in code behind without window

Post by Salto » Thu Jul 24, 2014 12:06 pm

Hello

I need to create a picture for a report on a server where my application is running as service. How can i do this? I've tried, but the chart throws an exception and when i raise the loaded event the chart hangs in the SaveToStream method.
Here is my example code:

Code: Select all

namespace CreatePictureCodeBehind
{
    using System;
    using System.IO;
    using System.Linq;
    using System.Windows;
    using System.Windows.Media;
    using Arction.WPF.LightningChartUltimate;
    using Arction.WPF.LightningChartUltimate.Axes;
    using Arction.WPF.LightningChartUltimate.SeriesXY;

    class Program
    {
        private LightningChartUltimate m_chart;

        public LightningChartUltimate MChart
        {
            get { return m_chart; }
        }

        [STAThread]
        static void Main(string[] args)
        {
            Program program = new Program();
            program.CreateChart();
            program.MChart.RaiseEvent(new RoutedEventArgs(FrameworkElement.LoadedEvent));
            const string picturePng = "C:\\temp\\picture.png";
            if (File.Exists(picturePng))
            {
                File.Delete(picturePng);
            }
            FileStream fileStream = new FileStream(picturePng, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);

            program.MChart.SaveToStream(fileStream, TargetImageFormat.Png, new BitmapAntialiasOptions(), 3507, 2480);
            fileStream.Close();
            fileStream.Dispose();
        }

        /// <summary>
        /// Create chart.
        /// </summary>
        private void CreateChart()
        {
            //Create new chart 
            m_chart = new LightningChartUltimate();

            //Disable rendering, strongly recommended before updating chart properties
            m_chart.BeginUpdate();

            //Chart parent must be set
            
            //Chart title text
            m_chart.Title.Text = "Body weight by groups";

            //Hide legend box
            m_chart.ViewXY.LegendBox.Visible = false;

            //Setup x-axis
            AxisX axisX = m_chart.ViewXY.XAxes[0];
            axisX.SetRange(0, 5);
            axisX.ScrollMode = XAxisScrollMode.None;
            axisX.ValueType = AxisValueType.Number;
            axisX.Title.Text = "Groups";

            //Setup y-axis
            AxisY axisY = m_chart.ViewXY.YAxes[0];
            axisY.Title.Text = "Weight (kg)";

            AddSeries();

            //Allow chart rendering
            m_chart.EndUpdate();
        }

        void AddSeries()
        {
            //Add polygon for box 
            PointLineSeries pointLineSeries = new PointLineSeries(m_chart.ViewXY, m_chart.ViewXY.XAxes[0], m_chart.ViewXY.YAxes[0]);
            
            pointLineSeries.MouseInteraction = false;
            pointLineSeries.Title.Visible = true;
            pointLineSeries.Title.Text = "Curve";
            pointLineSeries.Title.Color = ChartTools.CalcGradient(Colors.Aqua, Colors.DarkBlue, 70);

            SeriesPoint[] seriesPoints = CreatePoints().Select(x => new SeriesPoint(x.X, x.Y)).ToArray();
            pointLineSeries.AddPoints(seriesPoints, true);

            m_chart.ViewXY.PointLineSeries.Add(pointLineSeries);
        }

        static Point[] CreatePoints()
        {
            const int numbersOfPoints = 1000;
            Point[] points = new Point[numbersOfPoints];
            double xValue = 0;
            Random random = new Random();

            for (int index = 0; index < numbersOfPoints; index++, xValue++)
            {
                points[index] = new Point(xValue, random.NextDouble());
            }

            return points;
        }
    }
}
greetings
Raphael

ArctionJari

Re: SaveToStream in code behind without window

Post by ArctionJari » Thu Jul 24, 2014 2:02 pm

In WPF you need to render the chart at least once so that exports can be made. You can test this yourself by creating a Window object, set chart to window's Content property and call window's ShowDialog method and the close the window. All this before calling SaveToStream method. Then it works. I recommend using WinForms instead of WPF if you plan to make exports without showing the chart.

ArctionJari

Re: SaveToStream in code behind without window

Post by ArctionJari » Thu Jul 24, 2014 2:41 pm

By the way, you shouldn't raise Loaded event like that because chart hasn't been laid out or rendered. You are probably getting the exception because of that.

Salto
Posts: 27
Joined: Mon Jan 13, 2014 5:43 pm

Re: SaveToStream in code behind without window

Post by Salto » Thu Jul 24, 2014 2:47 pm

It works when I add the chart to a window, but I do not want create a window to render. Is there an option to render without a window. I've found this:

Code: Select all

Window window = new Window() //make sure the window is invisible
{
     Width = 0,
     Height = 0,
     WindowStyle = WindowStyle.None,
     ShowInTaskbar = false,
     ShowActivated = false
};
but, it will create a hidden window and that is not the solution.

ArctionJari

Re: SaveToStream in code behind without window

Post by ArctionJari » Thu Jul 24, 2014 3:16 pm

Since you are using a zero dimension window set chart's MinWidth and MinHeight properties when you create it. This will be the size of your export file. Default values are 120 and 100 or something like that so your export won't look any good with them.

To close the window automatically (after calling ShowDialog) subscribe to charts Loaded event and close the window in there. This way chart gets laid out before closing the window. Remove your manual Loaded event raising if it's not absolutely necessary.

This should do the trick. :)

Salto
Posts: 27
Joined: Mon Jan 13, 2014 5:43 pm

Re: SaveToStream in code behind without window

Post by Salto » Fri Jul 25, 2014 8:51 am

Hello

Is it not possible without a window?

When I run the code in an console application, it's working without showing the window. But our application run as a service and I've made some test as a service, but i need an STATread an the chart hangs in the method SaveToStream. That's not what I want. :cry:

Code: Select all

public partial class Service1 : ServiceBase
{
        private System.Diagnostics.EventLog eventLog1;
        private int eventId;

        public Service1()
        {
            InitializeComponent();
            eventLog1 = new System.Diagnostics.EventLog();
            if (!System.Diagnostics.EventLog.SourceExists("MySource"))
            {
                System.Diagnostics.EventLog.CreateEventSource(
                    "MySource", "MyNewLog");
            }
            eventLog1.Source = "MySource";
            eventLog1.Log = "MyNewLog";
        }

        private void CreatePicture()
        {
            UserControl1 userControl1 = new UserControl1();
            CurveViewModel measuringPointCurveViewModel = new CurveViewModel();
            userControl1.Curve1.AddPoints(measuringPointCurveViewModel.DataPoints);
            userControl1.DataContext = measuringPointCurveViewModel;


            Window window = new Window() //make sure the window is invisible
            {
                Width = 0,
                Height = 0,
                WindowStyle = WindowStyle.None,
                ShowInTaskbar = false,
                ShowActivated = false
            };
            userControl1.Loaded += (sender, eventArgs) => window.Close();

            StackPanel stackPanel = new StackPanel { Orientation = Orientation.Vertical };
            stackPanel.Children.Add(userControl1);
            window.Content = stackPanel;
            window.Show();

            string picturePng = "C:\\temp\\picture.png";
            if (File.Exists(picturePng))
            {
                File.Delete(picturePng);
            }
            FileStream fileStream = new FileStream(picturePng, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);

            userControl1.ChartControl.SaveToStream(fileStream, TargetImageFormat.Png, new BitmapAntialiasOptions(), 350, 250);//3507, 2480);
            fileStream.Close();
            fileStream.Dispose();
        }

        static Point[] CreatePoints()
        {
            const int numbersOfPoints = 1000;
            Point[] points = new Point[numbersOfPoints];
            double xValue = 0;
            Random random = new Random();

            for (int index = 0; index < numbersOfPoints; index++, xValue++)
            {
                points[index] = new Point(xValue, random.NextDouble());
            }

            return points;
        }

        public class CurveViewModel
        {
            /// <inheritdoc />
            public Point[] DataPoints
            {
                get { return CreatePoints(); }
            }

            /// <inheritdoc />
            public string Title
            {
                get { return "ChartTitle"; }
            }

            /// <inheritdoc />
            public string XLabel
            {
                get { return "XLabel"; }
            }

            /// <inheritdoc />
            public string XUnit
            {
                get { return "XUnit"; }
            }

            /// <inheritdoc />
            public string Y1Label
            {
                get { return "Y1Label"; }
            }

            /// <inheritdoc />
            public string Y1Unit
            {
                get { return "Y1Unit"; }
            }

            /// <inheritdoc />
            public string Y2Label
            {
                get { return "Y2Label"; }
            }

            /// <inheritdoc />
            public string Y2Unit
            {
                get { return "Y2Unit"; }
            }
        }

        protected override void OnStart(string[] args)
        {
            eventLog1.WriteEntry("In OnStart");
            // Set up a timer to trigger every minute.
            System.Timers.Timer timer = new System.Timers.Timer();
            timer.Interval = 60000; // 60 seconds
            timer.Elapsed += new System.Timers.ElapsedEventHandler(this.OnTimer);
            timer.Start();
        }

        public void OnTimer(object sender, System.Timers.ElapsedEventArgs args)
        {
            eventLog1.WriteEntry("PictureStart");
            Thread thread = new Thread(CreatePicture);
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
            if (!thread.Join(5000))
            {
                eventLog1.WriteEntry("PictureFailed");
                Stop();
            }
            eventLog1.WriteEntry("PictureFinish");
            eventLog1.WriteEntry("Monitoring the System", EventLogEntryType.Information, eventId++);
        }

        protected override void OnStop()
        {
            eventLog1.WriteEntry("In onStop.");
        }

        protected override void OnContinue()
        {
            eventLog1.WriteEntry("In OnContinue.");
        } 
}
I've made also some test with the example from the site http://stackoverflow.com/questions/5189 ... g-a-window, but nothing works.

ArctionJari

Re: SaveToStream in code behind without window

Post by ArctionJari » Fri Jul 25, 2014 11:53 am

Not in the WPF version of LightningChart Ultimate. Chart's internal rendering device is created when the chart is laid out (i.e. Loaded event gets triggered) so a window is required.

I just tested exporting the chart using the WinForms version and it worked. I used the code in your first post. The internal rendering device is created differently in the WinForms version than in WPF.

Salto
Posts: 27
Joined: Mon Jan 13, 2014 5:43 pm

Re: SaveToStream in code behind without window

Post by Salto » Mon Jul 28, 2014 2:01 pm

Hello

I've made some new tests:

Example 1: Start the UI thread explicitly http://www.c-sharpcorner.com/uploadfile ... d-threads/

Code: Select all

namespace WindowsService1
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.ServiceProcess;
    using System.Threading;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using Arction.WPF.LightningChartUltimate;
    using Arction.WPF.LightningChartUltimate.Axes;
    using Arction.WPF.LightningChartUltimate.SeriesXY;

    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        }
       
        protected override void OnStart(string[] args)
        {
            // Set up a timer to trigger every minute.
            System.Timers.Timer timer = new System.Timers.Timer();
            timer.Interval = 60000; // 60 seconds
            timer.Elapsed += new System.Timers.ElapsedEventHandler(this.OnTimer);
            timer.Start();
        }
        
        public void OnTimer(object sender, System.Timers.ElapsedEventArgs args)
        {
            Thread thread = new Thread(() =>
            {
                Test test = new Test();
                test.CreateChart();

                Window window = new Window() //make sure the window is invisible
                {
                    Width = 350,
                    Height = 250,
                    WindowStyle = WindowStyle.None,
                    ShowInTaskbar = false,
                    ShowActivated = false
                };
                test.MChart.Loaded += (o, eventArgs) =>
                {
                    string picturePng = "C:\\temp\\picture.png";
                    if (File.Exists(picturePng))
                    {
                        File.Delete(picturePng);
                    }
                    FileStream fileStream = new FileStream(picturePng, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);

                    test.MChart.SaveToStream(fileStream, TargetImageFormat.Png, new BitmapAntialiasOptions(), 350, 250);//3507, 2480);
                    fileStream.Close();
                    fileStream.Dispose();
                    window.Close();
                };

                StackPanel stackPanel = new StackPanel { Orientation = Orientation.Vertical };
                stackPanel.Children.Add(test.MChart);
                window.Content = stackPanel;
                window.Show();
                window.Closed += (sender1, e1) => window.Dispatcher.InvokeShutdown();
                
                System.Windows.Threading.Dispatcher.Run();
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();
            if (!thread.Join(10000))
            {
                Stop();
            }
        }

        protected override void OnStop()
        {
        }

        protected override void OnContinue()
        {
        } 
    }

    class Test
    {
        private LightningChartUltimate m_chart;

        public LightningChartUltimate MChart
        {
            get { return m_chart; }
        }

        /// <summary>
        /// Create chart.
        /// </summary>
        internal void CreateChart()
        {
            //Create new chart 
            m_chart = new LightningChartUltimate("abc");

            //Disable rendering, strongly recommended before updating chart properties
            m_chart.BeginUpdate();

            //Chart parent must be set

            //Chart title text
            m_chart.Title.Text = "Body weight by groups";

            //Hide legend box
            m_chart.ViewXY.LegendBox.Visible = false;

            //Setup x-axis
            AxisX axisX = m_chart.ViewXY.XAxes[0];
            axisX.SetRange(0, 5);
            axisX.ScrollMode = XAxisScrollMode.None;
            axisX.ValueType = AxisValueType.Number;
            axisX.Title.Text = "Groups";

            //Setup y-axis
            AxisY axisY = m_chart.ViewXY.YAxes[0];
            axisY.Title.Text = "Weight (kg)";

            AddSeries();

            //Allow chart rendering
            m_chart.EndUpdate();
        }

        void AddSeries()
        {
            //Add polygon for box 
            PointLineSeries pointLineSeries = new PointLineSeries(m_chart.ViewXY, m_chart.ViewXY.XAxes[0], m_chart.ViewXY.YAxes[0]);

            pointLineSeries.MouseInteraction = false;
            pointLineSeries.Title.Visible = true;
            pointLineSeries.Title.Text = "Curve";
            pointLineSeries.Title.Color = ChartTools.CalcGradient(Colors.Aqua, Colors.DarkBlue, 70);

            SeriesPoint[] seriesPoints = CreatePoints();
            pointLineSeries.AddPoints(seriesPoints, true);

            m_chart.ViewXY.PointLineSeries.Add(pointLineSeries);
        }

        static SeriesPoint[] CreatePoints()
        {
            const int numbersOfPoints = 1000;
            SeriesPoint[] points = new SeriesPoint[numbersOfPoints];
            double xValue = 0;
            Random random = new Random();

            for (int index = 0; index < numbersOfPoints; index++, xValue++)
            {
                points[index] = new SeriesPoint(xValue, random.NextDouble());
            }

            return points;
        }
    }
}
It works on a console application, but not with a service.

Example 2: Create a picture from a usercontrol http://stackoverflow.com/questions/5189 ... g-a-window

Code: Select all

namespace ServiceWithUserControl
{
    using System.IO;
    using System.ServiceProcess;
    using System.Threading;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using ConsoleApplicationWithUserControl;

    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            // Set up a timer to trigger every minute.
            System.Timers.Timer timer = new System.Timers.Timer();
            timer.Interval = 60000; // 60 seconds
            timer.Elapsed += new System.Timers.ElapsedEventHandler(this.OnTimer);
            timer.Start();
        }
        
        public void OnTimer(object sender, System.Timers.ElapsedEventArgs args)
        {
            Thread thread = new Thread(() =>
            {
                UserControl1 userControl1 = new UserControl1();

                userControl1.Measure(new Size(350, 250));
                userControl1.Arrange(new Rect(new Size(350, 250)));

                if (File.Exists("C:\\temp\\picture2.png"))
                {
                    File.Delete("C:\\temp\\picture2.png");
                }

                using (FileStream fs = new FileStream("C:\\temp\\picture2.png", FileMode.Create))
                {
                    RenderTargetBitmap bmp = new RenderTargetBitmap(350, 250, 96, 96, PixelFormats.Pbgra32);
                    bmp.Render(userControl1);
                    BitmapEncoder encoder = new PngBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(bmp));
                    encoder.Save(fs);
                }
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();
            if (!thread.Join(10000))
            {
                Stop();
            }
        }

        protected override void OnStop()
        {
        }

        protected override void OnContinue()
        {
        } 
    }
}
Result:
The console and the service application generate a picture but without the chart.

Console application
pictureConsoleApplication.png
pictureConsoleApplication.png (1.66 KiB) Viewed 42798 times
Windows Service
pictureService.png
pictureService.png (1.18 KiB) Viewed 42798 times
XAML UserControl:

Code: Select all

<UserControl x:Class="ConsoleApplicationWithUserControl.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:lcu="http://www.arction.com/schemas/"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="20" />
        </Grid.RowDefinitions>
        <lcu:LightningChartUltimate Grid.Row="0" x:Name="chart"
             HorizontalAlignment="Stretch"
             VerticalAlignment="Stretch">
        </lcu:LightningChartUltimate>
        <Button Grid.Row="1">
            <TextBlock>dsjdflksjdf</TextBlock>
        </Button>
    </Grid>
</UserControl>
Why does WPF-Controls are visible in the picture and the lightningChart is missing? Are there any methods to execute or properties to set, so that i can create a chart export on a service? I don't want to create a picture of the usercontrol, I want to use the method SaveToStream. I think when it works to create a bitmap on a service, there must be a way to export the lighningchart.

Can you help me?

ArctionTero
Posts: 42
Joined: Thu Mar 28, 2013 9:20 am

Re: SaveToStream in code behind without window

Post by ArctionTero » Wed Jul 30, 2014 11:48 am

Hi Raphael,

Unfortunately DirectX usage is forbidder on windows service. See this.
LightningChart Support Team, TK

ArctionTero
Posts: 42
Joined: Thu Mar 28, 2013 9:20 am

Re: SaveToStream in code behind without window

Post by ArctionTero » Wed Jul 30, 2014 11:58 am

You could try set device type to software:

Code: Select all

chart.ChartRenderOptions.DeviceType = RendererDeviceType.SoftwareOnly;
It will slow down the chart a lot.
LightningChart Support Team, TK

Salto
Posts: 27
Joined: Mon Jan 13, 2014 5:43 pm

Re: SaveToStream in code behind without window

Post by Salto » Wed Jul 30, 2014 1:45 pm

It doesn't work with Device-Type: SoftwareOnly.

Salto
Posts: 27
Joined: Mon Jan 13, 2014 5:43 pm

Re: SaveToStream in code behind without window

Post by Salto » Thu Jul 31, 2014 6:47 am

Is there an option to use the Windows Advanced Rasterization Platform (WARP) with the LightningChart?

User avatar
ArctionPasi
Posts: 1367
Joined: Tue Mar 26, 2013 10:57 pm
Location: Finland
Contact:

Re: SaveToStream in code behind without window

Post by ArctionPasi » Thu Jul 31, 2014 7:09 pm

LightningChart uses low-level DirectX 9.0c rendering and it can't be used with WARP.

One solution probably would be to make a regular WPF or WinForms exe run in the background, that is a slave to the service. Communication between processes must be built, with e.g. socket I/O or named pipes.
LightningChart Support Team, PT

Salto
Posts: 27
Joined: Mon Jan 13, 2014 5:43 pm

Re: SaveToStream in code behind without window

Post by Salto » Tue Aug 05, 2014 3:26 pm

Do you have an example of lightningchart which use directX 10 or higher? Will you increase the version of directX in a later version? I would like to try to run the chart with warp. (http://msdn.microsoft.com/en-us/library/gg615082.aspx) I think that's the last thing which I can try. Run directX on a windows service (Session Id 0) is not possible. The problem with your suggestion to run a second application is, that the user must log on at least once. Our application works as soon as windows is started.

User avatar
ArctionPasi
Posts: 1367
Joined: Tue Mar 26, 2013 10:57 pm
Location: Finland
Contact:

Re: SaveToStream in code behind without window

Post by ArctionPasi » Tue Aug 05, 2014 7:57 pm

No we don't have such example. We can't convert LightningChart to DirectX10 and WARP right now. It'd be more than a year project.
LightningChart Support Team, PT

Salto
Posts: 27
Joined: Mon Jan 13, 2014 5:43 pm

Re: SaveToStream in code behind without window

Post by Salto » Mon Aug 11, 2014 3:57 pm

Is there a plan in the future to update to a higher version of directX?
Is there a plan in the future to have an option to render the chart on a windows service in any kind?

User avatar
ArctionPasi
Posts: 1367
Joined: Tue Mar 26, 2013 10:57 pm
Location: Finland
Contact:

Re: SaveToStream in code behind without window

Post by ArctionPasi » Mon Aug 11, 2014 4:10 pm

We have to add a parallel rendering engine made with DX11-12 sooner or later. I can't say do they work as a service any better, but they should have WARP fallback like DX10, when used with DX10 feature level GPU instruction set.
LightningChart Support Team, PT

Salto
Posts: 27
Joined: Mon Jan 13, 2014 5:43 pm

Re: SaveToStream in code behind without window

Post by Salto » Tue Aug 12, 2014 7:26 am

That sounds good.
microsoft writes:
WARP allows fast rendering in a variety of situations where hardware implementations are unavailable, including:
When running as a service or in a server environment

Do you approximate an idea when it will come?

Post Reply