Images in Background Workers in WPF Application - c#

I have the following code snippet that I use to create a List to add to a scrollviewer as a binding in a WPF application:
private void LoadThumbs(object sender, DoWorkEventArgs e)
{
//ClearScreen();
int max = (int)e.Argument;
int current = 0;
foreach (string filename in filenames)
{
Image thumbnail = new Image();
Uri image_path = new Uri(filename);
BitmapImage image = new BitmapImage(image_path);
Thickness thumb_margin = thumbnail.Margin;
thumb_margin.Bottom = 2.5;
thumb_margin.Top = 2.5;
thumb_margin.Left = 2.5;
thumb_margin.Right = 2.5;
thumbnail.Margin = thumb_margin;
thumbnail.Width = 100;
image.DecodePixelWidth = 200;
thumbnail.Source = image;
thumbnail.Tag = filename;
thumbnail.MouseDown += image_Click;
thumbnail.MouseEnter += hand_Over;
thumbnail.MouseLeave += normal_Out;
images.Add(thumbnail);
thumbnail = null;
}
}
This worked fine until I added a BackgroundWorker to process this. Now, when execution gets to
Image thumbnail = new Image();
I get the following exception:
System.InvalidOperationException: 'The calling thread must be STA, because many UI components require this.'
Two questions:
(1) How can I process this code to allow the background worker to work on Image, or, (2) is there a better way to do what I am doing to allow for the BackgroundWorker to work?
I have zero experience working in a multi-threaded environment. I want it to work this way because the largest record I process has 180 images and creates about a 10-15 second hang.

Do not create Image elements in code behind. Instead, use an ItemControl with an appropriate ItemTemplate:
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Images}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Bind it to a view model like shown below, which is capable of asynchronously loading the image files. It is important that the BitmapImages are frozen to make them cross-thread accessible.
public class ViewModel
{
public ObservableCollection<ImageSource> Images { get; }
= new ObservableCollection<ImageSource>();
public async Task LoadImagesAsync(IEnumerable<string> filenames)
{
foreach (var filename in filenames)
{
Images.Add(await Task.Run(() => LoadImage(filename)));
}
}
public ImageSource LoadImage(string filename)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.DecodePixelWidth = 200;
bitmap.UriSource = new Uri(filename);
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
}
which is initialized like this:
private ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
...
await viewModel.LoadImagesAsync(..., "*.jpg"));
}
An alternative view model method could load the BitmapImages directly from a FileStream instead of an Uri:
public ImageSource LoadImage(string filename)
{
using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.DecodePixelWidth = 200;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
}

Related

UWP how to upload a list of images from different folders to different pivotItems

I'm creating an UWP App image gallery and I want to upload a list of images
with a single method changing folder location to multipes PivotItems with a single method
public async void precargar()
{
List<StackPanel> spanel = new List<StackPanel>();
IReadOnlyList<StorageFile> files = await Imagefolder.GetFilesAsync();
foreach (var item in files)
{
StackPanel stack = new StackPanel();
StorageItemThumbnail thumbnail = null;
try { thumbnail = await item.GetThumbnailAsync(ThumbnailMode.PicturesView); }
catch (Exception) { System.Diagnostics.Debug.WriteLine("esto es un error lo sentimos"); }
BitmapImage bi;
if (thumbnail == null)
{
bi = new BitmapImage(new Uri("ms-appx:///wallpaper/2.png"));
}
else
{
Stream stream = thumbnail.AsStream();
bi = new BitmapImage();
await bi.SetSourceAsync(stream.AsRandomAccessStream());
}
Image image = new Image() { Width = 300 };
image.Source = bi;
stack.Children.Add(image);
spanel.Add(stack);
}
Viewtiles.ItemsSource = spanel;
}
the above code works correctly
in this way:
and I use it in this way to load them to the interface
public async void CargarFolders()
{
Imagefolder = await appInstalledFolder.GetFolderAsync(carpetas[0]);
precargar();
}
Now I want to use that code to load other lists of images using the same code in the following way:
public async void Naturaleza()
{
Imagefolder = await appInstalledFolder.GetFolderAsync(carpetas[1]);
precargar();
Naturals.ItemsSource = spanel;
}
but it does not work. How can I do it?
I think you should use FlipView control rather than pivot control for displaying image. Because FlipView control has move forward and backward option with touch friendly. You can set large sets of images to FlipView control.
Please go through https://learn.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/flipview

WPF Image source doesn't update image

I'm trying to have a rotating ad display in my WPF application. When I load the application the GetNewAd() method properly displays the advertisement. When I try to update the ad by calling my GetNewAd() method again, the chatHost is returning the new ad, but the image does not update in the UI. I have tried updating the image without the animation, but I still have the same problem. What am I missing here?
public class ncWindow
{
public ncWindow(Grid grid)
{
imgAd = new Image();
imgAd.Margin = new Thickness(2,2,2,2);
imgAd.HorizontalAlignment = HorizontalAlignment.Left;
imgAd.MouseDown += imgAd_MouseDown;
adTimer.Interval = 60000;
adTimer.Elapsed += adTimer_Elapsed;
adTimer.AutoReset = true;
adTimer.Start();
grid.Children.Add(imgAd);
}
public void GetNewAd()
{
DisplayedAd = chatHost.GetNewAd();
debug.Print("GetNewAd: " + DisplayedAd.VendorName + ", ImageData.Length = " + DisplayedAd.ImageData.Length);
BitmapImage image = new BitmapImage();
if (DisplayedAd.ImageData!=null && DisplayedAd.ImageData.Length>0)
{
using (var mem = new MemoryStream(DisplayedAd.ImageData))
{
mem.Position = 0;
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = mem;
image.EndInit();
}
}
else
return;
image.Freeze();
imgAd.Source = image;
}
private void adTimer_Elapsed(object source, ElapsedEventArgs e)
{
GetNewAd();
}
}
If the timer that calls your GetNewAd() method is not a DispatcherTimer, you'll explicitly have to invoke the assignment of the Image control's Source property in the UI thread:
image.Freeze(); // necessary for cross-thread access
imgAd.Dispatcher.BeginInvoke(new Action(() => imgAd.Source = image));

Aforge.NET Webcam Control XAML page

I want to use a webcam in c# code with Aforge.NET, but It didin't work because I don't use forms.
See my code below :
public partial class CapturePage : Page
{
private bool DeviceExist = false;
private FilterInfoCollection videoDevices;
private VideoCaptureDevice videoSource = null;
public CapturePage()
{
InitializeComponent();
getCamList();
startVideo();
}
// get the devices name
private void getCamList()
{
try
{
videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
if (videoDevices.Count == 0)
throw new ApplicationException();
DeviceExist = true;
}
catch (ApplicationException)
{
DeviceExist = false;
}
}
//toggle start and stop button
private void startVideo() // as it's say
{
if (DeviceExist)
{
videoSource = new VideoCaptureDevice(videoDevices[0].MonikerString); // the only one webcam
videoSource.NewFrame += new NewFrameEventHandler(video_NewFrame);
CloseVideoSource();
// videoSource.DesiredFrameSize = new Size(160, 120); // deprecated ?
//videoSource.DesiredFrameRate = 10;
videoSource.Start();
}
else
{
// error
}
}
else
{
if (videoSource.IsRunning)
{
timer1.Enabled = false;
CloseVideoSource();
}
}
}
//eventhandler if new frame is ready
private void video_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
Bitmap img = (Bitmap)eventArgs.Frame.Clone();
//do processing here
pictureBox1.Image = img; // But I can't have pictureBox in xaml ??
}
//close the device safely
private void CloseVideoSource()
{
if (!(videoSource == null))
if (videoSource.IsRunning)
{
videoSource.SignalToStop();
videoSource = null;
}
}
}
}
I tried this code in a form application, and it's work, but with a page, PictureBox are not referenced. Even if I reference it and create one in the code, this didn't work.
Can I, and how, use a PictureBox in a XAML Page ? Or there is an other solution tu use Aforge.net Webcam in Xaml Page ?
I succeded with changing the NewFrame methode like this:
private void video_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
System.Drawing.Image imgforms = (System.Drawing.Bitmap)eventArgs.Frame.Clone();
BitmapImage bi = new BitmapImage();
bi.BeginInit();
MemoryStream ms = new MemoryStream();
imgforms.Save(ms, ImageFormat.Bmp);
ms.Seek(0, SeekOrigin.Begin);
bi.StreamSource = ms;
bi.EndInit();
//Using the freeze function to avoid cross thread operations
bi.Freeze();
//Calling the UI thread using the Dispatcher to update the 'Image' WPF control
Dispatcher.BeginInvoke(new ThreadStart(delegate
{
ImageWebcam.Source = bi; /*frameholder is the name of the 'Image' WPF control*/
}));
}
cheers !
You could use WindowsFormsHost to integrate this Page in your WPF application.
If you need a WPF bitmap you may "convert" it like desribed here: Load a WPF BitmapImage from a System.Drawing.Bitmap

How to display a downloaded image in WPF window

I need to download a webcam image thru http, and refresh it at 10fps and display it on a WPF window. Right now I'm using this code:
Window1 wndMain;
BitmapImage img;
DispatcherTimer tmrRefresh;
public WCam(Window1 wndMain, string imguri)
{
this.wndMain = wndMain;
this.MouseLeftButtonDown += delegate { DragMove(); };
url = imguri;
InitializeComponent();
tmrRefresh = new DispatcherTimer(TimeSpan.FromMilliseconds(100),
DispatcherPriority.Normal, Refresh, Dispatcher.CurrentDispatcher);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (url != "")
{
try
{
img = new BitmapImage();
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
img.UriSource=(new Uri(url));
img.EndInit();
}
catch (Exception ex)
{
new WPopup().Show(ex.Message);
}
ImgBox.Source = img;
tmrRefresh.Start();
}
}
public void Refresh(object sender, EventArgs e)
{
try
{
img = new BitmapImage();
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
img.UriSource = (new Uri(url));
img.EndInit();
}
catch (Exception ex)
{
new WPopup().Show(ex.Message);
}
ImgBox.Source = null;
ImgBox.Source = img;
}
It displays nothing, if I increase the timer interval to 1000 it displays images but the image goes away while it loads the next one. Also the window loads in awfully slowly.
Seperate the loading logic from the image.
Keep the loaded code as is, the refresh should download the image with a simple async(to keep ui running) request and only when finished change the image source to the local memory.

Asynchronously Loading a BitmapImage in C# using WPF

What's the best way to asynchronously load an BitmapImage in C# using WPF?
I was just looking into this and had to throw in my two cents, though a few years after the original post (just in case any one else comes looking for this same thing I was looking into).
I have an Image control that needs to have it's image loaded in the background using a Stream, and then displayed.
The problem that I kept running into is that the BitmapSource, it's Stream source and the Image control all had to be on the same thread.
In this case, using a Binding and setting it's IsAsynch = true will throw a cross thread exception.
A BackgroundWorker is great for WinForms, and you can use this in WPF, but I prefer to avoid using the WinForm assemblies in WPF (bloating of a project is not recommended, and it's a good rule of thumb too). This should throw an invalid cross reference exception in this case too, but I didn't test it.
Turns out that one line of code will make any of these work:
//Create the image control
Image img = new Image {HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch};
//Create a seperate thread to load the image
ThreadStart thread = delegate
{
//Load the image in a seperate thread
BitmapImage bmpImage = new BitmapImage();
MemoryStream ms = new MemoryStream();
//A custom class that reads the bytes of off the HD and shoves them into the MemoryStream. You could just replace the MemoryStream with something like this: FileStream fs = File.Open(#"C:\ImageFileName.jpg", FileMode.Open);
MediaCoder.MediaDecoder.DecodeMediaWithStream(ImageItem, true, ms);
bmpImage.BeginInit();
bmpImage.StreamSource = ms;
bmpImage.EndInit();
//**THIS LINE locks the BitmapImage so that it can be transported across threads!!
bmpImage.Freeze();
//Call the UI thread using the Dispatcher to update the Image control
Dispatcher.BeginInvoke(new ThreadStart(delegate
{
img.Source = bmpImage;
img.Unloaded += delegate
{
ms.Close();
ms.Dispose();
};
grdImageContainer.Children.Add(img);
}));
};
//Start previously mentioned thread...
new Thread(thread).Start();
Assuming you're using data binding, setting Binding.IsAsync property to True seems to be a standard way to achieve this.
If you're loading the bitmap in the code-behind file using background thread + Dispatcher object is a common way to update UI asynchronous
This will allow you to create the BitmapImage on the UI thread by using the HttpClient to do the async downloading:
private async Task<BitmapImage> LoadImage(string url)
{
HttpClient client = new HttpClient();
try
{
BitmapImage img = new BitmapImage();
img.CacheOption = BitmapCacheOption.OnLoad;
img.BeginInit();
img.StreamSource = await client.GetStreamAsync(url);
img.EndInit();
return img;
}
catch (HttpRequestException)
{
// the download failed, log error
return null;
}
}
To elaborate onto aku's answer, here is a small example as to where to set the IsAsync:
ItemsSource="{Binding IsAsync=True,Source={StaticResource ACollection},Path=AnObjectInCollection}"
That's what you would do in XAML.
BitmapCacheOption.OnLoad
var bmp = await System.Threading.Tasks.Task.Run(() =>
{
BitmapImage img = new BitmapImage();
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.UriSource = new Uri(path);
img.EndInit();
ImageBrush brush = new ImageBrush(img);
}
Use or extend System.ComponentModel.BackgroundWorker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Personally, I find this to be the easiest way to perform asynchronous operations in client apps. (I've used this in WinForms, but not WPF. I'm assuming this will work in WPF as well.)
I usually extend Backgroundworker, but you dont' have to.
public class ResizeFolderBackgroundWorker : BackgroundWorker
{
public ResizeFolderBackgroundWorker(string sourceFolder, int resizeTo)
{
this.sourceFolder = sourceFolder;
this.destinationFolder = destinationFolder;
this.resizeTo = resizeTo;
this.WorkerReportsProgress = true;
this.DoWork += new DoWorkEventHandler(ResizeFolderBackgroundWorker_DoWork);
}
void ResizeFolderBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
DirectoryInfo dirInfo = new DirectoryInfo(sourceFolder);
FileInfo[] files = dirInfo.GetFiles("*.jpg");
foreach (FileInfo fileInfo in files)
{
/* iterate over each file and resizing it */
}
}
}
This is how you would use it in your form:
//handle a button click to start lengthy operation
private void resizeImageButtonClick(object sender, EventArgs e)
{
string sourceFolder = getSourceFolderSomehow();
resizer = new ResizeFolderBackgroundWorker(sourceFolder,290);
resizer.ProgressChanged += new progressChangedEventHandler(genericProgressChanged);
resizer.RunWorkerCompleted += new RunWorkerCompletedEventHandler(genericRunWorkerCompleted);
progressBar1.Value = 0;
progressBar1.Visible = true;
resizer.RunWorkerAsync();
}
void genericRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Visible = false;
//signal to user that operation has completed
}
void genericProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
//I just update a progress bar
}

Categories

Resources