Display pop up with animated gif while loading file - c#

The goal of my program is to load a directory into my treeview node, which takes more 10 sec(since the directory resides in a remote PC). During this time I shall add a pop up image of waiting. I copied the answer of this post: How can i show an image while my application is loading
to my code, like:
private void treeView2_AfterSelect(object sender, TreeViewEventArgs e)
{
Form f = new Form();
f.Size = new Size(20, 25);
Image im = Image.FromFile(#"C:\Documents and Settings\JiangC\Documenti\Immagini\loader.gif");
f.FormBorderStyle = FormBorderStyle.None;
f.MinimizeBox = false;
f.MaximizeBox = false;
f.AutoSize = true;
PictureBox pb = new PictureBox();
//pb.Dock = DockStyle.Fill;
pb.SizeMode = PictureBoxSizeMode.AutoSize;
pb.Image = im;
pb.Location = new Point(50, 50);
f.Controls.Add(pb);
f.Show();
// Application.DoEvents();
BuildTree(directory, treeView2.Nodes, treeView2.SelectedNode);
f.Close();
}
What I want to do is that during the loading (when Buildtree() method is implementing) the form f with the picturebox pb will be shown, and after loading they just disappear.
The first problem is in "pb.Dock = DockStyle.Fill;"
If I uncomment it, this form f will not be shown during the loading(but I can see its minimized window in the bottom of the screen). Only if I take off this line the form f could be shown out.
The second problem is "f.Show();" When the form f is shown, the picturebox pb isn't shown at all(just an empty hole). If I modify it into "f.ShowDialog();", the form f with the picture could be shown.
However here comes the third problem, which is the form f will be always there, my function "BuildTree();" isn't implemented at all!
The forth problem is if I add the line "Application.DoEvents();", it works quite fine, during the loading the form f with the picturebox will be shown and after the loading the f will disappear, but the picture in f is a gif, in this case the gif doesn't animate at all!
So anybody could help me solve the problem?
Here's my code of BuildTree function:
private void BuildTree(DirectoryInfo[] directoryInfo, TreeNodeCollection addInMe, TreeNode clicked)
{
for (int i = 0; i < directoryInfo.Length; i++)
{
var files = directoryInfo[i].GetFiles();
int length = files.Length;
clicked.Nodes.Add(directoryInfo[i].Name);
List<TreeNode> dateNode = new List<TreeNode>();
string[] allDates = new string[length];
try
{
for (int j = 0; j < length; j++)
{
allDates[j] = files[j].Name.Substring(11, 6);
}
}
catch (Exception e)
{ }
string[] date = allDates.Distinct().ToArray();
for (int k = 0; k < date.Length; k++) //From here to the end
{
// is my code loading file
dateNode.Add(clicked.Nodes.Add(date[i]));
for (int j = 0; j < length; j++)
{
// curNode.Nodes.Add(file.FullName, file.Name);
if (files[j].Name.Substring(11, 6) == date[i])
{
dateNode[i].Nodes.Add(files[j].FullName, files[j].Name);
}
}
}
foreach (DirectoryInfo subdir in directoryInfo[i].GetDirectories())
{
BuildTree(subdir, clicked.Nodes);
}
}
}

It depends on whether your BuildTree() method can be executed on background thread or not.
since you are passing references to properties of TreeView control I'm assuming that that code has to be executed on UI thread.
In this case th only thing I can suggest to you is call Application.DoEvents(); from within BuildTree() method, for example in a loop.
Also if you are making any long term calls to get data inside BuildTree() method, you should make these on background thread and synchronize with UI in any convenient way.
[EDIT]
I had a quick look at your BuildTree() method. I think in this case the best approach for you would be:
declare additional data structures to hold all data recuired to fill the TreeView.
Fill all the data on background (You can use BackgroundWorker for this)
here is a good question highlighting how get results from BackgroundWorker
Once all data is ready, create and add all nodes to TreeView. This also will take some time, but it has to be done on UI thread.
in order to keep UI responsive you can call Application.DoEvents() inside a loop where you will be adding new nodes to the tree
Code in treeView2_AfterSelect() would looks like this:
//...
f.Show();
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (ss, ee) =>
{
ee.Result = PrepareData(directory); // prepare all data required to build the Tree
}
bw.RunWorkerCompleted += (ss, ee) =>
{
BuildTreeByData(ee.Result, addInMe, clicked);
f.Close();
}
bw.RunWorkerAsync();

Add this line to your code, before calling f.Show():
f.Activated += (s, e) => BuildTree(directory, treeView2.Nodes, treeView2.SelectedNode);
// it's f.Shown for Windows Forms, Activated is for WPF
It subscribes an anonymous event handler to the OnShow event of your f form. In this handler you simply call your method.
If it doesn't work, you will have to replace the call to BuildTree here for a BackgroundWorker call, since it is possible that your BuildTree method access UI controls, and that is a big no-no. Call this code instead of f.ShowDialog:
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (ss, ee) =>
{
f.ShowDialog(this);
BuildTree(directory, treeView2.Nodes, treeView2.SelectedNode);
}
bw.RunWorkerCompleted += (ss, ee) => f.Close();
bw.RunWorkerAsync();
And change every access and modification to your controls in BuildTree like this:
this.Invoke((MethodInvoker) (() =>
{
// access you control here
}));

Related

Taking Control of WPF elements

I have an integer array declared in a class. Using that array, a PolyLine is drawn on a grid. The elements of the array are the y-coordinates. The array has to updated continuously and then the values are to be displayed. My idea of implementing this is to have two threads. One to update the values of the array and the other to print PolyLine on the grid.
Updating the array is not a problem. When I try to print PolyLine on the grid using the code below, an exception is thrown with the following message: "The calling thread cannot access this object because a different thread owns it."
Kindly suggest a workaround for this problem using thread. Perhaps, an event may be used instead of thread? I am open to suggestions. Any help is appreciated!
public partial class MainWindow : Window
{
Polyline Wave = new Polyline();
public MainWindow()
{
InitializeComponent();
Refresh();
DisplyOnGrid();
ThreadStart child = new ThreadStart(DisplyOnGrid);
Thread _DisplayOnGrid = new Thread(child);
_DisplayOnGrid.Priority = ThreadPriority.Highest;
_DisplayOnGrid.Start();
}
private void DisplyOnGrid()
{
Wave.Stroke = Brushes.Yellow;
Wave.StrokeThickness = 1.25;
for (int i = 0; i < DisplayGrid.Width; i++)
{
Wave.Points.Add(new Point(i, 50));
}
DisplayGrid.Children.Add(Wave);
}
private void Refresh()
{
DisplayGrid.Children.Clear();
}
}
You are getting Error because you are trying to access object which is owned by UI/Dispatcher thread
try something like this:
private void DisplyOnGrid()
{
Dispatcher.Invoke(new Action(()=>
{
Wave.Stroke = Brushes.Yellow;
Wave.StrokeThickness = 1.25;
for (int i = 0; i < DisplayGrid.Width; i++)
{
Wave.Points.Add(new Point(i, 50));
}
DisplayGrid.Children.Add(Wave);
}
))};

Wait until panel controls are drawn

I have a form, this form has a panel. When the form OnShown fires, I start a background worker to execute a long running code method a set number of times. In this background worker, I add a User Control to the panel one-by-one/per-iteration while displaying a Wait picture on the UI thread.
Problem: My long running code runs faster than the panel can draw the controls I've added to it. Basically, when my form loads, it takes about 1.5 seconds to run the code (I can tell by how fast the wait picture hides itself), however, the child controls on the panel take between 4 and 6 seconds to draw themselves...
As you can imagine this would cause some major confusion to the user in that the Wait image would hide/the code would be done, but the controls wouldn't be shown for another 4 seconds.
So, is there a way to essentially "Wait" until all the child controls on the panel are drawn, so that i can ONLY THEN hide the Wait picture through some mechanism in the Panel control?
If that's not possible, keep in mind that I'm using a User Control and have access to those events as well. So if there's something there I could use to fire AFTER the User Control has drawn itself (or thinks it has), maybe i could use that instead and count until I reach the known count?
Here's my code, obscured a bit to not show any work info/not tell you what I'm doing but should be enough for you to find out if there's something wrong with the code itself...
Legend:
SW() = Show Waiting Picture,
HW() = Hide Waiting Picture,
this.wait = Wait Picture Control with progress bars...
private void MyForm_Shown(object sender, EventArgs e)
{
if (this.SomeList.Count <= 0 || this.SomeObject == null)
this.DialogResult = DialogResult.Abort;
SW();
this.wait.Text = "Loading everything...";
this.wait.TotalBar_Max = this.SomeList.Count;
this.wait.TotalBar_Value = 0;
this.wait.CurrentBar_Max = 100;
BackgroundWorker bkg = new BackgroundWorker();
bkg.DoWork += new DoWorkEventHandler(bkg_DoWork);
bkg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bkg_RunWorkerCompleted);
bkg.RunWorkerAsync();
}
void bkg_DoWork(object sender, DoWorkEventArgs e)
{
foreach (SomeType SomeItem in this.SomeList)
{
//Update Progress bar...
this.Invoke(new MethodInvoker(delegate
{
this.wait.Text = "Loading Specifics for: " + SomeItem.ToString();
this.wait.CurrentBar_Value = 50;
}));
MyControl tmp = new MyControl();
tmp.Name = SomeItem.ToString();
tmp.Text = SomeItem.ToString() + " - " + this.SomeObject.Name;
ServerWorker work = new ServerWorker(SomeItem);
Dictionary<string, List<string>> test = work.LongRunningCode();
//fill in/Init User Control based on long running code results...
if (test["Key1"] != null && test["Key1"].Count > 0)
{
test["Key1"].Sort();
tmp.Key1 = test["Key1"];
}
else
{
tmp.Key1_Available = false;
}
if (test["Key2"] != null && test["Key2"].Count > 0)
{
test["Key2"].Sort();
tmp.Key2 = test["Key2"];
}
else
{
tmp.Key2_Available = false;
}
if (test["Key3"] != null && test["Key3"].Count > 0)
{
test["Key3"].Sort();
tmp.Key3 = test["Key3"];
}
else
{
tmp.Key3_Available = false;
}
//Add user control, and update progress bars...
this.Invoke(new MethodInvoker(delegate
{
if (this.panel1.Controls.Count <= 0)
{
tmp.Top = 5;
tmp.Left = 5;
}
else
{
tmp.Top = this.panel1.Controls[this.panel1.Controls.Count - 1].Bottom + 5;
tmp.Left = 5;
}
this.panel1.Controls.Add(tmp);
this.wait.Text = "Loading Specifics for: " + SomeItem.ToString();
this.wait.CurrentBar_Value = 100;
this.wait.TotalBar_Value += 1;
this.panel1.Refresh();
}));
}
e.Result = true;
}
void bkg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
HW();
}
I found my own solution here:
https://stackoverflow.com/a/6653042/1583649
By building a List within the background worker instead of Invoking the UI thread, and then Panel.Add() on the RunWorkerCompleted event instead, the Wait image was able to stay alive until all the controls were actually drawn on the Panel.

I created an array of pictureBoxes how can i register for an event to all the pictureBoxes?

The code:
pbs = new PictureBox[8];
for (int i = 0; i < pbs.Length; i++)
{
pbs[i] = new PictureBox();
pbs[i].MouseEnter += Form1_MouseEnter;
pbs[i].MouseLeave += Form1_MouseLeave;
pbs[i].Size = new Size(100, 100);
pbs[i].Margin = new Padding(0, 0, 0, 60);
pbs[i].Dock = DockStyle.Top;
pbs[i].SizeMode = PictureBoxSizeMode.StretchImage;
Panel p = i < 4 ? panel1 : panel2;
p.Controls.Add(pbs[i]);
pbs[i].BringToFront();
}
I did:
pbs[i].MouseEnter +=
And when i clicked TAB it did: Form1_MouseEnter
It's not what i wanted.
I want that when i move with the mouse over each of the pictureBoxes area it will do something.
One event for all the pictureBoxes.
If i moved over pictureBox1 do something...pictureBox2 the same...
How can i do it ? I don't want to create 8 events for each pictureBox but one enter event for all.
You need simply write
pbs[i].MouseEnter += globalMouseEnterEvent;
of course you need to have a globalMouseEnterEvent somewhere in your code
public void globalMouseEnterEvent(object sender, System.EventArgs e)
{
....
}
However, another piece of information is needed when your work with event shared between numerous controls. You need to recognize the control that triggers the event. The control instance is passed using the sender parameter that you can cast to your appropriate control type, but what is needed is to give a unique identifier to your control. Like setting the Tag or Name properties when you build the control
for (int i = 0; i < pbs.Length; i++)
{
.....
pbs[i].Tag = "PB" + i.ToString()
...
}
so in the MouseEnter code you could write
public void globalMouseEnterEvent(object sender, System.EventArgs e)
{
PictureBox p = sender as PictureBox;
if(p.Tag.ToString() == "PB1")
.....
else if ......
}
dont use form1_event , copy it's code and rename it
pbs[i].MouseEnter += yourEventName
it's enough
What you're doing is absolute correct, you attach the handler to the event of each control in
turn, so that the same handler works for every PictureBox.
I guess your problem is that the method VS creates is named Form1_MouseEnter. This is completely irrelevant, what determines what a method will handle is the += operator, not its name. Just try to run your original code and it will do what you want.
It seems to be a bug in the C# editor though, as it should have named the automatically generated handler something more appropriate, but anyway, you can rename the method afterwards to reflect its true meaning.
I've tried to apply the tips from the others to your code:
pbs = new PictureBox[8];
for (int i = 0; i < pbs.Length; i++)
{
pbs[i] = new PictureBox();
pbs[i].MouseEnter += Picturebox_MouseEnter;
pbs[i].MouseLeave += PictureBox_MouseLeave;
pbs[i].Name = string.Concat("PB", i); //Added to identify each picturebox
pbs[i].Size = new Size(100, 100);
pbs[i].Margin = new Padding(0, 0, 0, 60);
pbs[i].Dock = DockStyle.Top;
pbs[i].SizeMode = PictureBoxSizeMode.StretchImage;
Panel p = i < 4 ? panel1 : panel2;
p.Controls.Add(pbs[i]);
pbs[i].BringToFront();
}
And the handlers:
private void Picturebox_MouseEnter(object sender, EventArgs e)
{
PictureBox pb = sender as PictureBox;
if (pb != null)
{
if (pb.Name == "PB2")
{
//Do PB2 specific task
}
//Your code when mouse enters one of the pictureboxes
//Use Name property to determine wich one, if needed
}
}
private void PictureBox_MouseLeave(object sender, EventArgs e)
{
//Your code when mouse leaves one of the pictureboxes
//Use Name property to determine wich one, if needed
}

C# Threading - an array of threads, where each thread contains a form with an image

I have an array of five threads. Each thread contains the same form, each form is put on to the screen in a different location (still working on that method :P).
I am trying to have each form load its contents (an image) before the other forms have finishing being placed. At the moment this works for the first form, but the others are blank or disappear :P
Originally each form would be placed but the method would need to finish before all the forms contents were displayed.
Any help would be appreciated, thanks :)
public partial class TrollFrm : Form
{
int number = 0;
public TrollFrm()
{
InitializeComponent();
startThreads();
}
private void TrollFrm_Load(object sender, EventArgs e)
{
}
private void TrollFrm_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
}
public void startThreads()
{
Thread[] ThreadArray = new Thread[5];
for (int i = 0; i < 5; i++)
{
ThreadArray[i] = new Thread(new ThreadStart(createForm));
ThreadArray[i].Start();
}
}
public void createForm()
{
Form frm = new TrollChildFrm();
Random randomX = new Random();
Random randomY = new Random();
number++;
int xValue;
int yValue;
if (number % 2 == 0) //number is even.
{
xValue = (Convert.ToInt32(randomX.Next(1, 1920))) + 200;
yValue = (Convert.ToInt32(randomY.Next(1, 1080))) - 200;
}
else //number is not even.
{
xValue = (Convert.ToInt32(randomX.Next(1, 1920))) - 200;
yValue = (Convert.ToInt32(randomY.Next(1, 1080))) + 200;
}
frm.Show();
frm.Location = new Point(xValue, yValue);
Thread.Sleep(1000);
}
Your forms are not displaying correctly because they are not running on a thread with a message loop. The general rule is that all UI element accesses must occur on the main UI thread.
Since you have a call to Thread.Sleep(1000) I am going to assume that you want to wait 1 second between the initial display of each form. In that case I would use a System.Windows.Forms.Timer who's Tick event will call createForm directly. Enable the timer, let 5 Tick events come through, and then disable the timer. I see no need to create any threads at all.
The reason your forms aren't displaying is because you are running inside one method on the main UI thread. Instead, you could create a method that spawns a new form and launch that at certain intervals on another thread (making sure the form handling is done on the main UI thread). So you could do something like:
(Pseudo Code)
private const int TIME_THRESHOLD = 100;
int mElapsedTime = 0;
Timer mTimer = new Timer();
.ctor
{
mTimer.Elapsed += mTimer_Elapsed;
}
private void mTimer_Elapsed(...)
{
mElapsedTime++;
if (mElapsedTime >= TIME_THRESHOLD)
{
mElapsedTime = 0;
SpawnForm();
}
}
private void SpawnForm()
{
// Make sure your running on the UI thread
if (this.InvokeRequired)
{
this.BeginInvoke(new Action(SpawnForm));
return;
}
// ... spawn the form ...
}
This is just an example of what I was proposing - it would not look exactly like this in the code, but this should give you an idea of the execution steps.
I would suggest to use Thread.Sleep(1000) in this manner
Caller section
for (int i = 0; i < 5; i++)
{
ThreadArray[i] = new Thread(new ThreadStart(createForm));
ThreadArray[i].Start();
}
Thread.Sleep(1000);
Also in the method that executing the work for the thread.
while(!something)
{
Thread.Sleep(1000)
}

How do I implement a progress bar in C#?

How do I implement a progress bar and backgroundworker for database calls in C#?
I do have some methods that deal with large amounts of data. They are relatively long running operations, so I want to implement a progress bar to let the user know that something is actually happening.
I thought of using progress bar or status strip label, but since there is a single UI thread, the thread where the database-dealing methods are executed, UI controls are not updated, making the progress bar or status strip label are useless to me.
I've already seen some examples, but they deal with for-loops, ex:
for(int i = 0; i < count; i++)
{
System.Threading.Thread.Sleep(70);
// ... do analysis ...
bgWorker.ReportProgress((100 * i) / count);
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = Math.Min(e.ProgressPercentage, 100);
}
I'm looking for better examples.
Some people may not like it, but this is what I do:
private void StartBackgroundWork() {
if (Application.RenderWithVisualStyles)
progressBar.Style = ProgressBarStyle.Marquee;
else {
progressBar.Style = ProgressBarStyle.Continuous;
progressBar.Maximum = 100;
progressBar.Value = 0;
timer.Enabled = true;
}
backgroundWorker.RunWorkerAsync();
}
private void timer_Tick(object sender, EventArgs e) {
if (progressBar.Value < progressBar.Maximum)
progressBar.Increment(5);
else
progressBar.Value = progressBar.Minimum;
}
The Marquee style requires VisualStyles to be enabled, but it continuously scrolls on its own without needing to be updated. I use that for database operations that don't report their progress.
If you can't know the progress you should not fake it by abusing a progress bar, instead just display some sort of busy icon like en.wikipedia.org/wiki/Throbber#Spinning_wheel Show it when starting the task and hide it when it's finished. That would make for a more "honest" GUI.
When you perform operations on Background thread and you want to update UI, you can not call or set anything from background thread. In case of WPF you need Dispatcher.BeginInvoke and in case of WinForms you need Invoke method.
WPF:
// assuming "this" is the window containing your progress bar..
// following code runs in background worker thread...
for(int i=0;i<count;i++)
{
DoSomething();
this.Dispatcher.BeginInvoke((Action)delegate(){
this.progressBar.Value = (int)((100*i)/count);
});
}
WinForms:
// assuming "this" is the window containing your progress bar..
// following code runs in background worker thread...
for(int i=0;i<count;i++)
{
DoSomething();
this.Invoke(delegate(){
this.progressBar.Value = (int)((100*i)/count);
});
}
for WinForms delegate may require some casting or you may need little help there, dont remember the exact syntax now.
The idea behind reporting progress with the background worker is through sending a 'percent completed' event. You are yourself responsible for determining somehow 'how much' work has been completed. Unfortunately this is often the most difficult part.
In your case, the bulk of the work is database-related. There is to my knowledge no way to get progress information from the DB directly. What you can try to do however, is split up the work dynamically. E.g., if you need to read a lot of data, a naive way to implement this could be.
Determine how many rows are to be retrieved (SELECT COUNT(*) FROM ...)
Divide the actual reading in smaller chunks, reporting progress every time one chunk is completed:
for (int i = 0; i < count; i++)
{
bgWorker.ReportProgress((100 * i) / count);
// ... (read data for step i)
}
I have not compiled this as it is meant for a proof of concept. This is how I have implemented a Progress bar for database access in the past. This example shows access to a SQLite database using the System.Data.SQLite module
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
using(SQLiteConnection cnn = new SQLiteConnection("Data Source=MyDatabase.db"))
{
cnn.Open();
int TotalQuerySize = GetQueryCount("Query", cnn); // This needs to be implemented and is not shown in example
using (SQLiteCommand cmd = cnn.CreateCommand())
{
cmd.CommandText = "Query is here";
using(SQLiteDataReader reader = cmd.ExecuteReader())
{
int i = 0;
while(reader.Read())
{
// Access the database data using the reader[]. Each .Read() provides the next Row
if(worker.WorkerReportsProgress) worker.ReportProgress(++i * 100/ TotalQuerySize);
}
}
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Notify someone that the database access is finished. Do stuff to clean up if needed
// This could be a good time to hide, clear or do somthign to the progress bar
}
public void AcessMySQLiteDatabase()
{
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.DoWork +=
new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged +=
new ProgressChangedEventHandler(
backgroundWorker1_ProgressChanged);
}
This will Helpfull.Easy to implement,100% tested.
for(int i=1;i<linecount;i++)
{
progressBar1.Value = i * progressBar1.Maximum / linecount; //show process bar counts
LabelTotal.Text = i.ToString() + " of " + linecount; //show number of count in lable
int presentage = (i * 100) / linecount;
LabelPresentage.Text = presentage.ToString() + " %"; //show precentage in lable
Application.DoEvents(); keep form active in every loop
}
You have to execute the process from a thread, and from the thread you invoke the progress bar and change its value, maybe this example helps you
public void main()
{
int count = 20;
progressbar.Maximum = count;
progressbar.Value = 0;
new Thread(() => Work(progressbar, count)).Start();
}
public static void Work(ProgressBar progressbar, int count)
{
for (int i = 0; i <= count; i++)
{
Thread.Sleep(70);
// ... do analysis ...
Application.Current.Dispatcher.Invoke(new Action(() =>
{
progressbar.Value = i;
}));
}
}

Categories

Resources