I am just playing around with arrays of rectangles in c# everything is working fine until I want to cycle between "layers" of rectangles, after cycling a few times to application (Silverlight) becomes very slow.
My code is as follows, would it be possible to offer any advice on what could be causing this degradation?
private int worldHeight = 20;
private int currentLayerNo = 0;
private int keypressCounter = 0;
public MainPage()
{
InitializeComponent();
createGrid();
}
private void createGrid()
{
blockCanvas.Children.Clear();
int x = 0, y = 0;
Layer generateWorld = new Layer();
generateWorld.z = worldHeight;
generateWorld.x = 50;
generateWorld.y = 50;
generateWorld.Gen();
Rectangle[,] currentLayer = generateWorld.createLayer(currentLayerNo);
for (int a = 0; a < currentLayer.GetLength(0); a++)
{
for (int b = 0; b < currentLayer.GetLength(1); b++)
{
Rectangle currentBlock = new Rectangle();
currentBlock = currentLayer[a, b];
blockCanvas.Children.Add(currentBlock);
Canvas.SetTop(currentBlock, x);
Canvas.SetLeft(currentBlock, y);
y = y + 32;
}
x = x + 32;
y = 0;
}
currentLayer = null;
this.KeyDown += new KeyEventHandler(onKeyDown);
}
private void onKeyDown(object sender, KeyEventArgs e)
{
//Code here cycles between layers, each time calling the createGrid() method after altering currentLayerNo
}
}
Each time you call createGrid() you are also calling this.KeyDown += new KeyEventHandler(onKeyDown); which adds a new event listener.
On the first call to createGrid() this means that on key down createGrid() will be called once, which will then register again for KeyDown, meaning will now be called twice. On the next KeyDown event createGrid() is called twice and each time it adds itself to the KeyDown event.
Repeat until slow down.
You can put the registration in the MainPage constructor.
Clearing and adding children on Canvas is performance intensive operation. If you want to improve performance, use caching. By this I mean instead of recreating all those rectangles, simply reuse them.
Reposition the ones you need, add extras if you need some or hide those that were created in the previous step, but will not be needed now.
Another reason for performance problem is that you have added the same event handler over and over again whenever you call creategrid. This means that when you press the key for the second time, you will call createGrid 2 times. On the each subsequent key press, you double the number of calls to createGrid.
Try to remove that line first and see if you need to use caching. I needed it in visually heavy WPF application and it worked like a charm.
Related
I have an event handler on a button that runs some lines of code. All the code does is run bubble sort on a list but also does some changes to some rectangles on a canvas. My problem is those changes only appear once it has exited the event handler. A similar question has been asked before however that only does one change instead of multiple in concession UI update in WPF elements event handlers.
This is a similar goal to what I'm trying to achieve.
If you're interested in what exactly the code is:
private void Run_btt_Click(object sender, RoutedEventArgs e)
{
Key hold = new Key();
var converter = new System.Windows.Media.BrushConverter();
double hold2, hold3;
//for inside a for loop(bubble sort)
for (int i = 0; i < Sequence.Count; i++)
{
for (int c = 0; c < Sequence.Count-i-1; c++)
{
//changes the colour of the 2 that are being compared
Sequence[c].shape.Fill =(Brush)converter.ConvertFromString("#FFFF00");
Sequence[c+1].shape.Fill = (Brush)converter.ConvertFromString("#FFFF00");
//pause for a bit so that you can see what the algorithm is doing
Thread.Sleep(delay);
if (Sequence[c].Value > Sequence[c + 1].Value)
{
// swap the 2 rectangles
hold2 = Canvas.GetLeft(Sequence[c].shape);
hold3 = Canvas.GetLeft(Sequence[c + 1].shape);
hold = Sequence[c];
Sequence[c] = Sequence[c + 1];
Sequence[c + 1] = hold;
Canvas.SetLeft(Sequence[c].shape, hold2);
Canvas.SetLeft(Sequence[c + 1].shape, hold3);
}
//set colour back to normal
Sequence[c].shape.Fill = (Brush)converter.ConvertFromString(Sequence[c].Colour);
Sequence[c + 1].shape.Fill = (Brush)converter.ConvertFromString(Sequence[c + 1].Colour);
}
}
}
I'm sorry if this question is too vague or not enough detail but I don't know much about this topic and really just starting out.
Just putting #Clemens comment as answer.
If you give the method the async like:
public void async methodname()
and then once you need to update the UI you add:
await Task.Delay(1);
This will act like break point in your code that will update the UI with the changes you made. The 1 represents how long in millisecond the delay will be.
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.
I am currently using a loop to create a new User Control on my windows form. I want it to add a single instance of this User Control in a new position where Y is incremented by 125 each time.
I'm pretty new with C# and Visual Studio, so with the below code, the first instance is being replicated each time I press the 'add' event. I was just wondering if someone can give some assistance on the best way to store the value of 'y' from this first instance to be passed into the loop the second time? Or if there is any better way to do this.
Thanks in advance!
private void btnAddRow_Click(object sender, EventArgs e)
{
int y = 175;
for (int i = 0; i <= 0; i++)
{
NewSkillRow nSkill = new NewSkillRow();
nSkill.Location = new Point(75, y += 125);
Controls.Add(nSkill);
btnAddRow.Click += new EventHandler(btnAddRow_Click);
}
}
Make your y variable local to the class (you can also initialize it with its default):
private int y = 175;
The event handler is called every time you click the button. So remove the initialization of y from there.
private void btnAddRow_Click(object sender, EventArgs e)
{
var nSkill = new NewSkillRow();
nSkill.Location = new Point(75, y += 125);
Controls.Add(nSkill);
}
Note that the event handler attachment was removed. Reattaching an event handler from within the handler would lead to an increasing number of invokations every time the button is clicked.
The loop is fine, but not necessary: For just one iteration, you can as well just omit it.
The use of the y += 125 is also ok, it relies on the specification that the return value of an assignment operator is the value that has been assigned.
I'm having an eventHandler on mouseEnter in which I have simple linear complexity code, where I add and remove some values to and from List. The thing is, that when I tick the event slowly, everything works, but when I tick it at normal speed ( as a user will do ) or fast, the program skips a part of a code(I can see it visualy as its drawing some objects) causing the List being unstable.
I heard that there isn't way to do this via locking, because all UI calls are processed in a single thread.
I need the code to be executed whole and correctly for the price of some events(mouse enter) being unnoticed.
Here's the handler function:
public static void HandleMouseEnter(object sender)
{
for (int i = 0; i < currentlyPrebuilt.Count; i++)
{
currentlyPrebuilt[i].Image = null;
}
currentlyPrebuilt.Clear();
for (int i = 1; i <= sender.x - startingPoint.x; i++)
{
currentlyPrebuilt.Add(pictures[i]);
pictures[i].Image = "sth.jpg";
}
}
}
It just doesn't set null to all Images in the List if moving too fast with mouse
I am reading Data from a serial port using the DataReceived event. While receiving data, I am making interpolations on the data and showing them in a PictureBox in real time. Everything works fine and I can see the interpolated heatmap data in the PictureBox great and quickly. But the problem is while data is being read, the WinForms application freezes, the form can't even apply the FormPaint event. The DataReceived event gets data, interpolates it, and applies this to the PictureBox. Suddenly the DataReceived event starts and gets data and moves on in the same cycle.
The DataReceived event -> the PictureBox refresh -> interpolate and draw on PictureBox.
Here is some code that might help you to understand the problem:
void seriPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
......
//I am getting data here
.....
//then in order to see the data in picturebox in real time I use the following
this.BeginInvoke((MethodInvoker)delegate { pictureBox1.Refresh(); });
}
Here is the PictureBox Paint event when refresh is triggered:
private void FrameBox_Paint(object sender, PaintEventArgs e)
{
if (activeFrame != null)
{
DrawChart chart = new DrawChart();
chart.ContourFrame(e.Graphics, activeFrame);
}
}
Here is the contourFrame method:
//interpolation code above
for (int i = 0; i < npoints; i++)
{
for (int j = 0; j < npoints; j++)
{
color = AddColor(pts[i, j], zmin, zmax);
aBrush = new SolidBrush(color);
points[0] = new PointF(pts[i, j].X, pts[i, j].Y);
points[1] = new PointF(pts[i + 1, j].X, pts[i + 1, j].Y);
points[2] = new PointF(pts[i + 1, j + 1].X, pts[i + 1, j + 1].Y);
points[3] = new PointF(pts[i, j + 1].X, pts[i, j + 1].Y);
g.FillPolygon(aBrush, points);
aBrush.Dispose();
}
}
I think the g.FillPolygon method takes more time than expected. Because when I comment in it, the form_paint event works too. But for the best results we can't sacrifice the data amount and quality. As I said, right now it gets the data, interpolates and draws quickly, but the only problem, is it locks all other functions in main thread I guess. This might be solved by threading but I am new on threads and a little confused. So I can't go further.
Any ideas how to move forward?
I have changed the this.BeginInvoke to this this.invoke and now it doesnt freeze anymore. you can take a look at the difference between these two on this link What's the difference between Invoke() and BeginInvoke()