how to temporarily pause drawing updates of a realtime data chart - c#

I would like a “Pause” the chart's series updates to do some job (like i have a button when i click it will suspend the chart update and then when I click resume button, it will update all suspended point in series.
I know about
chart1.Series.SuspendUpdates();
but it does not seem to work with me. I use mschart sample -- realtime data (thread safe).
Here is the full code
public partial class RealTimeSample : Form
{
public RealTimeSample()
{
InitializeComponent();
}
private Thread addDataRunner;
private Random rand = new Random();
public delegate void AddDataDelegate();
public AddDataDelegate addDataDel;
private void RealTimeSample_Load(object sender, System.EventArgs e)
{
// create the Adding Data Thread but do not start until start button clicked
ThreadStart addDataThreadStart = new ThreadStart(AddDataThreadLoop);
addDataRunner = new Thread(addDataThreadStart);
// create a delegate for adding data
addDataDel += new AddDataDelegate(AddData);
}
/// Main loop for the thread that adds data to the chart.
/// The main purpose of this function is to Invoke AddData
/// function every 1000ms (1 second).
private void AddDataThreadLoop()
{
while (true)
{
chart1.Invoke(addDataDel);
Thread.Sleep(1000);
}
}
public void AddData()
{
DateTime timeStamp = DateTime.Now;
foreach (Series ptSeries in chart1.Series)
{
AddNewPoint(timeStamp, ptSeries);
}
}
/// The AddNewPoint function is called for each series in the chart when
/// new points need to be added. The new point will be placed at specified
/// X axis (Date/Time) position with a Y value in a range +/- 1 from the previous
/// data point's Y value, and not smaller than zero.
public void AddNewPoint(DateTime timeStamp, System.Windows.Forms.DataVisualization.Charting.Series ptSeries)
{
double newVal = 0;
if (ptSeries.Points.Count > 0)
{
newVal = ptSeries.Points[ptSeries.Points.Count - 1].YValues[0] + ((rand.NextDouble() * 2) - 1);
}
if (newVal < 0)
newVal = 0;
// Add new data point to its series.
chart1.Series.SuspendUpdates();
ptSeries.Points.AddXY(timeStamp.ToOADate(), rand.Next(10, 20));
chart1.Series.SuspendUpdates();
// remove all points from the source series older than 1.5 minutes.
double removeBefore = timeStamp.AddSeconds((double)(90) * (-1)).ToOADate();
//remove oldest values to maintain a constant number of data points
while (ptSeries.Points[0].XValue < removeBefore)
{
ptSeries.Points.RemoveAt(0);
}
chart1.ChartAreas[0].AxisX.Minimum = ptSeries.Points[0].XValue;
chart1.ChartAreas[0].AxisX.Maximum = DateTime.FromOADate(ptSeries.Points[0].XValue).AddMinutes(2).ToOADate();
}
/// Clean up any resources being used.
protected override void Dispose(bool disposing)
{
if ((addDataRunner.ThreadState & ThreadState.Suspended) == ThreadState.Suspended)
{
addDataRunner.Resume();
}
addDataRunner.Abort();
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
private void startTrending_Click_1(object sender, EventArgs e)
{
// Disable all controls on the form
startTrending.Enabled = false;
// and only Enable the Stop button
stopTrending.Enabled = true;
// Predefine the viewing area of the chart
var minValue = DateTime.Now;
var maxValue = minValue.AddSeconds(120);
chart1.ChartAreas[0].AxisX.Minimum = minValue.ToOADate();
chart1.ChartAreas[0].AxisX.Maximum = maxValue.ToOADate();
// Reset number of series in the chart.
chart1.Series.Clear();
// create a line chart series
Series newSeries = new Series("Series1");
newSeries.ChartType = SeriesChartType.Line;
newSeries.BorderWidth = 2;
newSeries.Color = Color.OrangeRed;
newSeries.XValueType = ChartValueType.DateTime;
chart1.Series.Add(newSeries);
// start worker threads.
if (addDataRunner.IsAlive == true)
{
addDataRunner.Resume();
}
else
{
addDataRunner.Start();
}
}
private void stopTrending_Click_1(object sender, EventArgs e)
{
if (addDataRunner.IsAlive == true)
{
addDataRunner.Suspend();
}
// Enable all controls on the form
startTrending.Enabled = true;
// and only Disable the Stop button
stopTrending.Enabled = false;
}
}
EDIT:
I figured out that as long as you set the minmum or the maximum property for the Axis the chart will keep display even if you have used
chart1.Series.SuspendUpdates();
I had to to remove those lines after i call SuspendUpdates() and now i can see the chart series suspended
chart1.ChartAreas[0].AxisX.Minimum = ptSeries.Points[0].XValue;
chart1.ChartAreas[0].AxisX.Maximum = DateTime.FromOADate(ptSeries.Points[0].XValue).AddMinutes(2).ToOADate();

MsChart does support this directly and indeed using Series.SuspendUpdates() is a good way but you need to do it right. (See however the update below for a drawback)
MSDN says this:
A call to the Invalidate method will have no effect after the
SuspendUpdates method is called.
If you call the SuspendUpdates method several times, you will need to
call the ResumeUpdates method an equal number of times.
This would explain why it doesn't work for you: Keeping the calls balanced is crucial. You need to keep track of them yourself as there is no counter you could query. But if you overshoot the ResumeUpdates calls, nothing bad happens, extra calls are simply ignored and the next SuspendUpdates will pause again.
Here is an example screenshot, watch the suspension counter..!
Note that normally adding points will automatically triggger an Invalidate. If you are doing other things, like drawing in a Paint event etc.. you may need to call Chart.Invalidate(), which SuspendUpdates will prevent, until cancelled by the same number of ResumeUpdates..
Alternatively you can also use one of these simple workarounds:
The most straightforward will create the DataPoints via a constructor and then either
use series.Add(theNewPoint) for normal, or..
use someList<DataPoint>.Add(theNewPoint) for paused mode.
When setting to pause mode simply add all points to the series.Points before clearing it. Unfortunately there is no points.AddRange so you will have to use a foreach loop. Maybe chart.SuspendLayout could help with performance.
The other workaround that comes to mind may or may not be suitable: You could play with the xAxis.Maximum and maybe xAxis.Minimum values. By setting them to fixed values you would allow addding points to the right without displaying them. To show the whole set of points you would reset them to double.NaN. This may work for you but it may also interfer with what you have.
Update: As noted by OP, the data are updated when he changes the Minimum and/or Maximum of an Axis. The same effect will show on many other occasions:
Calling chart.AreasRecalculateAxesScale();
Changing the chart's Size
Changing any Axis property like Color or Width..
Changing the LegendText of a Series
and many more..
So I guess the updated data are needed whenever the ChartArea is manipulated and forced to update itself..
So, this may well make the 1st workaround the better because the more robust solution.

Related

auto scroll in an ultragridview for infragistics

I am using an Infragistics UltraGridView in my program. Is it possible to set it to automatically scroll the UltraGridView starting at top to the bottom and then resetting it back to the top? Also the UltraGridView is to be set as AutoRefresh. Any ideas?
You can simply build a tight loop as this
foreach (UltraGridRow row in grid.Rows)
{
row.Activate();
}
But it is unclear what is your purpose for this code. Your user probably will not be able to understand anything of the data while it scrolls on the grid.
Instead if your point is to set a particular row as the first one in the grid area then you should work along the line of this
grid.ActiveRowScrollRegion.FirstRow = grid.Rows[500];
(Assuming that you have more than 500 rows of course)
If you want to slow down the scrolling then you could add a Timer and in the Tick event run the Activate call. In this context you could write a class like this
public class SlowScroller
{
private UltraGridRow current = null;
private UltraGrid grd = null;
private System.Windows.Forms.Timer t = null;
public SlowScroller(UltraGrid grid)
{
grd = grid;
t = new System.Windows.Forms.Timer();
}
public void Start(int interval)
{
t.Interval = interval;
t.Tick += onTick;
t.Start();
}
public void Stop()
{
if (t.Enabled)
t.Stop();
}
private void onTick(object sender, EventArgs e)
{
if(current == null)
current = grd.Rows[0];
else
current = current.GetSibling(SiblingRow.Next);
current.Activate();
}
}
And call it with
SlowScroller ss = new SlowScroller(grid);
ss.Start(500); // Scroll every 500 milliseconds
Note the presence of the Stop method. This is necessary because you don't want this class to continue fire the Tick event even when you discard your form. Thus you need to call the Stop in the Form_Closing event handler

Waiting Dialog white wpf toolkit chart loads the graph

I want to display a large collection of points as a chart (at least 300 000 points), using wpf toolkit chart.
I have the following XAML
<chartingToolkit:Chart Name="chartHistory">
<chartingToolkit:Chart.Axes>
<chartingToolkit:LinearAxis x:Name="horizontalAxis" Orientation="X" Title="Time [s]" ShowGridLines="True"/>
<chartingToolkit:LinearAxis x:Name="verticalAxis" Orientation="Y" Title="Value [mm]" ShowGridLines="True"/>
</chartingToolkit:Chart.Axes>
<chartingToolkit:AreaSeries x:Name="chartSeries" DataPointStyle="{StaticResource chartDataPoint}"
IndependentValuePath="TimeInSeconds"
DependentValuePath="Value">
</chartingToolkit:AreaSeries>
</chartingToolkit:Chart>
And in code behind:
public class PointData
{
public double TimeInSeconds { get; set; }
public double Value { get; set; }
}
private List<PointData> points;
private void Screen_Loaded(object sender, RoutedEventArgs e)
{
// this is a large collection of points (minimum 300 000)
points = LoadPointsFromFile();
// and it takes a lot of time to read from the file and load in the UI
chartSeries.ItemsSource = points;
// additional chart display properties (setting min/max on the axes etc.)
}
So, I have 2 time consuming operations that block my UI. What I want is to display a "please load dialog" while the time consuming operations take place, so that the user knows the application is still doing something.
The time consuming operations are:
reading the points from the file (this operation could be done on a separate thread, but since the next operation (loading the points in the chart) depends on it and is a UI operation, I didn't put it in a separate thread)
loading the points as ItemsSource in the chart - this is a UI operation and should be done on the UI thread. But how can I still make the application responsive since I do not have any control on how the points are displayed - this is the chart's own logic?
So, any ideas? Did you have any similar problems?
Thank you,
Nadia
Actually, what I did was to create a separate thread that displays a different "loading dialog" that shows up while the data is loading. There still is about one second from the moment the dialog is closed until the UI gets fully responsive, but it's still better than looking at a unresponsive UI for 5-10 seconds.
public class PointData
{
public double TimeInSeconds { get; set; }
public double Value { get; set; }
}
#region Wait Dialog Thread
private class MainWindowSize
{
public double Left;
public double Top;
public double Width;
public double Height;
}
private Thread newWindowThread = null;
private void ThreadStartingPoint(object obj)
{
// WaitDialog is a new window from your project - you can place a animation or message inside it
WaitDialog tempWindow = new WaitDialog();
// since we don't have an owner for the window, we need
// to compute the location of the popup dialog, so that
// its centered inside the main window
MainWindowSize wndSize = obj as MainWindowSize;
if (wndSize != null)
{
tempWindow.Left = wndSize.Left + wndSize.Width / 2 - tempWindow.Width / 2;
tempWindow.Top = wndSize.Top + wndSize.Height / 2 - tempWindow.Height / 2;
}
// it's very important not to set the owner of this dialog
// otherwise it won't work in a separate thread
tempWindow.Owner = null;
// it shouldn't be a modal dialog
tempWindow.Show();
tempWindow.Closed += (sender1, e1) => tempWindow.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
}
private void CreateAndStartWaitWindowThread()
{
// create a thread only if you don't have an active one
if (newWindowThread == null)
{
// use ParameterizedThreadStart instead of ThreadStart
// in order to send a parameter to the thread start method
newWindowThread = new Thread(new ParameterizedThreadStart(ThreadStartingPoint));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
// get the properties of the window, in order to compute the location of the new dialog
Window mainWnd = App.CurrentApp.MainWindow;
MainWindowSize threadParams = new MainWindowSize { Left = mainWnd.Left, Top = mainWnd.Top, Width = mainWnd.ActualWidth, Height = mainWnd.ActualHeight };
// start thread with parameters
newWindowThread.Start(threadParams);
}
}
private void AbortAndDeleteWaitWindowThread()
{
// abort a thread only if you have an active one
if (newWindowThread != null)
{
newWindowThread.Abort();
newWindowThread = null;
}
}
#endregion
private List<PointData> points;
private void Screen_Loaded(object sender, RoutedEventArgs e)
{
try
{
// call this before long operation
this.Cursor = Cursors.Wait;
CreateAndStartWaitWindowThread();
// this is a large collection of points (minimum 300 000)
points = LoadPointsFromFile();
// and it takes a lot of time to read from the file and load in the UI
chartSeries.ItemsSource = points;
// additional chart display properties (setting min/max on the axes etc.)
}
catch(Exception ex)
{
// do something with the exception
}
finally
{
// call this after long operation - and make sure it's getting called
// so put it in the finally block - to call it even if an exception is raised
this.Cursor = Cursors.Arrow;
AbortAndDeleteWaitWindowThread();
}
}
Source: http://www.c-sharpcorner.com/uploadfile/suchit_84/creating-wpf-windows-on-dedicated-threads/

Keep track of screen change and screen resolution change in Windows Form Application to change form size

I want to change form size depending on Screen and it's resolution.
What I want is a correct event to track these screen changes as well as screen resolution changes at runtime.
In other words,
If user is using two screens and move application to another screen, that should be tracked and change size accordingly, i.e. reduce size if new screen's resolution is low or increase size if resolution is larger.
Also track screen resolution change on the same screen, and make changes to size accordingly.
I know how to change Form size, get current screen and it's resolution, just need these events to keep track of these changes.
Going over this answer I've decided to improve it and add further information to form a more complete solution.
The Challenge
Tracking which screen a Form is currently being rendered on. This can change if a user drags the form to another monitor or unplugs a monitor. The resolution can change if a user manually drags a window to a different display or changes the resolution directly.
Firstly, tracking form location. We need to hook into a Move event for the form context, fortunately the .Net framework provides such an event, and it is named Control.Move Event.
Secondly, we will need to hook into a screen resolution changed event, we can do this with the SystemEvents.DisplaySettingsChanged event.
And putting it together, I got this:
struct Resolution
{
public int Width;
public int Height;
}
int previous = -1;
int current = -1;
private bool CheckScreenChanged()
{
bool changed = false;
current = GetScreenIndex();
if (current != -1 && previous != -1 && current != previous) // form changed screen.
{
changed = true;
}
previous = current;
return changed;
}
private int GetScreenIndex()
{
return Array.IndexOf(Screen.AllScreens, Screen.FromControl(this));
}
private Resolution GetCurrentResolution()
{
Screen screen = Screen.FromControl(this);
Resolution res = new Resolution();
res.Width = screen.Bounds.Width;
res.Height = screen.Bounds.Height;
return res;
}
private void SetResolutionLabel()
{
Resolution res = GetCurrentResolution();
label2.Text = String.Format("Width: {0}, Height: {1}", res.Width, res.Height);
}
private void ScreenChanged()
{
label1.Text = "Screen " + current.ToString();
}
private void Form_Moved(object sender, System.EventArgs e)
{
bool changed = CheckScreenChanged();
if (changed == true)
{
ScreenChanged();
SetResolutionLabel();
}
}
public void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{
SetResolutionLabel();
}
public void Initialize()
{
this.Move += Form_Moved;
SystemEvents.DisplaySettingsChanged += new
EventHandler(SystemEvents_DisplaySettingsChanged);
previous = GetScreenIndex();
current = GetScreenIndex();
ScreenChanged();
SetResolutionLabel();
}
The code above is tested on a simple form with two labels called label1 and label2, which are updated when the screen the form is on changes or the resolution changes.
An image of this in action on my primary screen/display
And on my secondary screen/display when the form has been dragged to it:

Silverlight UI lag - Crosshair on Graph

I've got a telerik radchart that's displaying a beautiful and incredibly-useful-to-end-user line chart.
One of the requirements for this chart was to have a x-value crosshair on the graph (e.g. when a user hovers at any point over the graph, a horizontal line appears, and where this line intersects the actual graph line, a value is displayed in another area of the screen).
In a previous iteration, I used flotJS to do my graphing & crosshair'ing, worked great, and was blazing fast. In converting this to silverlight, I've seen a tremendous amount of lag, and I'd like to know if anyone has any ideas on improving performance.
I currently expose the chart's MouseEnter/MouseLeave events to hide/show the horizontal line. This is done via a System.Windows.Visibility property sitting in my viewmodel (which my crosshair's visibility property is bound to). I then use the MouseMove method to actually calculate the x position.
Here is the code that runs this (first in my chart view's code behind)
private void rad_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
var plotAreaPanel = this.rad.DefaultView.ChartArea.ChildrenOfType<ClipPanel>().FirstOrDefault();
var position = e.GetPosition(plotAreaPanel);
var x = rad.DefaultView.ChartArea.AxisX.ConvertPhysicalUnitsToData(position.X);
(this.DataContext as ViewModels.DateCountsViewModel).XVolume = x;
}
private void rad_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
(this.DataContext as ViewModels.DateCountsViewModel).XVolumeVisibility = System.Windows.Visibility.Visible;
}
private void rad_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
(this.DataContext as ViewModels.DateCountsViewModel).XVolumeVisibility = System.Windows.Visibility.Collapsed;
}
Then in my viewmodel, in the setter for XVolume (which is being Set in the MouseMove method above), this code:
public double XVolume
{
get { return xVolume; }
set
{
this.xVolume = value;
decimal currentX = (decimal)Math.Round(value, 0);
var vol = this.VolumeCollection.Where(x => x.XValue == currentX).FirstOrDefault() as Models.ChartModel;
this.Volume = (vol == null) ? 0 : (int)vol.YValue;
RaisePropertyChanged("XVolume");
}
}
With heavy usage, the line lags up to several inches behind my pointer. I tried throttling my mouse events outlined here, but it's still really laggy.
[Note regarding link above: It uses the CompositionTarget.Rendering event, which is fired just before a frame is rendered. Therefore, to throttle the effect of the mouse event, we set a flag to indicate that we are waiting for a change to be rendered, then reset this flag just before rendering occurs... not allowing simultaneous rendering]
What can I do to mitigate this lag?

How do I safely populate with data and Refresh() a DataGridView in a multi-threaded application?

My app has a DataGridView object and a List of type MousePos. MousePos is a custom class that holds mouse X,Y coordinates (of type "Point") and a running count of this position. I have a thread (System.Timers.Timer) that raises an event once every second, checks the mouse position, adds and/or updates the count of the mouse position on this List.
I would like to have a similar running thread (again, I think System.Timers.Timer is a good choice) which would again raise an event once a second to automatically Refresh() the DataGridView so that the user can see the data on the screen update. (like TaskManager does.)
Unfortunately, calling the DataGridView.Refresh() method results in VS2005 stopping execution and noting that I've run into a cross-threading situation.
If I'm understanding correctly, I have 3 threads now:
Primary UI thread
MousePos List thread (Timer)
DataGridView Refresh thread (Timer)
To see if I could Refresh() the DataGridView on the primary thread, I added a button to the form which called DataGridView.Refresh(), but this (strangely) didn't do anything. I found a topic which seemed to indicate that if I set DataGridView.DataSource = null and back to my List, that it would refresh the datagrid. And indeed this worked, but only thru the button (which gets handled on the primary thread.)
So this question has turned into a two-parter:
Is setting DataGridView.DataSource to null and back to my List an acceptable way to refresh the datagrid? (It seems inefficient to me...)
How do I safely do this in a multi-threaded environment?
Here's the code I've written so far (C#/.Net 2.0)
public partial class Form1 : Form
{
private static List<MousePos> mousePositionList = new List<MousePos>();
private static System.Timers.Timer mouseCheck = new System.Timers.Timer(1000);
private static System.Timers.Timer refreshWindow = new System.Timers.Timer(1000);
public Form1()
{
InitializeComponent();
mousePositionList.Add(new MousePos()); // ANSWER! Must have at least 1 entry before binding to DataSource
dataGridView1.DataSource = mousePositionList;
mouseCheck.Elapsed += new System.Timers.ElapsedEventHandler(mouseCheck_Elapsed);
mouseCheck.Start();
refreshWindow.Elapsed += new System.Timers.ElapsedEventHandler(refreshWindow_Elapsed);
refreshWindow.Start();
}
public void mouseCheck_Elapsed(object source, EventArgs e)
{
Point mPnt = Control.MousePosition;
MousePos mPos = mousePositionList.Find(ByPoint(mPnt));
if (mPos == null) { mousePositionList.Add(new MousePos(mPnt)); }
else { mPos.Count++; }
}
public void refreshWindow_Elapsed(object source, EventArgs e)
{
//dataGridView1.DataSource = null; // Old way
//dataGridView1.DataSource = mousePositionList; // Old way
dataGridView1.Invalidate(); // <= ANSWER!!
}
private static Predicate<MousePos> ByPoint(Point pnt)
{
return delegate(MousePos mPos) { return (mPos.Pnt == pnt); };
}
}
public class MousePos
{
private Point position = new Point();
private int count = 1;
public Point Pnt { get { return position; } }
public int X { get { return position.X; } set { position.X = value; } }
public int Y { get { return position.Y; } set { position.Y = value; } }
public int Count { get { return count; } set { count = value; } }
public MousePos() { }
public MousePos(Point mouse) { position = mouse; }
}
You have to update the grid on the main UI thread, like all the other controls. See control.Invoke or Control.BeginInvoke.
UPDATE! -- I partially figured out the answer to part #1 in the book "Pro .NET 2.0 Windows Forms and Customer Controls in C#"
I had originally thought that Refresh() wasn't doing anything and that I needed to call the Invalidate() method, to tell Windows to repaint my control at it's leisure. (which is usually right away, but if you need a guarantee to repaint it now, then follow up with an immediate call to the Update() method.)
dataGridView1.Invalidate();
But, it turns out that the Refresh() method is merely an alias for:
dataGridView1.Invalidate(true);
dataGridView1.Update(); // <== forces immediate redraw
The only glitch I found with this was that if there was no data in the dataGridView, no amount of invalidating would refresh the control. I had to reassign the datasource. Then it worked fine after that. But only for the amount of rows (or items in my list) -- If new items were added, the dataGridView would be unaware that there were more rows to display.
So it seems that when binding a source of data (List or Table) to the Datasource, the dataGridView counts the items (rows) and then sets this internally and never checks to see if there are new rows/items or rows/items deleted. This is why re-binding the datasource repeatedly was working before.
Now to figure out how to update the number of rows to display in dataGridView without having to re-bind the datasource... fun, fun, fun! :-)
After doing some digging, I think I have my answer to part #2 of my question (aka. safe Multi-threading):
Rather than using System.Timers.Timer, I found that I should be using System.Windows.Forms.Timer instead.
The event occurs such that the method that is used in the Callback automatically happens on the primary thread. No cross-threading issues!
The declaration looks like this:
private static System.Windows.Forms.Timer refreshWindow2;
refreshWindow2 = new Timer();
refreshWindow2.Interval = 1000;
refreshWindow2.Tick += new EventHandler(refreshWindow2_Tick);
refreshWindow2.Start();
And the method is like this:
private void refreshWindow2_Tick(object sender, EventArgs e)
{
dataGridView1.Invalidate();
}
Looks like you have your answer right there!
Just in cawse you're curious about how to do cross thread calls back to ui:
All controls have a Invoke() method (or BEginInvoke()- in case you want to do things asynchronously), this is used to call any method on the control within the context of the main UI thread.
So, if you were going to call your datagridview from another thread you would need to do the following:
public void refreshWindow_Elapsed(object source, EventArgs e)
{
// we use anonymous delgate here as it saves us declaring a named delegate in our class
// however, as c# type inference sometimes need a bit of 'help' we need to cast it
// to an instance of MethodInvoker
dataGridView1.Invoke((MethodInvoker)delegate() { dataGridView1.Invalidate(); });
}

Categories