How to refresh canvas - c#

I am trying to create a visual representation of any sorting algorithm where the data is represented in an int[] array. An example of bubble sort on wikipedia:
My sorting algorithms all raise an event ItemsSwapped when two items in the int[] array are swapped. I am trying to display the data after every event on canvas, this is my code:
// Handler for ItemsSwapped event.
private void Render(object sender, ItemsSwapEventArgs e)
{
canvas.Children.Clear();
int numberOfElements = e.Data.Length;
for (int x = 0; x < numberOfElements; x++)
{
RenderValue(x, e.Data[x]);
}
// Here I should somehow refresh canvas.
}
private void RenderValue(int x, int y)
{
var value = new Ellipse
{
Width = 5,
Height = 5,
Stroke = Brushes.Black,
StrokeThickness = 2,
};
Canvas.SetTop(value, x);
Canvas.SetLeft(value, y);
canvas.Children.Add(value);
}
The problem is, that the canvas doesn't refresh itself, it just displays the final solution after some time. How can I refresh it after every raised event?
Edit - I tried with UpdateLayout, InvalidateMeasure and Dispatcher object, but neither worked.

Maybe you start your sort algorithm on the UI thread, so it won't update until finished. Try sorting in another thread and update the Canvas children using the Dispatcher, by calling Invoke or BeginInvoke.
If your ItemsSwapped handler is called from a separate thread, it may look like this:
private void Render(object sender, ItemsSwapEventArgs e)
{
Dispatcher.Invoke((Action)(() =>
{
canvas.Children.Clear();
int numberOfElements = e.Data.Length;
for (int x = 0; x < numberOfElements; x++)
{
RenderValue(x, e.Data[x]);
}
}));
}

Are you using threads? You must do your work in a separate thread from the main UI. Here is a link to get you started: How to update the GUI from another thread in C#?

Related

C# Accessing form control from different thread

I've been looking at https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls and other Stack Overflow questions for a good long while now to figure out how to use thread-safe methods to access a ListView controls from different threads.
Here's how I want to implement parallel tasks in my program:
I call four different methods in parallel with:
Parallel.Invoke(ProcessLow, ProcessMed, ProcessHigh, ProcessSprint);
Each method searches through the same collection (data[i].Knots) with a for loop and looks for a different range of values within that collection, then if one of the methods finds an appropriate value within the range it's looking for it adds the time and knots (data[i].Time, data[i].Knots) to its respective ListView (the methods write to lstvLow, lstvMed, lstvHigh and lstvSprint respectively). At the moment it's just throwing the exception for non-thread safe code. Also will it break if I have different threads just reading off the same collection? (if so, how can I work around this?)
tldr: Parallel processing is new to me, how do I make a thread-safe call to a windows form control.
And also if you can, point me in the direction of some good reading other than msdn for Parallel tasking.
edit: this is with winforms
To make thread safe call. use Control.Invoke(); method. Assuming you have instance of ListView mylistView; you can write:
object result = mylistView.Invoke(new Action(() => mylistView.DoSomething()));
As I understand, you are new to multitasking. I hope my case study will help you. Create a windows form with next controls:
startButton, stopButton as Button
minTextEdit, maxTextEdit as TextEdit
listListView as ListView
For startButton and stopButton use the appropriate methods: startButton_Click and stopButton_Click.
Full the code below:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Thread0
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private static readonly Random _random = new Random();
private List<int> lst = new List<int>();
private bool isRun = false;
private void startButton_Click(object sender, EventArgs e)
{
isRun = true;
stopButton.Enabled = true;
startButton.Enabled = false;
var tskMain = Task.Run(() =>
{
for (int i = 0; i < 8; i++)
{
var tsk1 = Task.Run(() =>
{
while (true)
{
int max = 0;
int min = Int32.MaxValue;
lock (lst)
{
int num = _random.Next(1, 1000000);
lst.Add(num);
foreach (var x in lst)
{
if (x > max) max = x;
if (min > x) min = x;
}
listListView.BeginInvoke(new Action(() => listListView.Items.Insert(0, num.ToString())));
}
maxTextBox.BeginInvoke(new Action(() => maxTextBox.Text = max.ToString()));
minTextBox.BeginInvoke(new Action(() => minTextBox.Text = min.ToString()));
if (!isRun) break;
Thread.Sleep(100);
}
});
}
});
}
private void stopButton_Click(object sender, EventArgs e)
{
isRun = false;
stopButton.Enabled = false;
startButton.Enabled = true;
}
}
}
When you click on the Start button, the stream create and run tskMain thread, which creates 8 more threads. In each of them, an integer is added to the list of values lst, and the search for the maximum and minimum values. The new number is also added to the listListView, and the maximum and minimum value in the corresponding minTextEdit and maxTextEdit.
For work it is convenient to use lambda expression.
lock(lst) ... blocks work with the list of values. One action per time, so that there are no exceptions.
And the BeginInvoke method allows you to call methods from threads for form elements that are in the main form stream. The Action is used to "transform" the lambda expression into a delegate method.
Inside each thread, the variable IsRun is checked, if its value becomes false, the thread execution stops.
Well, to ensure that everything is not very quickly use .Sleep

Background worker for search and highlight feature

I have a simple while loop that allows me to find text within a textbox, but when I am searching for a word that appears several times in the textbox, it locks up the interface for a while. I'd like to move it into a background worker, but I don't think this can be done because the interface elements (i.e. textbox3.text) are on the main thread. How can I make a background worker when the main interface elements are involved?
I found decent information on the web, but I am having trouble implementing other solutions I have read into my particular situation.
public void button2_Click(object sender, EventArgs e)
{
//Highlight text when search button is clicked
int index = 0;
while (index < dragDropRichTextBox1.Text.LastIndexOf(textBox3.Text))
{
dragDropRichTextBox1.Find(textBox3.Text, index, dragDropRichTextBox1.TextLength, RichTextBoxFinds.None);
dragDropRichTextBox1.SelectionBackColor = Color.Orange;
index = dragDropRichTextBox1.Text.IndexOf(textBox3.Text, index) + 1;
}
}
Thanks for the help.
I guess what you want to do is create sub thread doing the job not to block the UI thread ( pseudo-code, aka not tested ) :
public void button2_Click(object sender, EventArgs e)
{
// Copy text in a non-thread protected string, to be used within the thread sub-routine.
string searchText = textBox3.Text;
string contentText = dragDropRichTextBox1.Text;
// Create thread routine
ThreadPool.QueueUserWorkItem(o => {
// Iterate through all instances of the string.
int index = 0;
while (index < contentText.LastIndexOf(searchText))
{
dragDropRichTextBox1.Invoke((MethodInvoker) delegate {
// Update control within UI thread
//Highlight text when search button is clicked
dragDropRichTextBox1.Find(searchText, index, contentText.Length, RichTextBoxFinds.None);
dragDropRichTextBox1.SelectionBackColor = Color.Orange;
}
// Go to next instance
index = contentText.IndexOf(searchText, index) + 1;
}
});
}
Again, this is untested, but that would give you the idea.
-- EDIT --
You don't need threading at all, doing all the work between a dragDropRichTextBox1.SuspendLayout() and dragDropRichTextBox1.ResumeLayout() is enough.
private void button1_Click(object sender, EventArgs e)
{
// Copy text in a non-thread protected string, to be used within the thread sub-routine.
string searchText = textBox1.Text;
string contentText = richTextBox1.Text;
// Suspend all UI refresh, so time won't be lost after each Find
richTextBox1.SuspendLayout();
// Iterate through all instances of the string.
int index = 0;
while (index < contentText.LastIndexOf(searchText))
{
//Highlight text when search button is clicked
richTextBox1.Find(searchText, index, contentText.Length, RichTextBoxFinds.None);
richTextBox1.SelectionBackColor = Color.Orange;
// Go to next instance
index = contentText.IndexOf(searchText, index) + 1;
}
// Finally, resume UI layout and at once get all selections.
richTextBox1.ResumeLayout();
}
You just need to use invoke when you're manipulating the UI elements in your background thread
https://msdn.microsoft.com/en-us/library/vstudio/ms171728(v=vs.100).aspx
You can call the Invoke Method on the control.
dragDropRichTextBox1.Invoke((MethodInvoker) delegate {
//Your code goes here for whatever you want to do.
}
);
This should fix your problem.

Model as dependency property used within different threads

I do have the following szenario. I created a WPF C# project that follows the MVVM pattern. Furthermore I developed a user control that is some kind of a image drawer. Now I create a Model within the viewmodel and share it with the usercontrol as dependency property. This is done like this:
User control:
public DXModel Model
{
get { return (DXModel)GetValue(ModelProperty); }
set { SetValue(ModelProperty, value); }
}
public static readonly DependencyProperty ModelProperty =
DependencyProperty.Register("Model", typeof(DXModel), typeof(DXControl),
new PropertyMetadata(null, OnChanged));
static void OnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as DXControl).OnChanged();
}
void OnChanged()
{
if (Model != null)
Model.PropertyChanged += new PropertyChangedEventHandler(Model_Changed); // comes from the ui thread...comes only when init model...ok
textTitle.Text = Model.Title;
internalModel = (DXModel)Model;
}
void Model_Changed(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (!receiveTimer.Enabled)
{
receiveTimer.Interval = receiveDelay;
receiveTimer.Start();
refreshTimer.Start();
}
}
So I also get the event when the Model was changed in the viewmodel and I then perform the drawing. Basically I have a list of points which I completely overwrite within the viewmodel several times per second:
ViewModel:
dxModel1.Series[0].Points = list1;
In the User control the points are redrawn also several times per second. To do so, I have to iterate through the points first:
User control:
int length = internalModel.Series[i].Points.Count-1;
float lastX = internalModel.Series[i].Points[0].X;
for (int j = 0; j <= length; j++)
{
float x = ((float)targetImage.Width - 0) / (internalModel.Axes[0].Max - internalModel.Axes[0].Min) * (internalModel.Series[i].Points[j].X- lastX - internalModel.Axes[0].Min);
float y = (0 - (float)targetImage.Height) / (internalModel.Axes[1].Max - internalModel.Axes[1].Min) * (internalModel.Series[i].Points[j].Y - internalModel.Axes[1].Min) + (float)targetImage.Height;
tmpPoints.Add(new SharpDX.Vector2(x, y));
}
What is important, the Model is changed in a worker thread within the viewmodel. The user control is living in the ui thread. The problem I have is that this iteration through the points gives me an exception from time to time. I guess, this is because The points are overwritten in the worker thread at the same time it iterates through the points.
I tried to lock the internalModel in the usercontrol and the dxModel1 within the viewmodel but that did not help me.
Am I right with my approach why it fails and if so, how could I overcome this issue?
if you are working with .net4.5, I think by using async-await in your method should take care of this issue. some link I came across with: http://www.i-programmer.info/programming/c/1514-async-await-and-the-ui-problem.html
I'm not sure that I fully understand your problem as you didn't tell us the Exception details, but if you just want to ensure that your code runs on the UI thread, you can do something like this:
RunOnUiThread((Action)delegate()
{
int length = internalModel.Series[i].Points.Count-1;
float lastX = internalModel.Series[i].Points[0].X;
for (int j = 0; j <= length; j++)
{
float x = ((float)targetImage.Width - 0) / (internalModel.Axes[0].Max - internalModel.Axes[0].Min) * (internalModel.Series[i].Points[j].X- lastX - internalModel.Axes[0].Min);
float y = (0 - (float)targetImage.Height) / (internalModel.Axes[1].Max - internalModel.Axes[1].Min) * (internalModel.Series[i].Points[j].Y - internalModel.Axes[1].Min) + (float)targetImage.Height;
tmpPoints.Add(new SharpDX.Vector2(x, y));
}
});
...
public object RunOnUiThread(Delegate method)
{
return Dispatcher.Invoke(DispatcherPriority.Normal, method);
}

Adding multiple expanders event programmatically wont work

I've got a problem with adding Expanded event to my Expanders. I have expanders on my Window and I want to get the effect when I expand my expander all other will go down. I write functions that let me do this and it work correct. The problem is that I have 96 expanders I don't want add 96 events for Expand and 96 events for Collapse so I thought that I can add this programmatically.
look at the code:
private void InitExpanders()
{
var expanders = GetExpanders(); // List<Expander> - list of expanders
for (int i = 0; i < expanders.Count; i++)
{
if (i % 6 == 1)
{
expanders[i - 1].Expanded += new RoutedEventHandler(delegate(object sender, RoutedEventArgs args)
{
DisableBigExpanders(1); // problem is here!
});
}
}
}
this code works fine but for each expander function parameter will be 1.
Ive tried to add integer and increment it but it wont works.
private void InitExpanders()
{
var expanders = GetExpanders();
int x = 0;
for (int i = 0; i < expanders.Count; i++)
{
if (i % 6 == 1)
{
expanders[i - 1].Expanded += new RoutedEventHandler(delegate(object sender, RoutedEventArgs args)
{
DisableBigExpanders(x);
});
x++;
}
}
}
Thanks for all replies.
I suspect you are finding x in the delegate is always the highest value it reached during the loop. This is due to the way the compiler instantiates the instance of the anonymous method you've defined. It looks at what captured outer variables there are around the delegate and decides whether it requires separate instances or if it can use a single instance. In this case, you have no captured outer variables; therefore, the compiler is permitted to use the same instance of the delegate and therefore the same value of x.
To get around this issue, all you need to do is add a more granular variable just before the delegate and assign it the value of x. This will work:
private void InitExpanders()
{
var expanders = GetExpanders();
int x = 0;
for (int i = 0; i < expanders.Count; i++)
{
if (i % 6 == 1)
{
int y = x++;
expanders[i - 1].Expanded += delegate
{
DisableBigExpanders(y);
};
}
}
}
See here for more info on the theory: http://en.csharp-online.net/ECMA-334%3A_14.5.15.4_Anonymous_method_evaluation

Slide object using For Loop (C#)

I did quite a bit of searching around and didn't find anything of much help.
Is it possible to "slide" or "move" using C#, an object from one Location to another using a simple For loop?
Thank you
I would suggest you rather use a Timer. There are other options, but this will be the simplist if you want to avoid threading issues etc.
Using a straight for loop will require that you pump the message queue using Application.DoEvents() to ensure that windows has the opportunity to actually render the updated control otherwise the for loop would run to completion without updating the UI and the control will appear to jump from the source location to the target location.
Here is a QAD sample for animating a button in the Y direction when clicked. This code assumes you put a timer control on the form called animationTimer.
private void button1_Click(object sender, EventArgs e)
{
if (!animationTimer.Enabled)
{
animationTimer.Interval = 10;
animationTimer.Start();
}
}
private int _animateDirection = 1;
private void animationTimer_Tick(object sender, EventArgs e)
{
button1.Location = new Point(button1.Location.X, button1.Location.Y + _animateDirection);
if (button1.Location.Y == 0 || button1.Location.Y == 100)
{
animationTimer.Stop();
_animateDirection *= -1; // reverse the direction
}
}
Assuming that the object you're talking about is some kind of Control you could just change the Location property of it.
So something like this:
for(int i = 0; i < 100; i++)
{
ctrl.Location.X += i;
}
Should work I think.

Categories

Resources