How can I load multiple images using LoadAsync() in C#? - c#

I'm trying to update the GUI, and I have an asynchronous function that uses LoadAsyc(), when I load just one image, it works but when I try to load more than one, the second one doesn't display.
This my code:
public UserFriendlyInterface()
{
InitializeComponent();
locationFileH5 = "";
serverStatus = false;
ipAddress = getLocalIPAddress();
port = 5000;
watcher = new FileSystemWatcher(#"flask_server\cnn\_prepImages_");
watcher.EnableRaisingEvents = true;
watcher.Changed += watcher_Changed;
}
private void watcher_Changed(object sender, FileSystemEventArgs e)
{
updateImages();
}
async Task updateImages()
{
pictureBoxNormalImg.WaitOnLoad = false;
pictureBoxNormalImg.LoadAsync(#"flask_server\cnn\_prepImages_\normal.jpg");
pictureBoxSegmentation.WaitOnLoad = false;
pictureBoxSegmentation.LoadAsync(#"flask_server\cnn\_prepImages_\segmentation.jpg");
}

What you are trying to achieve can be achieved more robustly by querying the Name property of the FileSystemEventArgs object, and updating only the corresponding PictureBox.
private static void Watcher_Changed(object sender, FileSystemEventArgs e)
{
PictureBox pictureBox;
switch (e.Name.ToLowerInvariant())
{
case "normal.jpg": pictureBox = pictureBoxNormalImg; break;
case "segmentation.jpg": pictureBox = pictureBoxSegmentation; break;
default: pictureBox = null; break;
}
if (pictureBox != null)
{
Image image = null;
try
{
using (var temp = new Bitmap(e.FullPath))
{
image = new Bitmap(temp);
}
}
catch { } // Swallow exception
if (image != null)
{
pictureBox.Invoke((MethodInvoker)(delegate ()
{
pictureBox.Image = image;
}));
}
}
}
I would avoid the LoadAsync method because it is intended mainly for loading images from the internet, and because I don't totally trust it.
Update: There were two problems with my initial code:
1) Free file locked by new Bitmap(filePath)
2) FileSystemWatcher Changed event is raised twice
The updated code solves these problems (hopefully), but not in the most robust or efficient way possible.
Update: To make the code more efficient, by avoiding the repeated loading of the images caused by multiple firings of the Changed event, you could use the extension method OnChanged found in this answer. It suffices to replace the line below:
watcher.Changed += Watcher_Changed;
...with this one:
watcher.OnChanged(Watcher_Changed, 100);

Related

C# .NET if image is loaded

I need to find out if my pictureboxes have loaded images.
I create them and give them ImageLocation. Pictures DO load after some time but I need to check if it is loaded. I tried using
if(pb.ImageLocation != null){
Console.WriteLine("Loaded!");
}
But this shows that its loaded even if it actually isn't. Also I have a bunch of dynamic created pictureboxes:
void CreateBrick(int x,int y)
{
bricks[i] = new PictureBox();
bricks[i].Name = "pb_b" + i.ToString();
bricks[i].Location = new Point(y, x);
bricks[i].Size = new Size(60, 60);
bricks[i].ImageLocation = #"Images/brick_wall.jpg";
pb_bg.Controls.Add(bricks[i]);
brick.Add(bricks[i]);
i++;
}
And I have no idea how to check these...
The problem in your code is that you don't call the Load or LoadAsync method.
void CreateBrick(int x,int y)
{
bricks[i] = new PictureBox();
bricks[i].Name = "pb_b" + i.ToString();
bricks[i].Location = new Point(y, x);
bricks[i].Size = new Size(60, 60);
// You can pass the path directly to the Load method
// bricks[i].ImageLocation = #"Images/brick_wall.jpg";
bricks[i].Load(#"Images/brick_wall.jpg");
pb_bg.Controls.Add(bricks[i]);
brick.Add(bricks[i]);
i++;
}
If you use Load method then then the image is loaded after the call, if you use LoadAsync you could add the event handler for the LoadComplete event.
bricks[i].LoadCompleted += onLoadComplete;
bricks[i].LoadAsync(#"Images/brick_wall.jpg");
....
private void onLoadComplete(Object sender, AsyncCompletedEventArgs e)
{
// Don't forget to check if the image has been really loaded,
// this event fires also in case of errors.
if (e.Error == null && !e.Cancelled)
Console.WriteLine("Image loaded");
else if (e.Cancelled)
Console.WriteLine("Load cancelled");
else
Console.WriteLine("Error:" + e.Error.Message);
}
If you want to use the LoadAsync approach you still have to solve the problem how to match the complete load of a particular image to the related picture box. This could be solved using the sender parameter of the LoadAsync. This sender parameter is the PictureBox who has completed the load of the image.
You can use the Tag property and set it to "1" to mark your picturebox as loaded and to the error message in case of problems.
private void onLoadComplete(Object sender, AsyncCompletedEventArgs e)
{
PictureBox pic = sender as PictureBox;
// Don't forget to check if the image has been really loaded,
// this event fires also in case of errors.
if (e.Error == null && !e.Cancelled)
{
pic.Tag = "1";
Console.WriteLine("Image loaded");
}
else
{
pic.Tag = e.Error.Message;
Console.WriteLine("Cancelled:" + e.Error.Message);
}
}
After this the pictureboxes in your bricks arrays have their Tag property marked as "1" for the loaded ones and with an error message for the ones with an error.
Could you try using LoadCompleted event of PictureBox after you assign a ImageLocation as the way it is described here
You can make sure images are loaded asynchronously of course.
pb.WaitOnLoad = false;
Then load the image asynchronously:
pb.LoadAsync("some.gif");
For more from stackoverflow you can have a look here and here
Assign an event handler like the following:
pb.LoadCompleted += PictureBox1_LoadCompleted;
Sample event handler right from msdn:
private void PictureBox1_LoadCompleted(Object sender, AsyncCompletedEventArgs e) {
System.Text.StringBuilder messageBoxCS = new System.Text.StringBuilder();
messageBoxCS.AppendFormat("{0} = {1}", "Cancelled", e.Cancelled );
messageBoxCS.AppendLine();
messageBoxCS.AppendFormat("{0} = {1}", "Error", e.Error );
messageBoxCS.AppendLine();
messageBoxCS.AppendFormat("{0} = {1}", "UserState", e.UserState );
messageBoxCS.AppendLine();
MessageBox.Show(messageBoxCS.ToString(), "LoadCompleted Event" );
}
private stattic bool CheckUplodedImage()
{
bool return = false;
try
{
PictureBox imageControl = new PictureBox();
imageControl.Width = 60;
imageControl.Height = 60;
Bitmap image = new Bitmap("Images/brick_wall.jpg");
imageControl.Image = (Image)image;
Controls.Add(imageControl);
return true;
}
catch(Exception ex)
{
return false;
}
}
can check of its return
bool isUploded = CheckUplodedImage();
if(isUploded)
{
\\ ...uploaded
\\ Perform Operation
}
else
\\ not uploaded

FileSystem Watcher- Checking if copied file is an Image

I'm monitoring a Folder for File creation(Copied) event using FileSystem Watcher. I only want the program to process image Files.
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Filter = "*.*";
watcher.Created += new FileSystemEventHandler(watcher_FileCreated);
watcher.Path = path;
So I try to create a Bitmap and avoid the file if an exception is thrown
private static void watcher_FileCreated(object sender, FileSystemEventArgs e)
{
try
{
using (Bitmap test = new Bitmap(Bitmap.FromFile(e.FullPath)))
{
mytoprocesslist.add(e.FullPath);
}
//do my processing with image
Console.WriteLine(e.FullPath);
}
catch (Exception error)
{
Console.WriteLine("File Error");
}
}
This throws Out of Memory exception even when a valid image file is copied, which I think happens because the event was raised before the file was copied completely. How can I get over this? I only want to add the valid image files to a to do list and I will process these images one by one later.
A bit cleaner solution than a Try-Catch might be this one.
Im using this code without any exceptions raised.
private static bool IsImage(string path) {
try {
var result = false;
using (var stream = new FileStream(path, FileMode.Open)) {
stream.Seek(0, SeekOrigin.Begin);
var jpg = new List<string> { "FF", "D8" };
var bmp = new List<string> { "42", "4D" };
var gif = new List<string> { "47", "49", "46" };
var png = new List<string> { "89", "50", "4E", "47", "0D", "0A", "1A", "0A" };
var imgTypes = new List<List<string>> { jpg, bmp, gif, png };
var bytesIterated = new List<string>();
for (var i = 0; i < 8; i++) {
var bit = stream.ReadByte().ToString("X2");
bytesIterated.Add(bit);
var isImage = imgTypes.Any(img => !img.Except(bytesIterated).Any());
if (isImage) {
result = true;
break;
}
}
}
return result;
} catch (UnauthorizedAccessException) {
return false;
}
}
Usage of code
foreach (var file in Directory.EnumerateFiles(#"pathToFlowersFolder"))
{
Console.WriteLine($"File: {file} Result:{IsImage(file)}");
}
Edit
After playing around i got an IO-Exception (File already in use)
After reading this i'd offer you the following solution:
private void button1_Click(object sender, EventArgs e)
{
var watcher = new FileSystemWatcher();
watcher.Created += new FileSystemEventHandler(fileSystemWatcher1_Changed);
watcher.Path = #"c:\temp";
watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
watcher.EnableRaisingEvents = true;
}
private void fileSystemWatcher1_Changed(object sender, System.IO.FileSystemEventArgs e)
{
Thread.Sleep(100); // <- give the Creator some time. Increase value for greate pause
if (IsImage(e.FullPath))
{
Console.WriteLine("success----------->" + e.FullPath);
}
}
Note
This piece of code properly works on my machine. My HDD is an SSD, so you might need to increase the thread-sleeping time. It properly works for all images (jpg, bmp, gif, png) up to a size of 7 Mb (im quite sure and greater).
If this code doesnt works for you, please post the exception rather than uploading your code.
For the first requirement: "I only want the program to process image files"
private static void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
{
string strFileExt = getFileExt(e.FullPath);
// filter file types
if (Regex.IsMatch(strFileExt, #"\.png|\.jpg", RegexOptions.IgnoreCase))
{
//here Process the image file
}
}
For the second requirement: "Out of Memory Exception"
Here what happens is, when the file is created (only file name and some attributes) the system is calling the created event. Then the file changed event is also called
So you have to do the processing in the changed event. Also to prevent duplicate calling you have to add a filter to your watcher.
The following is the complete code.
private void fileSystemWatcher1_Changed(object sender, System.IO.FileSystemEventArgs e)
{
FileInfo fileInfo = new FileInfo(e.FullPath);
string strFileExt = fileInfo.Extension;
// filter file types
if (Regex.IsMatch(strFileExt, #"\.png|\.jpg", RegexOptions.IgnoreCase))
{
//here Process the image file
try
{
using (Bitmap test = new Bitmap(Bitmap.FromFile(e.FullPath)))
{
//Do your code here.
}
}
catch (Exception error)
{
Console.WriteLine("File Error");
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
fileSystemWatcher1.Path = #"C:\Users\Christlin\Desktop\res";
//To Prevent duplicated calling of changed event
fileSystemWatcher1.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
}

Updating UI with BackgroundWorker in WPF

I am currently writing a simple WPF 3.5 application that utilizes the SharePoint COM to make calls to SharePoint sites and generate Group and User information. Since this process takes awhile I want to show a ProgressBar while the groups are being generated. The desired process is as follows:
User enters url and clicks button to fetch site data.
ProgressBar begins animation
Groups are generated and names are added to a ListView
Upon completion ProgressBar animation ends
The problem I am running into is that the UI is never updated. Neither the ProgressBar or the ListView makes any changes. If anyone has any ideas to help with the code below it would be greatly appreciated.
private void GetGroupsAndUsersButton_Click(object sender, RoutedEventArgs e)
{
siteUrl = "";
if (SiteURLTextBox.Text.Length > 0)
{
FetchDataProgressBar.IsIndeterminate = true;
mWorker = new BackgroundWorker();
mWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
mWorker.WorkerSupportsCancellation = true;
mWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
mWorker.RunWorkerAsync();
}
else
{
System.Windows.MessageBox.Show("Please enter a URL for the SharePoint site you wish to retrieve data");
}
}
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
siteUrl = SiteURLTextBox.Text;
GroupListView.ItemsSource = null;
try
{
using (SPSite site = new SPSite(siteUrl))
{
SPWeb web = site.OpenWeb();
SPGroupCollection collGroups = web.SiteGroups;
if (GroupNames == null)
GroupNames = new List<string>();
foreach (SPGroup oGroup in collGroups)
{
GroupListView.Items.Add(new ListViewItem() { Content = oGroup.Name });
}
foreach (ListViewItem item in GroupListView.Items)
{
item.MouseLeftButtonUp += item_MouseLeftButtonUp;
}
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show("Unable to locate a SharePoint site at: " + siteUrl);
}
}
private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
FetchDataProgressBar.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
FetchDataProgressBar.IsIndeterminate = false;
}
));
}
At first you need to support ProgressChanged events.
Update your BackgroundWorker initialization to:
GroupListView.ItemSource = null;
mWorker = new BackgroundWorker();
mWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
mWorker.WorkerSupportsCancellation = true;
mWorker.WorkerReportsProgress = true;
mWorker.ProgressChanged += OnProgressChanged;
mWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
mWorker.RunWorkerAsync(SiteURLTextBox.Text);
After that you have to add a OnProgressChanged handler:
private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
FetchDataProgressBar.Value = e.ProgressPercentage;
ListViewItem toAdd = (ListViewItem)e.UserState;
toAdd.MouseLeftButtonUp += item_MouseLeftButtonUp;
GroupListView.Items.Add(toAdd);
}
Therefore you have to change your DoWork:
private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
try
{
using (SPSite site = new SPSite((String)e.Argument))
{
SPWeb web = site.OpenWeb();
SPGroupCollection collGroups = web.SiteGroups;
if(GroupNames == null)
GroupNames = new List<string>();
int added = 0;
foreach(SPGroup oGroup in collGroups)
{
added++;
ListViewItem tmp = new ListViewItem() {
Content = oGroup.Name
};
worker.ReportProgress((added * 100)/collGroups.Count,tmp);
}
}
}
catch (Exception ex)
{
MessageBox.Show("Unable to locate a SharePoint site at: " + siteUrl);
}
}
That's because you're not allowed to change GUI on DoWork.
After that, each ListViewItem is added separately to your ListView. I would also recommend, that your URL is passed as an argument to RunWorkerAsync.
Edit: Add percentage to OnProgressChanged.
In your DoWork method, you are manipulating WPF controls in code on a background thread, which you are not supposed to do. Actually, you should receive errors like "Cannot access control from other thread". Probably those exceptions are caught by your catch-all error handler, and maybe even the MessageBox doesn't work from the background thread.
As a quick fix, you would have to make siteURL and collGroups class fields, move everything before the using block to your GetGroupsAndUsersButton_Click method, and everything starting with the first foreach loop to the RunworkerCompleted event, so that all code which accesses controls runs on the UI thread.
Another thing you should change is that you should not create ListViewItems in code, but use a DataTemplate instead... this is not connected to your problem, though.
You'll need:
mWorker.WorkerReportsProgress = true;
mWorker.ProgressChanged +=
new ProgressChangedEventHandler(worker_ProgressChanged);
Then in your DoWork you'll need to call:
var worker = (BackgroundWorker)sender;
worker.ReportProgress(progressAmount);
Good worked example here: http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx

Capture class (Emgu) issue in multiple background worker threads

I have a simple Winforms app that allows users to select multiple videos (files) simultaneously and runs background workers threads to loop through each of the videos in the BW. Have pasted code below, I get a NullReferenceException as "Unable to create capture from ..." at this line
Capture _capture = new Capture(videoFileName)
in processVideo method.
N.B: The same code work fine if I select a single video. So some issue with the multiple instances of Capture class.
I would expect the ProcessVideo method to have new instance of Capture and open it separately. Any ideas on what I might be doing wrong?
private void openVideoToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "Video | *.AVI;*.MPEG;*.WMV;*.MP4;*.MOV;*.MPG;*.MPEG;*.MTS;*.FLV";
ofd.Multiselect = true;
if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
string[] videos = ofd.FileNames;
if (videos != null)
{
BackgroundWorker[] bw = new BackgroundWorker[videos.GetLength(0)];
int n = 0;
foreach (string video in videos)
{
bw[n] = new BackgroundWorker();
bw[n].DoWork += new DoWorkEventHandler(bw_DoWork);
bw[n++].RunWorkerAsync(video);
}
}
}
}
catch (NullReferenceException excpt)
{
MessageBox.Show(excpt.Message);
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
string filename = (string)e.Argument;
ProcessVideo(filename);
}
private void ProcessVideo(string videoFileName)
{
Capture _capture = new Capture(videoFileName);
UInt64 TOTAL_FRAMES = Convert.ToUInt64(_capture.GetCaptureProperty(Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_COUNT));
for (UInt64 n = 0; n < TOTAL_FRAMES; n++)
{
using (Image<Bgr, Byte> img1 = _capture.QueryFrame())
{
//do something with the frame
}
}
}
I suggest you to update Sourcesafe service pack
it may help you
[I think you code is perfect there is
nothing wrong in it.
You got an error while creating object it clearly saw that
there may be chance that file format is not supported
or may be internal error problem.]
Let me know that after doing updation it works or not.
Regards Red

Multi threading in WPF using C# (with background worker)

I have written code to save an image which is generated by the application. The size of the image is around 32-35 MB. While saving the image to a BMB file, it is taking a long time, around 3-5 secs. For this purpose, I have used a background worker but when running the background worker, it shows an error like..."can't access the object as it is created on different thread".
Following is the code:
private void btnSaveDesign_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog sfd = new Microsoft.Win32.SaveFileDialog();
sfd.Title = "Save design as...";
sfd.Filter = "BMP|*.bmp";
if (sfd.ShowDialog() == true)
{
ww = new winWait();
ww.Show();
System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
fName = sfd.FileName;
cache = new CachedBitmap((BitmapSource)imgOut.Source, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
bw.RunWorkerAsync();
}
}
void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
ww.Close();
}
void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(cache)); //here... it says cant access...
using (FileStream file = File.OpenWrite(fName))
{
encoder.Save(file);
}
}
I have declared "cache" as a global object. (A similar trick worked when I was programming in Windows Forms with VB.NET.)
ww is the wait window that I want to be displayed while the precess is being executed.
How to do this? Is there any other simple method for multi threading in WPF?
When WPF objects are created they are assigned to a Dispatcher object. This disallows any threads other than the creating thread to access the object. This can be circumvented by freezing the object by calling the freeze method. You would need to call Freeze on your bitmapsource object. Once you have frozen your object it becomes uneditable
Your problem comes about because you are accessing an object which is not created by the background worker thread. Normally this would happen if you access a UI control which is created in the main thread and accessed from different thread.
Use the code below.
Dispatcher.Invoke
(
new Action(
delegate()
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(cache));
using (FileStream file = File.OpenWrite(fName))
{
encoder.Save(file);
}
}
)
);
I think you have to pass cache as a parameter to the new thread:
bw.RunWorkerAsync(cache);
and get it from the DoWork method:
var cache=(CacheType) e.Argument;
.NET framework provides a simple way to get started in threading with
the BackgroundWorker component. This wraps much of the complexity and
makes spawning a background thread relatively safe. In addition, it
allows you to communicate between your background thread and your UI
thread without doing any special coding. You can use this component
with WinForms and WPF applications. The BackgroundWorker offers
several features which include spawning a background thread, the
ability to cancel the background process before it has completed, and
the chance to report the progress back to your UI.
public BackgroudWorker()
{
InitializeComponent();
backgroundWorker = ((BackgroundWorker)this.FindResource("backgroundWorker"));
}
private int DoSlowProcess(int iterations, BackgroundWorker worker, DoWorkEventArgs e)
{
int result = 0;
for (int i = 0; i <= iterations; i++)
{
if (worker != null)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return result;
}
if (worker.WorkerReportsProgress)
{
int percentComplete =
(int)((float)i / (float)iterations * 100);
worker.ReportProgress(percentComplete);
}
}
Thread.Sleep(100);
result = i;
}
return result;
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
int iterations = 0;
if (int.TryParse(inputBox.Text, out iterations))
{
backgroundWorker.RunWorkerAsync(iterations);
startButton.IsEnabled = false;
cancelButton.IsEnabled = true;
outputBox.Text = "";
}
}
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
// TODO: Implement Cancel process
this.backgroundWorker.CancelAsync();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// e.Result = DoSlowProcess((int)e.Argument);
var bgw = sender as BackgroundWorker;
e.Result = DoSlowProcess((int)e.Argument, bgw, e);
}
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
workerProgress.Value = e.ProgressPercentage;
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
outputBox.Text = "Canceled";
workerProgress.Value = 0;
}
else
{
outputBox.Text = e.Result.ToString();
workerProgress.Value = 0;
}
startButton.IsEnabled = true;
cancelButton.IsEnabled = false;
}

Categories

Resources