I am trying to make a fading effect on my splash screen, on a WPF application.
The Opacity of the image object is initially 0. This code would modify the Opacity from 0 (min) to 1 (max), but the line img_waves.Opacity just doesn't work. The image opacity remains 0.
private void Splash_ContentRendered(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(3000);
for (double x = 0; x<=1; x+=0.01d)
{
System.Threading.Thread.Sleep(15);
//MessageBox.Show(x.ToString());
img_waves.Opacity = x;
}
this.Close();
}
But, if I activate the line ´MessageBox.Show(x.ToString());´
as you can see on this image:
The code works, but I have to keep clicking on the message boxes.
My ask is: Why? Why doesn't work without the MessageBox.Show?
Because you're blocking the GUI thread. It never gets a chance to redraw the form. When you add the message box, the message queue is pumped, which allows the drawing.
The simplest way to deal with this would be like this:
private async void Splash_ContentRendered(object sender, EventArgs e)
{
await Task.Delay(3000);
for (double x = 0; x<=1; x+=0.01d)
{
await Task.Delay(15);
img_waves.Opacity = x;
}
this.Close();
}
Do note that this means the form can still be interacted with during the animation. This shouldn't be a problem for a splashscreen, but it could cause you trouble in a "real" form. Still, make sure the form can't be closed during the animation - that could cause exceptions :)
There's also other ways to force the message queue to be pumped, but it's usually frowned upon.
All that said, you're using WPF - why are you doing the animation manually like this? Can't you just handle it as an animation effect in WPF, natively? There's a sample on MSDN.
Whilst I agree with #Luaan explanation as to why as an alternative solution to your loop you can use Storyboard with DoubleAnimation on Opacity property
private void Splash_ContentRendered(object sender, EventArgs e)
{
var sb = new Storyboard();
var da = new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(1.5)));
da.BeginTime = TimeSpan.FromSeconds(3);
Storyboard.SetTargetProperty(da, new PropertyPath("Opacity"));
Storyboard.SetTarget(da, img_waves);
sb.Children.Add(da);
sb.Completed += (s1, e1) => this.Close();
sb.Begin();
}
Related
Well , I have a form1 which has buttons and if you click one of its button
It would load the UserControl into panel in form1
That usercontrol1 contains a lot of data like Database,charts and picture boxes too. So it would definitely make the User Interface unresponsive while loading.
So I read some article and I found out that I need to run it through another thread so I tried it and it just increase the performance by a little bit.
The usercontrol1 still make the GUI unresponsive for about 3-5 sec and what if my data become larger.
I want to make it responsive and show to user that still loading by running the animated picturebox and stop if its finish loading
here is my code:
private void click_dashb_Click(object sender, EventArgs e)
{
ParameterizedThreadStart pts = new ParameterizedThreadStart(load_UserControl);
Thread t = new Thread(pts);
t.Start();
//Animated Picturebox to show user that UI is loading
pictureBox1.Enabled = true;
hover.Location = new Point(42, 130);
}
private void load_UserControl(object state)
{
Invoke(new Action(() =>
{
//load user control through another thread
while (panel1.Controls.Count > 0)
panel1.Controls[0].Dispose();
Home frm = new Home();
frm.AutoScroll = true;
panel1.Controls.Add(frm);
frm.Show();
}));
//Stop the animated GIF means the load is finish!
pictureBox1.Enabled = false;
}
If you help me about this problem. I might apply it to all of my works. because most of it contains large data.
Thanks stackoverflow community :)
EDIT:
After reading the comments suggesting to use Background worker . I tried to use it. but still getting a little bit unresponsiveness
Here's the new code:
private void click_dashb_Click(object sender, EventArgs e)
{
bgw.RunWorkerAsync();
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
try
{
BeginInvoke((MethodInvoker)delegate
{
while (panel1.Controls.Count > 0)
panel1.Controls[0].Dispose();
Home frm = new Home();
frm.AutoScroll = true;
panel1.Controls.Add(frm);
frm.Show();
});
}
catch (Exception x)
{
MessageBox.Show("An error occured while performing operation" + x);
}
}
private void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Operation Cancelled");
}
else
{
MessageBox.Show("Operation Completed");
}
}
It's a little bit better but i still got a little unresponsiveness. Can you check out my code and what's wrong with this.?
thanks again
The problem with your code is that although you run the load_UserControl code in a new thread, that code calls Invoke which effectively makes al the code run on the UI thread again. I can image you did that because accessing Forms and PictureBoxes requires running on the UI thread.
The solution (in general) is to do non-UI work on a seperate thread and then switch back to the UI thread to update the visual controls.
To do this, you can conveniently use the BackgroundWorker class. In the DoWork event handler you do the heavy computation, in the RunWorkerCompleted event handler you update the controls. If you want, you can even update some controls (like a progressbar) during the work by means of the ProgressChanged event handler.
Well, just starting a new thread doesn't make the UI responsive by definition. You need to make the thread so that it actually does stuff in parallel.
Your thread does not, as it basically executes all code in this.Invoke.
That being said: Your code needs to be executed in this.Invoke, as almost everything you do needs to be done on the UI thread.
So in your case, there's really no point in parallelizing stuff, as there's no way to do what you want to do without blocking the UI thread and no technique I know of (Threads, Tasks, BackgroundWorker, etc.) will solve this problem.
I already tried google to find an answer to my problem but haven't found a solution.
I working with C# and WinForms. I created a panel and added a label to it. This panel is set to myPanel.Visible = falseat first. I want to set it myPanel.Visible = true when I click a button. The button is calling a function. During the function call I want to show a progessbar in the panel, so I set this myPanel.Visible = trueand at the end of the function I set it back to myPanel.Visible = false.
The problem is, that the label isn't visible.
When I don't set myPanel.Visible = false at the end of the function, the label is visible, but only at the end of the function.
I also tried to programmatically add the label in the function called, still not working. The second idea I tried was to use this.PerformLayout(); during the call of the function.
It seems like that the application is drawing the label only at the end of the function call, but I need it to be drawn during the function is called.
Thanks for any help.
private void buttonAdd_Click(object sender, EventArgs e)
{
//Adding label to panel
MyLabel label = new MyLabel();
label.Text = "Test";
label.Location = new Point(0, 0);
progressPanel.Controls.Add(label);
//Showing progressPanel
progressPanel.Visible = true;
progressBar1.Minimum = 1;
progressBar1.Value = 1;
progressBar1.Step = 1;
//Some Code
progressPanel.Visible = false;
}
Your problem is obviously that you are performing your task in the UI thread. So the UI itself doesn't get repainted while this task is working. And when you finished, it's still invisible.
Try using a BackgroundWorker instead. Use its ProgressChanged event to update your progress bar.
If you show some code, I can go into details of implementation.
UPDATE:
I actually prefer sstan's answer, but I promised to show the BackgroundWorker way. It may still be helpful if for some reason you cannot use async/await:
public partial class Form1 : Form
{
private BackgroundWorker _backgroundWorker;
public Form1()
{
InitializeComponent();
_backgroundWorker.DoWork += DoWork;
_backgroundWorker.RunWorkerCompleted += WorkerCompleted;
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.ProgressChanged += WorkerProgressed;
}
private void buttonAdd_Click(object sender, EventArgs e)
{
//Adding label to panel
MyLabel label = new MyLabel();
label.Text = "Test";
label.Location = new Point(0, 0);
progressPanel.Controls.Add(label);
//Showing progressPanel
progressPanel.Visible = true;
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Value = 0;
progressBar1.Step = 1;
// to avoid multiple starts
buttonAdd.Enabled = false;
// start working
_backgroundWorker.RunWorkerAsync();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
_backgroundWorker.ReportProgress(1);
// work
_backgroundWorker.ReportProgress(50);
// more work
_backgroundWorker.ReportProgress(100);
}
private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressPanel.Visible = false;
buttonAdd.Enabled = true;
}
private void WorkerProgress(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
}
So as you see, BackgroundWorkers are fairly easy to use, but async/await is even easier since you don't have to write so much code just for parallelization.
The behavior you see is normal. The changes in visibility only take effect when the UI thread gets a chance to repaint your window. But, while you function is running, the UI thread is busy, so it can't repaint your window. The UI thread only frees up at the end of your function call, so that's when the component's visibility changes take effect.
What you need to do is to perform your function's work on a different thread so that the UI thread is free to repaint your window while the function is still running.
One way to do this, is by using Task.Run() combined with the async/await keywords. Here is a basic example of how this could look like, using the code you posted:
async private void buttonAdd_Click(object sender, EventArgs e)
{
// ....
//Showing progressPanel
progressPanel.Visible = true;
progressBar1.Minimum = 1;
progressBar1.Value = 1;
progressBar1.Step = 1;
// this work will happen on separate thread,
// so the UI thread will be free to update the panel visibility
Progress<int> progress = new Progress<int>(percentage => progressBar1.Value = percentage);
await Task.Run(() => this.WorkToBePerformedOnSeparateThread(progress));
progressPanel.Visible = false;
}
private void WorkToBePerformedOnSeparateThread(IProgress<int> progress)
{
// do work...
progress.Report(25); // Report 25% completed...
// do more work
progress.Report(50); // Report 50% completed...
// more work
progress.Report(75); // Report 75% completed...
// etc...
}
As pointed out by Rene in the comments, just remember that you can only do UI work on the UI thread. So, in the example above, you'll notice that the progress reporting is performed through the Progress<T> class which allows you to change the progress bar value (UI work) from the separate thread, because it takes care of ensuring that the progress reporting happens on the UI thread.
I am experimenting a behavior which makes me crazy.
I have a ProgressBar which represents the evolution of an import in database (in percents, from 0 to 100).
After the import is done (ProgressBar.Value = 100.0), I open a log window with a code which looks like this :
RadWindow window = new RadWindow()
{
//Set some properties
};
window.Closed += Log_Closed;
window.ShowDialog();
After the RadWindow is closed, I want to reset the ProgressBar. As you can see I use the function Log_Closed whose code is bellow :
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
}
Note : pbImport is my progress bar.
The instruction in Log_Closed has no effect.
Before instruction :
After instruction :
Obviously, the progress bar is not updated in UI. I can't understand this. Thank you for your help.
Animations hold onto properties, in order to reset them in code, you have to remove the animation first so that the property is "released".
See https://msdn.microsoft.com/en-us/library/aa970493%28v=vs.110%29.aspx for information on how to set a property after an animation in WPF.
Resetting the progress Bar can be achieved by using an "if" loop and incrementing the progress bar.
You can set a bool value for the database process and then simply:
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
if (database)
{
pbImport.Increment(100);
}
}
From Microsoft's documentation -
To remove a specific AnimationClock from a list of clocks, use the Controller property of the AnimationClock to retrieve a ClockController, then call the Remove method of the ClockController. This is typically done in the Completed event handler for a clock. Note that only root clocks can be controlled by a ClockController; the Controller property of a child clock will return null. Note also that the Completed event will not be called if the effective duration of the clock is forever. In that case, the user will need to determine when to call Remove.
In the example below I demonstrate setting up an event handler that runs when the animation is complete and removes the clock controller there, then set the ProgressBar value back to 0.
void RunAnimation()
{
Duration duration = new Duration(TimeSpan.FromSeconds(10));
DoubleAnimation doubleanimation = new DoubleAnimation(100.0, duration);
doubleanimation.Completed += ProgressBarCompleted;
ProgBar.BeginAnimation(ProgressBar.ValueProperty, doubleanimation);
}
private void ProgressBarCompleted(object sender, EventArgs e)
{
var clock = (AnimationClock)sender;
clock.Controller.Remove();
ProgBar.Value = 0;
}
Note: ProgBar is defined in a .xaml file like
<ProgressBar Margin="0,0,0,0"
Padding="0,0,0,0"
x:Name="ProgBar"
Width="800"
HorizontalAlignment="Right"
Foreground="LightGray"/>
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