I'm trying to develop some sort of "animation" for a control in winforms that will run in a new thread than the main one.
So the code I used for the animation is the one I leave you below (a label control that scrolls up pixel by pixel every few seconds until it reaches 0 pixels):
private void LabelAnimation(int amount)
{
this.Invoke((MethodInvoker)delegate
{
int currentX = Label.Location.X;
Label.Text = amount.ToString();
for (int h = 1; h < 7; h++)
{
int subtractHeight = h;
int currentY = Label.Location.Y;
Label.Location = new Point(currentX, (currentY - subtractHeight));
Thread.Sleep(200);
}
});
}
And the method in which the new thread is created:
private void ExecuteAnimation()
{
Thread t = new Thread(() => LabelAnimation(100));
t.Start();
}
The problem is that in itself it works but on a graphic level it sucks, I mean, instead of moving the entire control, the text string remained in the same position while the rectangle of the label moved in the indicated direction, covering its own string.
Use System.Timers.Timer.
It`s recommended to use a timer instead of sleeping the thread.
Here is one way to achieve this:
private System.Timers.Timer timer;
private int amount = 100;
private void ExecuteAnimation()
{
new Thread(() =>
{
// Set DoubleBuffered to true for smoother animation
this.DoubleBuffered = true;
if (timer == null)
{
timer = new System.Timers.Timer();
timer.Interval = 100;
timer.Elapsed += timer_tick;
}
timer.Start();
}).Start();
}
private void timer_tick(object sender, EventArgs e)
{
this.Invoke(new Action(() =>
{
int currentX = label1.Location.X;
label1.Text = amount.ToString();
if (label1.Location.Y > 0)
{
label1.Location = new Point(currentX, label1.Location.Y - 1);
}
}));
}
OUTPUT:
In addition to Jonathans fine answer, you might also be interested in this "Component Animator":
https://www.codeproject.com/Articles/548769/Animator-for-WinForms
If it could be useful to someone else, I finally solved the problem by mixing the #Jonathan Applebaum code and mine, like so:
private void LabelAnimation(int amount, int moveUp)
{
int currentX = 0;
this.Invoke(new Action(() =>
{
currentX = Label.Location.X;
Label.Text = amount.ToString();
}));
for (int h = 1; h < moveUp; h++)
{
this.Invoke(new Action(() =>
{
int currentY = Label.Location.Y;
Label.Location = new Point(currentX, (currentY - h));
}));
Thread.Sleep(150);
}
}
The thread execution always remains the same:
private void ExecuteAnimation()
{
Thread t = new Thread(() => LabelAnimation(100, 7));
t.Start();
}
In this way I make only the gets or sets related to the Label control run on the main thread while everything else, including the Thread.Sleep(150), in the secondary thread so as to avoid the Form getting stuck. In this way everything runs smoothly for me.
Related
I have made a Form that moves across the screen to the left, but all of the components on the Form are blank.
I put the code for the movement in comments and everything was fine, so the problem is in my movement code, but I don't know what the problem is.
System.Threading.Thread thread;
private void Form1_Load(object sender, EventArgs e)
{
thread = new System.Threading.Thread(loop);
thread.Start();
}
void loop()
{
this.BeginInvoke((Action)delegate () {
int x = 0;
int y = 0;
int MoveRate = 1;
Point TopLeft = this.Location;
Point TopRight = new Point (this.Location.X + this.Width, this.Location.Y);
while (true)
{
x = x + MoveRate;
this.Location = new Point(x, 150);
System.Threading.Thread.Sleep(10);
}
});
}
This should make the form move to the left, however the components on the Form are blank.
Let's look at the loop() method:
void loop()
{
this.BeginInvoke((Action)delegate () {
int x = 0;
int y = 0;
int MoveRate = 1;
Point TopLeft = this.Location;
Point TopRight = new Point (this.Location.X + this.Width, this.Location.Y);
while (true)
{
x = x + MoveRate;
this.Location = new Point(x, 150);
System.Threading.Thread.Sleep(10);
}
});
}
This code immediately invokes a delegate back on the main UI thread. The delegate runs a while(true) loop that never exits. As soon as the loop begins to execute, the UI thread is completely hosed with no hope of ever responding to other event messages, including paint events.
Try this instead:
void loop()
{
int x = 0;
int MoveRate = 1;
while(true)
{
x += MoveRate;
this.BeginInvoke((Action)delegate () { this.Location = new Point(x, 150); });
System.Threading.Thread.Sleep(16);
}
}
It's all the same code (except for the stuff that wasn't doing anything), but now arranged so the delegate is invoked inside the loop. The UI thread is only blocked for a brief time, and then control returns back to the loop thread, which will Sleep for a little while before bothering the UI again. The invoke even simple enough I was able to rewrite it as single line.
Notice I also increased the sleep time, because that still gives you 60 frames per second.
An Async variation of the same process, to test something different.
(The main reason why your thread wasn't working as expected has already been explained. If you use a thread and then invoke the UI thread in a close loop, it's more or less like not having your code run in a different thread at all: a Form doesn't have time to update itself or its controls).
This method add a termination to the scrolling procedure, when the Form is scrolled outside the current Screen bounds. When this condition is met, the while loop is exited and the Task ends, moving the Form in the center of the screen.
The Task is started in the Shown event. I think it's more appropriate than the Load event (the Form is ready to be presented, here).
Note that neither this Task or the Thread add any check on the Form.FormClosing event, to cancel the asynchronous proc: if the Form is closed while the scrolling is performed, you will most likely have a exception (the Form has been disposed, thus no more handle).
private async Task Scroller(int ScreenWidth)
{
int x = 0;
int MoveRate = 2;
while (true)
{
x += MoveRate;
this.BeginInvoke(new MethodInvoker(() => { this.Location = new Point(x, 150);}));
await Task.Delay(10);
if (x > ScreenWidth) break;
};
}
private async void Form_Shown(object sender, EventArgs e)
{
int ScreenWidth = Screen.FromHandle(this.Handle).Bounds.Width;
await this.Scroller(ScreenWidth);
this.Location = new Point((ScreenWidth - this.Width) / 2 , 150);
}
I have a weird problem (possibly a threading issue) that has been troubling me. I would like to have a progress bar for a task that I run in Excel/VSTO, that is started by clicking a button on the ribbon.
Since all access to the Excel Object model must occur in the main thread, I show my progress form modally on a separate thread. This works great most of the time, but odd things happen when I switch apps or windows via the Windows task bar. For example, if I am in the middle of a run and Excel is maximized, and I click Chrome on my taskbar to give it focus, then I click back to Excel and click between open Excel workbooks in the taskbar previews, sometimes the UI will partially freeze in the progress bar.
In Excel 2007/2010, I can still see the progress bar being updated but I can't drag the toolbar or click the Cancel button; this is why I say the UI partially freezes. Similar stuff happens in Excel 2013/2016 but I haven't tested as much on them.
If Excel has focus and we don't switch to another window during the execution of the task, then the progress bars work perfectly.
Does anyone have an idea what could be going wrong?
Is there another way I can go about displaying the progress bar in Excel to give consistent and reliable behavior? Note the limitation that the actual task needs to run on the main Excel UI thread and that for a progress form to be interactive and display properly, it needs to be on a secondary UI thread. Because of this, solution like using a BackgroundWorker won't work.
class RunSampleProgressTask
{
private ProgressForm _form;
internal volatile bool CancelPending;
internal volatile AutoResetEvent SignalEvent = new AutoResetEvent(false);
internal RunSampleProgressTask()
{
//create a new workbook
Globals.ThisAddIn.Application.Workbooks.Add(Microsoft.Office.Interop.Excel.XlWBATemplate.xlWBATWorksheet);
int hwnd = GetHwnd();
var thread = new Thread(() => ShowProgressForm(hwnd));
thread.SetApartmentState(ApartmentState.STA);
thread.Priority = ThreadPriority.Highest;
thread.Name = "ProgressFormThread";
thread.IsBackground = true;
thread.Start();
SignalEvent.WaitOne();
//In SDI Excel a newly created workbook won't be shown until the function ends so we use a timer to show the new workbook before running Run()
if (IsSDI)
ExecuteTaskIn(200,Run);
else
Run();
}
internal static void ExecuteTaskIn(int milliseconds, Action action)
{
var timer = new System.Windows.Forms.Timer();
timer.Tick += (s, e) =>
{
((System.Windows.Forms.Timer)s).Stop(); //s is the Timer
action();
};
timer.Interval = milliseconds;
timer.Start();
}
//returns true if it is Excel 2013 or 2016 that use the new Single Document Interface
public static bool IsSDI
{
get
{
string appVersion = Globals.ThisAddIn.Application.Version;
return appVersion.StartsWith("15") || appVersion.StartsWith("16");
}
}
private void ShowProgressForm(int hwnd)
{
_form = new ProgressForm(this) {StartPosition = FormStartPosition.CenterScreen};
_form.ShowInTaskbar = false;
_form.FormBorderStyle = FormBorderStyle.FixedSingle;
_form.ShowDialog(new Win32Window(hwnd));
}
protected void ReportProgress(int percent)
{
if (_form == null || !_form.IsHandleCreated) return;
_form.BeginInvoke(new Action(() => _form.SetProgress(percent)));
}
protected void CloseForm()
{
if (_form == null || !_form.IsHandleCreated) return;
_form.BeginInvoke(new Action(() => _form.Close()));
}
internal static int GetHwnd()
{
if (IsSDI)
{
var window = Globals.ThisAddIn.Application.ActiveWindow;
int hwnd = (int)window.GetType().InvokeMember("Hwnd", BindingFlags.GetProperty, null, window, null); //late binding call to get Window.Hwnd
return hwnd;
}
else
{
return Globals.ThisAddIn.Application.Hwnd;
}
}
private const int BufRowSize = 16384; //must be a factor of RowsPerPage
private const int RowsPerPage = 1048576;
private const int BufColSize = 10;
private const int Repetitions = 80;
internal void Run()
{
ReportProgress(0);
Globals.ThisAddIn.Application.ScreenUpdating = false;
Globals.ThisAddIn.Application.EnableEvents = false;
var buf = new string[BufRowSize, BufColSize];
//fill the buffer with sample data
int cnt = 0;
for (int i = 0; i < BufRowSize; i++)
for (int j = 0; j < BufColSize; j++)
buf[i, j] = "String" + (++cnt);
var workbook = Globals.ThisAddIn.Application.ActiveWorkbook;
var sheet = workbook.ActiveSheet;
int currRow = 1;
for (int i = 0; i < Repetitions; i++)
{
if (CancelPending)
{
CloseForm();
Globals.ThisAddIn.Application.ScreenUpdating = true;
Globals.ThisAddIn.Application.EnableEvents = true;
return;
}
sheet.Range[sheet.Cells[currRow, 1], sheet.Cells[currRow + BufRowSize - 1, BufColSize]].Value2 = buf;
currRow += BufRowSize;
if (currRow > RowsPerPage)
{
currRow = 1;
sheet = workbook.Sheets.Add(Missing.Value, sheet);
}
int percent = 100 * (i + 1) / Repetitions;
ReportProgress(percent);
}
CloseForm();
Globals.ThisAddIn.Application.ScreenUpdating = true;
Globals.ThisAddIn.Application.EnableEvents = true;
}
}
public partial class ProgressForm : Form
{
private ProgressBar _progressBar;
private Label _lblStatus;
private Button _btnCancel;
private RunSampleProgressTask _task;
internal ProgressForm(RunSampleProgressTask task)
{
InitializeComponent();
this.Width = 320;
this.Height = 120;
this.ControlBox = false;
_task = task;
_progressBar = new ProgressBar(){Left=10, Top = 10, Width= 300, Height = 30};
_lblStatus = new Label(){Left = 10, Top = 50};
_btnCancel = new Button(){Text = "Cancel", Left = _lblStatus.Right + 10, Top = 50, Width = 100};
_btnCancel.Click += _btnCancel_Click;
this.Controls.Add(_progressBar);
this.Controls.Add(_lblStatus);
this.Controls.Add(_btnCancel);
this.Shown += ProgressForm_Shown;
}
void ProgressForm_Shown(object sender, EventArgs e)
{
_task.SignalEvent.Set();
}
void _btnCancel_Click(object sender, EventArgs e)
{
_task.CancelPending = true;
_lblStatus.Text = "Cancelling...";
}
internal void SetProgress(int percent)
{
_progressBar.Value = percent;
_lblStatus.Text = percent + "%";
}
}
public class Win32Window : IWin32Window
{
public Win32Window(int hwnd)
{
Handle = new IntPtr(hwnd);
}
public Win32Window(IntPtr handle)
{
Handle = handle;
}
public IntPtr Handle { get; private set; }
}
I am trying to create a car game using pictureboxes and timers
Here is my code
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
PictureBox car = new PictureBox();
Timer t = new Timer();
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
//Right arrow key
if (e.KeyCode.Equals(Keys.Right) && carPlayer.Location.X < 300)
{
int x = carPlayer.Location.X + 5;
int y = carPlayer.Location.Y;
int width = carPlayer.Size.Width;
int height = carPlayer.Size.Height;
carPlayer.SetBounds(x, y, width, height);
}
//Left arrow key
if (e.KeyCode.Equals(Keys.Left) && carPlayer.Location.X > 35)
{
int x = carPlayer.Location.X - 5;
int y = carPlayer.Location.Y;
int width = carPlayer.Size.Width;
int height = carPlayer.Size.Height;
carPlayer.SetBounds(x, y, width, height);
}
if (e.KeyCode.Equals(Keys.Space))
{
spawnCar();
}
}
void spawnCar()
{
string[] cars = { "data/car_red.png", "data/car_blue.png", "data/car_green.png", "data/car_grey.png" };
Random rand = new Random();
car.SizeMode = PictureBoxSizeMode.StretchImage;
car.Image = Image.FromFile(cars[rand.Next(0, 4)]);
car.Visible = true;
if (rand.Next(0,2) == 0)
{
car.SetBounds(100, 10, 50, 85);
}
else
{
car.SetBounds(250, 10, 50, 85);
}
this.Controls.Add(car);
car.BringToFront();
t.Interval = 1;
t.Tick += new EventHandler(t_Tick);
t.Start();
}
private void t_Tick(object sender, EventArgs e)
{
if (car.Bounds.IntersectsWith(carPlayer.Bounds))
{
t.Stop();
car.Image = Image.FromFile("data/car_wreck.png");
carPlayer.Image = Image.FromFile("data/player_wreck.png");
}
if (car.Bounds.Y > 340)
{
t.Stop();
this.Controls.Remove(car);
}
else
{
car.Top++;
}
}
}
http://i.stack.imgur.com/NhCgY.png
Now when I press space once the car appears at the top and moves down slowly and disappears on reaching the bottom but when I press space multiple time the speed of the car gets faster and faster .
Anyone please help me make the car move at same speed everytime it is created.
Thanks
The problem is that you are registering a new Tick event handler with each car spawn, you only want to do this once. However, there isn't an easy way to check if a handler has been assigned yet so I would recommend using a global flag...
//at class level
bool eventSet = false;
//in spawn method
t.Interval = 1;
if(!eventSet)//check if no handler assigned yet
{
t.Tick += new EventHandler(t_Tick);
eventSet = true;
}
t.Start();
Alternatively, you could attempt to remove the handler before assigning...
//in spawn method
t.Interval = 1;
t.Tick -= new EventHandler(t_Tick);//remove previous one if it exists
t.Tick += new EventHandler(t_Tick);
t.Start();
I am using a DispatcherTimer which calls a method to execute in a different thread. I am running a simple program that does not need to really worry about inefficiencies. How do I get the UI thread to wait for the DispatcherTimer to finish while still allowing the DispatcherTimer thread to make changes to the UI?
*I understand this may be semi repetitive however the examples I have seen are case specific. Thanks
private void spinButton_Click(object sender, RoutedEventArgs e)
{
minSelTextBlock.Text = "";
Index = 0;
maxIndex = rnd.Next(40, 60);
DispatcherTimer timer;
timer = new DispatcherTimer(DispatcherPriority.Normal);
timer.Interval = TimeSpan.FromMilliseconds(60);
timer.Tick += new EventHandler(TimerTick);
timer.Start();
selPeople[x].IsCheck = false;
displayCount++;
Index = 0;
maxIndex = rnd.Next(40, 60);
timer = new DispatcherTimer(DispatcherPriority.Normal);
timer.Interval = TimeSpan.FromMilliseconds(60);
timer.Tick += new EventHandler(TimerTick);
timer.Start();
selPeople[x].IsCheck = false;
displayCount++;
displayImage2b.Source = new BitmapImage(new Uri(selPeople[0].ImgPath));
}
private void TimerTick(object sender, EventArgs e)
{
minSelTextBlock.Text = "";
x = rnd.Next(0, selPeople.Count);
while (x == temp)
{
x = rnd.Next(0, selPeople.Count);
}
if (displayCount == 0)
displayImage1a.Source = new BitmapImage(new Uri(selPeople[x].ImgPath));
if (displayCount == 1)
displayImage2a.Source = new BitmapImage(new Uri(selPeople[x].ImgPath));
if (++Index >= maxIndex)
{
((DispatcherTimer)sender).Stop();
}
Index++;
temp = x;
}
Basically I need TimerTick to get the final x value before selPeople[x].IsCheck = false, because the whole point is to remove the item that was just selected from the list.
If you want the caller method to wait for the result of the method which is called, you can use Async/Await that is available in .Net 4.5
You can do something like this:
private async void spin()
{
minSelTextBlock.Text = "";
Index = 0;
maxIndex = rnd.Next(40, 60);
DispatcherTimer timer;
timer = new DispatcherTimer(DispatcherPriority.Normal);
timer.Interval = TimeSpan.FromMilliseconds(60);
timer.Tick += new EventHandler(StartTicker);
timer.Start();
selPeople[x].IsCheck = false;
displayCount++;
Index = 0;
maxIndex = rnd.Next(40, 60);
await Ticker();
selPeople[x].IsCheck = false;
displayCount++;
displayImage2b.Source = new BitmapImage(new Uri(selPeople[0].ImgPath));
}
Private Task<void> StartTicker()
{
Task.Run<void>(()=>Ticker());
}
private void Ticker()
{
while(your condition is true)
{
minSelTextBlock.Text = "";
x = rnd.Next(0, selPeople.Count);
while (x == temp)
{
x = rnd.Next(0, selPeople.Count);
}
if (displayCount == 0)
displayImage1a.Source = new BitmapImage(new Uri(selPeople[x].ImgPath));
if (displayCount == 1)
displayImage2a.Source = new BitmapImage(new Uri(selPeople[x].ImgPath));
if (++Index >= maxIndex)
{
break;
}
Index++;
temp = x;
Thread.Sleep(60);
}
}
Instead of Timer I have used a While Loop and Thread.Sleep() which is the same as your timer.
If you want to learn about async/await I really recommend this
Bye the way I haven't compiled this code. so it might have some syntax or other errors.
So I am making a simple brick breaking game in c#/wpf. I am running into an issue using timers, I feel like it is probably a simple fix but here is whats happening. Whenever t_Elapsed is fired it attempts to call Update() but when it does its like OMG Im not in the right thread so I cant do that sir. How do I invoke the method from the Game from the proper thread? (And yes I know the code is ugly and has magic numbers but I just kinda chugged it out without putting a lot of effort in. And yes I have zero experience programming games)
public partial class Game : Grid
{
public bool running;
public Paddle p;
public Ball b;
Timer t;
public Game()
{
Width = 500;
Height = 400;
t = new Timer(20);
p = new Paddle();
b = new Ball();
for (int i = 15; i < 300; i += 15)
{
for (int j = 15; j < 455; j += 30)
{
Brick br = new Brick();
br.Margin = new Thickness(j, i, j + 30, i + 15);
Children.Add(br);
}
}
Children.Add(p);
Children.Add(b);
p.Focus();
t.AutoReset = true;
t.Start();
t.Elapsed += new ElapsedEventHandler(t_Elapsed);
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
if (running)
{
Update();
}
}
void Update()
{
b.Update(); //Error here when Update is called from t_Elapsed event
}
void Begin()
{
running = true;
b.Initiate();
}
}
You should use the DispatcherTimer object instead, it will ensure that the timer events are published to the correct thread.
Timer elapsed events fire on a thread from the thread pool (http://www.albahari.com/threading/part3.aspx#_Timers) and not on the UI thread. Your best approach is to invoke the control's dispatcher through a call like this:
yourControl.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Normal
, new System.Windows.Threading.DispatcherOperationCallback(delegate
{
// update your control here
return null;
}), null);
The calling thread cannot access this object because a different thread owns it
this.Dispatcher.Invoke((Action)(() =>
{
...// your code here.
}));