I have been dealing with a problem with a backgroundWorker these last couple of days. I have been looking through forums and documentation on MSDN but still haven't found the answer so now I want to ask you clever people.
Long story short, I have a custom user control consisting of a WrapPanel inside a ScrollViewer. The WrapPanel contains some elements that are notified when they are scrolled into view.
The elements are then supposed to load and display an image, and this is where the problem comes in. In order to not lock the gui thread i load the images in a BackgroundWorker, but the GUI stalls anyways. This is the code for the class that represents the elements contained in the WrapPanel:
class PictureThumbnail : INotifyingWrapPanelElement
{
private string path;
private Grid grid = null;
private BackgroundWorker thumbnailBackgroundCreator = new BackgroundWorker();
private delegate void GUIDelegate();
private Image thumbnailImage = null;
public PictureThumbnail(String path)
{
this.path = path;
visible = false;
thumbnailBackgroundCreator.DoWork += new DoWorkEventHandler(thumbnailBackgroundCreator_DoWork);
}
void thumbnailBackgroundCreator_DoWork(object sender, DoWorkEventArgs e)
{
BitmapImage bi = LoadThumbnail();
bi.Freeze(); //If i dont freeze bi then i wont be able to access
GUIDelegate UpdateProgressBar = delegate
{
//If this line is commented out the GUI does not stall. So it is not the actual loading of the BitmapImage that makes the GUI stall.
thumbnailImage.Source = bi;
};
grid.Dispatcher.BeginInvoke(UpdateProgressBar);
}
public void OnVisibilityGained(Dispatcher dispatcher)
{
visible = true;
thumbnailImage = new Image();
thumbnailImage.Width = 75;
thumbnailImage.Height = 75;
//I tried setting the thumbnailImage.Source to some static BitmapImage here, and that does not make the GUI stall. So it is only when it is done through the GUIDelegate for some reason.
grid.Children.Add(thumbnailImage);
thumbnailBackgroundCreator.RunWorkerAsync();
}
private BitmapImage LoadThumbnail()
{
BitmapImage bitmapImage = new BitmapImage();
// BitmapImage.UriSource must be in a BeginInit/EndInit block
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(path);
bitmapImage.DecodePixelWidth = 75;
bitmapImage.DecodePixelHeight = 75;
bitmapImage.EndInit();
return bitmapImage;
}
}
I have added some comments in the code explaining some stuff I tried and what leads I have. But I'll write it again here. If I just load the BitmapImage in the backgroundWorker, but don't apply it as the Source of the thumbnailImage the GUI doesn't stall (but no image is displayed obviously). Also if I set the Source of the thumbnailImage to some preloaded static BitmapImage in the OnVisibilityGained method (So in the GUI thread), then the GUI wont stall, so it is not the actual setting of the Image.Source that's the culprit.
You should make use of the reporting feature of the backgroundworker which lets you directly access the controls of your form without invoking.
Related
I want to store my own Emojis before using them in a (Rich)TextBlock.
Now it does not allow me to create a Windows.Controls.Image outside of the Main thread...
private static void AddEmojiToString(List<Inline> block, BitmapImage source)
{
var textRun = new Run("");
System.Windows.Controls.Image emoji = new System.Windows.Controls.Image();
emoji.Height = 15;
emoji.Width = 15;
emoji.VerticalAlignment = VerticalAlignment.Center;
emoji.Source = source;
block.Add(new InlineUIContainer(emo));
}
Is there any way to run this code outside of the main thread? I just want to prepare and store the text to be able to display it later.
Everything works fine if called from the Main thread.
Thank you for your help!
I have a winforms application where user can select an image from a list of available images and the corresponding image is shown in a PictureBox. The images can be very huge with a minimum of 10MB. This obviously makes the rest of the UI unresponsive while the image loads. So I thought of loading the image on a separate thread using the following code:
private void LoadImage()
{
// loadViewerThread is a Thread object
if (loadViewerThread != null && loadViewerThread.IsAlive)
{
loadViewerThread.Abort(); // Aborting the previous thread if the user has selected another image
}
loadViewerThread = new Thread(SetViewerImage);
loadViewerThread.Start();
}
The SetViewerImage function is as below:
private void SetViewerImage()
{
if (pictureBox1.Image != null)
pictureBox1.Image.Dispose();
pictureBox1.Image = new Bitmap(/*Some stream*/);
}
After this the images load smoothly and the UI can also be accessed.
But if the user moves very fast between the set of images then a big red X mark comes up. This happens because I have that call to Dispose in SetViewerImage.
I have assigned an ErrorImage to the PictureBox but that ErrorImage is never shown in this case.
Questions:
Is there anything wrong with my thread implementation? Why does the
Image gets disposed?
Is there any way that I can display a different ErrorImage and not
the red X?
You need to manipulate controls in the UI thread. You can do this by using Control.Invoke() from a different thread.
The big bottleneck is creating the image from the stream, so you should be able to reorganize your method like this to keep the UI thread freed up:
private void SetViewerImage()
{
Bitmap image = new Bitmap(/* Some stream*/);
pictureBox1.Invoke(new Action(() =>
{
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
pictureBox1.Image = null; // This might help? Only add if it does.
}
pictureBox1.Image = image;
}));
}
I have an issue that I think has not been covered in the multitude of other WPF image loading issues. I am scanning in several images and passing them to a "Preview Page". The preview page takes the image thumbnails and displays what a printout would look like via a generated bitmap.
The weird thing to me is, it will work fine if I run the program the first time. Upon reaching the end of the process and hitting "start over", the preview will return blank. I am creating the BitmapImage in a method that saves the bitmap as a random file name so I do not believe theres a lock on the file the second time around. Also, if I go to look at the temporary file created through explorer, it is drawn correctly so I know the appropriate data is getting to it.
Finally, when I navigate away from this page, I am clearing necessary data. I'm really perplexed and any help would be appreciated.
//Constructor
public Receipt_Form() {
InitializeComponent();
printData = new List<Object>();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e) {
// populates global variable fileName
var task = System.Threading.Tasks.Task.Factory.StartNew(() => outputToBitmap()); task.ContinueWith(t => setImage(fileName),
System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext());
// I started the image creation in a separate thread because I
// thought it may be blocking the UI thread, but it didn't matter
}
private void setImage(string imageURI) {
BitmapImage image;
using (FileStream stream = File.OpenRead(imageURI)) {
image = new BitmapImage();
image.BeginInit();
image.StreamSource = stream;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
}
receiptPreview.Source = image;
//this works the first iteration but not the second, though the temp file is created successfully
}
Found the issue - the Modern UI container was getting cleared when transitioning off the page.
I am trying to get the height and width of an image from websites but it always return 0, as the image is not yet downloaded so i used the following code and still didnt work as the image will start downloading only after the method end, so it hang
someMethod
{
foreach(string imagepath in paths){
IsDownloaded = false;
image = new BitmapImage(new Uri(imagepath));
image.ImageOpened += image_ImageOpened;
while (!IsDownloaded) ;
/// code that will use image.PixelHeight only if it satisfy a condition then break
}
private void image_ImageOpened(object sender, RoutedEventArgs e)
{
IsDownloaded = true;
}
Does anyone have any alternative or any fix for this supported in metro style apps
You can't use asynchronous programming like that - remove this line:
while (!IsDownloaded) ;
And put everything after it inside the image_ImageOpened method.
We usually refer to this as 'chaining', when you have a bunch of asynchronous methods, you have to continue processing after the completion of each one.
An example from my own code of getting width/height:
BitmapImage imageSource = new BitmapImage();
private void getImage()
{
Uri uir= new Uri("PATH", UriKind.Absolute);
imageSource.ImageOpened += new EventHandler<RoutedEventArgs>(imageopenened);
}
void imageopened(object sender, RoutedEventArgs e)
{
HEIGHT = ImageSource.PixelHeight;
WIDTH = ImageSource.PixelWidth;
...
}
In my (Silverlight) weather app I am downloading up to 6 seperate weather radar images (each one taken about 20 mins apart) from a web site and what I need to do is display each image for a second then at the end of the loop, pause 2 seconds then start the loop again. (This means the loop of images will play until the user clicks the back or home button which is what I want.)
So, I have a RadarImage class as follows, and each image is getting downloaded (via WebClient) and then loaded into a instance of RadarImage which is then added to a collection (ie: List<RadarImage>)...
//Following code is in my radar.xaml.cs to download the images....
int imagesToDownload = 6;
int imagesDownloaded = 0;
RadarImage rdr = new RadarImage(<image url>); //this happens in a loop of image URLs
rdr.FileCompleteEvent += ImageDownloadedEventHandler;
//This code in a class library.
public class RadarImage
{
public int ImageIndex;
public string ImageURL;
public DateTime ImageTime;
public Boolean Downloaded;
public BitmapImage Bitmap;
private WebClient client;
public delegate void FileCompleteHandler(object sender);
public event FileCompleteHandler FileCompleteEvent;
public RadarImage(int index, string imageURL)
{
this.ImageIndex = index;
this.ImageURL = imageURL;
//...other code here to load in datetime properties etc...
client = new WebClient();
client.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
client.OpenReadAsync(new Uri(this.ImageURL, UriKind.Absolute));
}
private void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error == null)
{
StreamResourceInfo sri = new StreamResourceInfo(e.Result as Stream, null);
this.Bitmap = new BitmapImage();
this.Bitmap.SetSource(sri.Stream);
this.Downloaded = true;
FileCompleteEvent(this); //Fire the event to let the app page know to add it to it's List<RadarImage> collection
}
}
}
As you can see, in the class above I have exposed an event handler to let my app page know when each image has downloaded. When they have all downloaded I then run the following code in my xaml page - but only the last image ever shows up and I can't work out why!
private void ImageDownloadedEventHandler(object sender)
{
imagesDownloaded++;
if (imagesDownloaded == imagesToDownload)
{
AllImagesDownloaded = true;
DisplayRadarImages();
}
}
private void DisplayRadarImages()
{
TimerSingleton.Timer.Stop();
foreach (RadarImage img in radarImages)
{
imgRadar.Source = img.Bitmap;
Thread.Sleep(1000);
}
TimerSingleton.Timer.Start(); //Tick poroperty is set to 2000 milliseconds
}
private void SingleTimer_Tick(object sender, EventArgs e)
{
DisplayRadarImages();
}
So you can see that I have a static instance of a timer class which is stopped (if running), then the loop should show each image for a second. When all 6 have been displayed then it pauses, the timer starts and after two seconds DisplayRadarImages() gets called again.
But as I said before, I can only ever get the last image to show for some reason and I can't seem to get this working properly.
I'm fairly new to WP7 development (though not to .Net) so just wondering how best to do this - I was thinking of trying this with a web browser control but surely there must be a more elegant way to loop through a bunch of images!
Sorry this is so long but any help or suggestions would be really appreciated.
Mike
You can use a background thread with either a Timer or Sleep to periodically update your image control.
Phạm Tiểu Giao - Threads in WP7
You'll need to dispatch updates to the UI with
Dispatcher.BeginInvoke( () => { /* your UI code */ } );
Why don't you add the last image twice to radarImages, set the Timer to 1000 and display just one image on each tick?