I am trying to learn .NET programming. As a part of my learning, I tried to make some effects on buttons. It is working... but not as smooth as I imagined! Is there any better way to do this? Thank you in advance!
My need:
There are 3 buttons.
When you hover the mouse over one of them, it expands and when you mouse out from that button, it returns to its initial size.
private void button1_MouseHover(object sender, EventArgs e)
{
button1.BackColor = Color.White;
button1.Width = 130;
button1.BringToFront();
}
private void button1_MouseLeave(object sender, EventArgs e)
{
button1.BackColor = Color.Red;
button1.Width = 75;
}
private void button2_MouseHover(object sender, EventArgs e)
{
button2.BackColor = Color.Gray;
button2.Width = 130;
button2.BringToFront();
}
private void Form1_MouseLeave(object sender, EventArgs e)
{
button2.BackColor = Color.Red;
button2.Width = 75;
}
private void button3_MouseHover(object sender, EventArgs e)
{
button3.BackColor = Color.DimGray;
button3.Width = 130;
button3.BringToFront();
}
private void button3_MouseLeave(object sender, EventArgs e)
{
button3.BackColor = Color.Red;
button3.Width = 75;
}
So first off, you don't want to do the exact same thing 3 times. Create a single method to add the appropriate handlers for a button, and then just write the code once to handle any given button.
Note that you can go into the expand/contract tick handlers and use the percentComplete value to set the height as well, to move the color along a spectrum (this would involve some mathematics of colors to do though) or to alter any other aspect of the button. If you're really motivated to generalize it you could add a parameter to the method of Action<double> that does something to the object based on the given percent progress.
public void AddAnimation(Button button)
{
var expandTimer = new System.Windows.Forms.Timer();
var contractTimer = new System.Windows.Forms.Timer();
expandTimer.Interval = 10;//can adjust to determine the refresh rate
contractTimer.Interval = 10;
DateTime animationStarted = DateTime.Now;
//TODO update as appropriate or make it a parameter
TimeSpan animationDuration = TimeSpan.FromMilliseconds(250);
int initialWidth = 75;
int endWidth = 130;
button.MouseHover += (_, args) =>
{
contractTimer.Stop();
expandTimer.Start();
animationStarted = DateTime.Now;
button.BackColor = Color.DimGray;
};
button.MouseLeave += (_, args) =>
{
expandTimer.Stop();
contractTimer.Start();
animationStarted = DateTime.Now;
button.BackColor = Color.Red;
};
expandTimer.Tick += (_, args) =>
{
double percentComplete = (DateTime.Now - animationStarted).Ticks
/ (double)animationDuration.Ticks;
if (percentComplete >= 1)
{
expandTimer.Stop();
}
else
{
button.Width = (int)(initialWidth +
(endWidth - initialWidth) * percentComplete);
}
};
contractTimer.Tick += (_, args) =>
{
double percentComplete = (DateTime.Now - animationStarted).Ticks
/ (double)animationDuration.Ticks;
if (percentComplete >= 1)
{
contractTimer.Stop();
}
else
{
button.Width = (int)(endWidth -
(endWidth - initialWidth) * percentComplete);
}
};
}
If you are using WinForms, animations are going to be rather painful and you will have to handle them yourself via Timer objects.
If you are getting into .NET and want to make cool-looking applications with animatons and styling, I highly recommend you look at WPF instead. It can do animations very easily though C# or XAML.
While it is still possible in WinForms, it will take far more development time where as those features are built into WPF already (and optimized).
When you modify a controls properties, it takes effect instantaneously. What you desire is something that is usually known as some type of fade or tweening. There might be libraries out there to do this, but if you wanted to write this yourself for fun, you can use a Timer object, and on each tick update the color.
What you would do is set a color as the TargetColor somewhere(this is a variable or property you make up), and then start a timer that ticks maybe every 10 milliseconds. In each tick, you look at the start time, and how long has passed since then. If you want the animation to take place of a full second, then that is 1000 milliseconds. So during each tick, you look at the amount of time that has passed, maybe 200 milliseconds, then divide 200/1000 to get the fraction of time that you have gone into the animation. Then you look at a difference between the Start and Target Color, multiply that difference by the fraction, and add the result to the start color. In other words, 200 milliseconds into an animation that last 1000 milliseconds, means you are 20% into the animation. Thus you want to set the color to be whatever color is 20% from the start color towards the end color.
There's alot you could do to refine this. Perhaps having a subclass Button control that encapsulates the timer and exposes functions/properties to track start/end color, animation transition time, etc. Most animated UI features like this let you specify how long the animation should last, and then it interpolates the inbetween states as it transitions. This is the origin of the term tweening, as it comes from transitioning from one state to another by inbetweening
Related
I am quite new to WP develoment and am currently playing around with animations.
What I am now trying to do is animate the Text property of a TextBlock.
For training purposes, I am developping a simple temperature convertion app and there is a big number on the screen (the temperature) that I would like to gradually increase or decrease until it reaches another value (for instance from 10 to 24 by displaying every number inbetween).
I tried using a storyboard on the text property but as I thought it doesn't work. Then I tried setting the property to each value one by one (for loop) but the view dons't refresh regularly enough and the app blocks until the loop finishes and displays only the last value.
I don't know if what I would like to do is possible (I hope it is it's not that uncommon, right?) and I have no other idea to get the result I would like.
Does anyone has an idea about that?
Thanks :)
You may use a DispatcherTimer, and update the TextBlock's Text property in its Tick event handler:
private readonly DispatcherTimer timer = new DispatcherTimer();
private int currentValue;
private int endValue;
public MainPage()
{
...
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += TimerTick;
}
private void TimerTick(object sender, object e)
{
currentValue++;
textBlock.Text = currentValue.ToString();
if (currentValue >= endValue)
{
timer.Stop();
}
}
private void AnimateText(int start, int end)
{
currentValue = start;
endValue = end;
textBlock.Text = currentValue.ToString();
if (currentValue < endValue)
{
timer.Start();
}
}
I need to store the piano duration with Ticks as so then make the music note show according to that duration (Music players would know).
I'm using an interval of 100, but for some testing I used it at 1000.
The problem is this. When I'm invoking the method (I'm taking the 1000 millisecond interval one) the timer starts.. if I DO NOT manage to get the 1000 milliseconds it shows Duration 0: but then if I do for example 2 seconds, it shows 3 seconds, if I try to press it for another second (a different key) it would show 4 seconds instead of 1.
It's like it keeps on recurring. Same happened with the 100 interval one. It went mad. sometimes 40 sometimes 23 and so on. Any idea how to fix (resetting the timer)
N.B I'm using System.Windows.Forms.Timer as library
part of a method which invokes the methods further below
for (int i = 0; i < 15; i++)
{
WhiteKey wk = new WhiteKey(wKeys[i], wPos[i]-35,0); //create a new white Key with [i] Pitch, at that x position and at y =0 position
wk.MouseDown += onRightClick; //holds the Duration on Right Click
wk.MouseUp += onMouseUp;
wk.Click += new EventHandler(KeyClick); //Go to KeyClick Method whenever a key is pressed
this.panel1.Controls.Add(wk); //Give it control (to play and edit)
}
Methods controlling the time
private void onRightClick(object sender, MouseEventArgs e)
{
wk = sender as WhiteKey;
duration = 0;
t1.Enabled = true;
t1.Tick += timeTick;
t1.Interval = 100;
}
private void timeTick(object sender, EventArgs e)
{
duration++;
}
private void onMouseUp (object sender, MouseEventArgs e)
{
t1.Enabled = false;
String time = "Key: " + pitch + "\nDuration: " +duration ; //Test purposes to see if timer works
MessageBox.Show(time);
}
You are trying to measure time, don't use Timer, use Stopwatch.
You can find C# Stopwatch Exmples at dotnetpearls.com.
In abstract this is what you would want to do is something like this:
private void onRightClick(object sender, MouseEventArgs e)
{
stopwatch.Reset();
stopwatch.Start();
}
private void onMouseUp (object sender, MouseEventArgs e)
{
stopwatch.Stop();
String msg = "Duration in seconds: " + (stopwatch.ElapsedMilliseconds / 1000.0).ToString("0.00");
MessageBox.Show(msg);
}
Note: you may want to change the units or the string format.
Notes on using timer:
1) System.Windows.Forms.Timer uses the message loop of your window, this means that it may get delayed because the window is busy handling other events (such as click). For a better behaviour use System.Threading.Timer.
2) If using System.Windows.Forms.Timer don't set the Tick event handler each click. The event handler will execute once for each time you add it.
That is:
private void onRightClick(object sender, MouseEventArgs e)
{
wk = sender as WhiteKey;
duration = 0;
t1.Enabled = true;
//t1.Tick += timeTick; you should add this only once not each click
t1.Interval = 100;
}
3) If you use System.Threading.Timer you may want to make the variable duration volatile.
t1.Tick += timeTick;
By the way in your code sample you subscribe to the 'Tick' timer event each time on Right mouse click.
So if you click 2 times the
private void timeTick(object sender, EventArgs e)
method will be called twice, and 'duration++' will be executed twice. Your event subscription code should be executed only once for the timer.
P.S. If you need to measure duration, Timer is not the best way to do it.
I'm only just getting into GUIs, so please forgive me if I've missed something obvious. Google hasn't helped much I'm afraid.
My base objective is to have Marquee style text scrolling across the screen a set number of times. I've achieved the scrolling aspect utilizing a timer which scrolls a label named "My text here" across the screen (taken from this tutorial: http://www.youtube.com/watch?v=-y-Z0i-DeAs Note: I don't speak the language he does), but I've been unable to get the thing to stop. I'm open to using a different way to achieve the scrolling, but this is the only example I've found so far that worked well with my current level of knowledge of GUIs (basically dragging and dropping).
private void timer_Tick(object sender, EventArgs e)
{
this.Refresh();
labelTesting.Left += 5;
if (labelTesting.Left >= this.Width)
{
labelTesting.Left = labelTesting.Width * -1;
}
}
My best guess is that the timer is simply starting the whole process over with each tick. I've tried countering this by having it return after running i times and telling the label what to display, but that's not working. Nor can I seem to find a way to tell the thing to stop.
http://www.dotnetperls.com/timer has an example where a timer is set to run for a given amount of time, but I do not know how to implement that when messing around with GUIs. What would be the best way to implement the feature I desire? Any insight or suggestions would be greatly appreciated.
EDIT: Based on suggestions in the answers and comments I have edited the code to run for what should be 30 seconds before setting itself to a given position. However the text no longer scrolls. I'm going to keep working at it, but more input would be appreciated.
private void timer_Tick(object sender, EventArgs e)
{
var time = DateTime.Now;
if(time < DateTime.Now.AddSeconds(-30)) // you decide when to stop scrolling
{
Timer timer = (Timer)sender;
timer.Stop();
labelTesting.Left = 0; // or wherever it should be at the end of the scrolling
}
this.Refresh();
labelTesting.Left += 5;
if (labelTesting.Left >= this.Width)
{
labelTesting.Left = labelTesting.Width * -1;
}
}
You need to stop the timer:
private void timer_Tick(object sender, EventArgs e)
{
if (someConditionToEndScroll) // you decide when to stop scrolling
{
Timer timer = (Timer) sender;
timer.Stop();
labelTesting.Left = 0; // or wherever it should be at the end of the scrolling
}
this.Refresh();
labelTesting.Left += 5;
if (labelTesting.Left >= this.Width)
{
labelTesting.Left = labelTesting.Width * -1;
}
}
I have a WinForms app that displays an animated gif in the simplest possible way - there is a PictureBox that loads the .gif directly.
The code generated by the WinForms designer looks like this:
//
// pictureBoxHomer
//
this.pictureBoxHomer.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None;
this.pictureBoxHomer.Dock = System.Windows.Forms.DockStyle.Fill;
this.pictureBoxHomer.Image = ((System.Drawing.Image)(resources.GetObject("pictureBoxHomer.Image")));
this.pictureBoxHomer.Location = new System.Drawing.Point(3, 3);
this.pictureBoxHomer.Name = "pictureBoxHomer";
this.pictureBoxHomer.Size = new System.Drawing.Size(905, 321);
this.pictureBoxHomer.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage;
this.pictureBoxHomer.TabIndex = 0;
this.pictureBoxHomer.TabStop = false;
The image is, of course, this: http://media.tumblr.com/tumblr_m1di1xvwTe1qz97bf.gif
Problem: while this animated gif displays wondrously in the browser, it is running way too fast in the WinForms app, which is not as happy as needed. So:
Question: is there a way to slow down an animated gif in a WinForms app?
I believe the answer is rather image-related than C#. If you edit that specific image in a tool like GIMP and take a look at the layers, you'll see it's a composition of 10 layers (frames) but no "delay time" between them is set - it has (0ms) in layer's attribute. You can edit layer's attribute and change it by right-clicking on it and selecting that option in menu. Of course, at the end you have to export your new image and save it as a GIF, selecting "animated" in options.
I believe in this case (when no delay time between frames is specified) web browser and C# PicutureBox force their own,different, default values. So, if you put a delay let say 100ms, like described here in step 3, you'll make the animation slow down.
For future reference, it is possible to override the delay time of a GIF in a picture box. Here is a rough example:
public partial class Form1 : Form
{
private FrameDimension dimension;
private int frameCount;
private int indexToPaint;
private Timer timer = new Timer();
public Form1()
{
InitializeComponent();
dimension = new FrameDimension(this.pictureBox1.Image.FrameDimensionsList[0]);
frameCount = this.pictureBox1.Image.GetFrameCount(dimension);
this.pictureBox1.Paint += new PaintEventHandler(pictureBox1_Paint);
timer.Interval = 100;
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
indexToPaint++;
if(indexToPaint >= frameCount)
{
indexToPaint = 0;
}
}
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
this.pictureBox1.Image.SelectActiveFrame(dimension, indexToPaint);
e.Graphics.DrawImage(this.pictureBox1.Image, Point.Empty);
}
}
This is in Win forms
On button click I want to change the color of the button temporarily say only for 1 second and then the button color should get back to the previous color. I used lambda expression and timer for this.
private void btn_Read_Click(object sender, EventArgs e)
{
System.Windows.Forms.Timer t1 = new System.Windows.Forms.Timer();
t1.Interval = 1000;
t1.Tick += (src, ee) =>
{
btn_Read.BackColor = Color.Transparent; t1.Stop();
};
t1.Start();
btn_Read.BackColor = Color.YellowGreen;
lvwMessages.Items.Clear();
string strcommand = "AT+CMGL=\"ALL\"";
objShortMessageCollection = ReadSMS(strcommand); // Line wher I am reading messages from the port
foreach (ShortMessage msg in objShortMessageCollection)
{
ListViewItem item = new ListViewItem(new string[] { msg.Sender, msg.Message, msg.Sent, msg.Index });
item.Tag = msg;
lvwMessages.Items.Insert(0, item);
}
if (lvwMessages.Items.Count == 0)
{
status_other.Visible = true;
status_other.Text = "No messages";
lbl_total.Text = "Total: 0";
System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
timer1.Interval = 2000;
timer1.Tick += (source, ex) => { status_other.Visible = false; timer1.Stop(); };
timer1.Start();
}
else
{
status_other.Visible = false;
chk_selectmsg.Visible = true;
btn_delete.Visible = true;
lbl_total.Text = "Total: " + lvwMessages.Items.Count.ToString(); ;
}
}
Later in this code I am reading the data from serial port, displaying it, etc. The problem is that the button color doesnot change as I click the button. It takes some time and does not give the desired feel I want. Some times doesn't even change the color. What could be the reason?
A simple solution would be using mouse hover event and mouse leave event
Use it this way:
private void btn_Read_MouseHover(object sender, EventArgs e)
{
btn_Read.BackColor = Color.AliceBlue;
}
private void btn_Read_MouseLeave(object sender, EventArgs e)
{
btn_Read.BackColor = Color.AntiqueWhite;
}
This does'nt require any change in your code and definitely will give you the functionality. See if it helps!
you should avoid having work-intensive code on the UI thread
to get the desired effect, sepperate the code for the UI from the code that does the work ...
when the button is clicked, change its appearence and start some background task (threadpool,backgroundworker,etc) that does the work
be aware that you can interact with a control only from the thread it was created on, so to display your data or interact with the UI, you will have to invoke the UI thread (see Control.Invoke(...))
if you have a lot of UI reseting stuff like that, you should think about a timer on the form, to check every let's say 200ms if there is something to be reset/done
you could use a sorted list with tuples (Datetime,delegate) that are executed and removed once the time has come ...
Write the rest of code in thread and fire that thread. this will make your UI responsive and will give you the desired output you want for button. Or use btnedit.Refresh() just after changing color to force button to redraw itself