Display loading while Caching image from URL - c#

I have a code in WPF C# that i use to load images from the web as such:
if (myImgURL != "")
{
var imgBitmap = new BitmapImage();
imgBitmap.BeginInit();
imgBitmap.UriSource = new Uri(myImgURL, UriKind.RelativeOrAbsolute);
imgBitmap.CacheOption = BitmapCacheOption.OnLoad;
imgBitmap.EndInit();
myImgControl.Source = imgBitmap;
}
It works perfectly but sometimes it takes a while before the images get displayed (if internet is slow and all). How can I have a ProgressRing (from the Mahapps.Metro toolkit) display and be enabled while the image loads and then disappear when the image is displayed?
I am not aware of any event trigger for when an image is being downloaded and when it is fully loaded.

Take a look at the following events in class BitmapSource (the base class of BitmapImage):
DownloadProgress
DownloadCompleted
DownloadFailed
And just a note. You are creating a BitmapImage from an Uri and showing it immediately. Hence there is no need to set BitmapCacheOption.OnLoad (which afaik is necessary only if you load from a stream that should be closed immediately after EndInit). So you could shorten your code like this:
if (!string.IsNullOrEmpty(myImgURL))
{
var imgBitmap = new BitmapImage(new Uri(myImgURL));
myImgControl.Source = imgBitmap;
if (imgBitmap.IsDownloading)
{
// start download animation here
imgBitmap.DownloadCompleted += (o, e) =>
{
// stop download animation here
};
imgBitmap.DownloadFailed += (o, e) =>
{
// stop download animation here
};
}

Related

UI freezes at the moment Image control loads BitmapImage [duplicate]

This question already has an answer here:
Load a large BitmapImage asynchronously
(1 answer)
Closed 2 years ago.
My WPF MVVM application loads an image from the given URL asynchronously, through Webclient.DownloadFileAsync(url, fileLocation). That process goes fine and smooth, no freezes at all when downloading a picture. But the problem occurs when I present the image file to the user - an application becomes unresponsive.
After file is downloaded, I assign the image file to the BitmapImage:
public async void LoadFileToBitmapImage(string filePath)
{
_isDownloading = false;
await FileToBitmapImage(filePath);
}
public Task FileToBitmapImage(string filePath)
{
return Task.Run(() =>
{
var executableLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var imageLocation = Path.Combine(executableLocation, filePath);
var bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri(imageLocation);
bi.EndInit();
bi.Freeze();
Image = bi;
});
}
Image.cs:
private BitmapImage _image;
public BitmapImage Image
{
get => _image;
set
{
_image = value;
NotifyOfPropertyChange("Image");
}
}
XAML Image Binding:
<Image Source="{Binding Image, IsAsync=True}" Margin="3"/>
The problem occurs when the image is downloaded and presenting it to the user. The bigger an image, the more time it takes to present an image to the user and the more time an application is unresponsive.
I tried clicking pause at that very time when the application freezes to check threads and get the following info and unfortunately it doesn't provide me with any information.
Any help will be much appreciated!
Edit
Worth noting that application becomes unresponsive after PropertyChanged event is raised, not before. Maybe it's something to do with rendering an image to the UI?
first, if you save the image, change the binding to a string/uri directly, no BitmapImage, no nned to create it, Wpf handle that for you
public BitmapImage Image ===> public Uri Image
and remove FileToBitmapImage.
I spent a few days to find a simple solution to this problem. I needed to display over a hundred images in high quality without UI freezing.
I tried various modifications of the binding and so on in the end only the creation of the Image control through the code and set of the Source property worked before Image appeared in the tree of interface elements.
In XAML only empty ContentControl:
<ContentControl x:Name="ImageContent"/>
In code:
static readonly ConcurrentExclusiveSchedulerPair _pair = new ConcurrentExclusiveSchedulerPair();
// Works for very big images
public void LoadImage(Uri imageUri)
{
var image = new System.Windows.Controls.Image(); // On UI thread
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
Task.Factory.StartNew(() =>
{
var source = new BitmapImage(imageUri); // load ImageSource
Dispatcher.RunOnMainThread(() =>
{
image.Source = source; // Set source while not in UI
// Add image in UI tree
ImageContent.Content = image; // ImageContent is empty ContentControl
ImageContent.InvalidateVisual();
});
}, default, TaskCreationOptions.LongRunning, _pair.ExclusiveScheduler);
}
Works better with loading image with CacheOption OnLoad.
public static ImageSource BitmapFromUri(Uri source)
{
if (source == null)
return new BitmapImage(source);
using (var fs = new FileStream(source.LocalPath, FileMode.Open))
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = fs;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
}

C# add a click event to grid childs

How can I add a click event to child objects I have created in my Code Behind?
String[] imageNames = System.IO.Directory.GetFiles("Assets/Images/Gallery");
foreach (String image in imageNames)
{
Uri source = new Uri("ms-appx:///" + image);
ImageSource imgSource = new BitmapImage(source);
Image myImage = new Image();
myImage.Source = imgSource;
gallery.Children.Add(myImage);
}
The above code gets all the images from the gallery folder and adds them to a variablesizedwrapgrid. I want to add a click event on each of these (to basically allow a user to save them to the device they are using)
Thanks
1)
myImage.PointerReleased += Img_PointerReleased;
private void Img_PointerReleased(object sender, PointerRoutedEventArgs e)
{
// do your job
}
2) or write UserControl wrapping Image that inherits from Button and add proper VisualStates if you don't want to have default button animations - this way you will be able to use Clicked event

Windows phone create animation of controls

I need to load some images and show on grid (with simple animation). I have
Image img = IconWithId(i);
// set some properties
//...
img.Tap += img_Tap;
img.ImageOpened += img_ImageOpened;
gameView.Children.Add(img);
In event img_ImageOpened i create animation
void img_ImageOpened(object sender, RoutedEventArgs e)
{
Image img = sender as Image;
Storyboard story = new Storyboard();
DoubleAnimation dAnimation = new DoubleAnimation();
dAnimation.From = 0;
dAnimation.To = 1;
dAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(100));
Storyboard.SetTarget(dAnimation, img);
Storyboard.SetTargetProperty(dAnimation, new PropertyPath(OpacityProperty));
story.Children.Add(dAnimation);
story.Begin();
}
I think i have animation which should show image but it don't work. Image loaded without animation. What can i do?
PS i need to animation when images will close. I think to use event unloaded
Sorry for my english
I tested your code on a Windows Phone 8 with Phone 8 SDK
Your code works perfectly fine.
You need to change the duration to a longer period. 100ms way too fast. You won't be able to see it quick enough.
Change it to something longer like this
dAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(5000));

Resizing custom user control according to data in the webBrowser control docked in it

I have a webBrowser control named webBrowser1 that is added and docked as DockStyle.Full on a custom user control. The web-browser accepts some HTML text dynamically and displays it. I disabled the scroll bars of the webBrowser control. My problem is that whenever the content is somewhat lengthy, the webBrowser hides it from below. But the requirement of my project objective is that the webBrowser must not show either scroll bars or it should not hide some of the content. The content must be completely shown as it is without scrolling. That means the user control on which the webBrowser is docked must resize itself according to webBrowser's content. So, can anyone please suggest me how to achieve this? I searched all over the internet and SO but found nothing.
You can get the current size of HTML window via WebBrowser.Document.Window.Size and resize the container control accordingly. Depending on how your WebBrowser control content receives dynamic updates, you'd probably need to do this after each update. You could also try WebBrowser.Document.Body.ScrollRectangle if Document.Window.Size doesn't grow in the expected way.
[EDITED] The following code works for me (IE10):
private void Form1_Load(object sender, EventArgs e)
{
this.BackColor = System.Drawing.Color.DarkGray;
this.webBrowser.ScrollBarsEnabled = false;
this.webBrowser.Dock = DockStyle.None;
this.webBrowser.Location = new System.Drawing.Point(0, 0);
this.webBrowser.Size = new System.Drawing.Size(320, 200);
DownloadAsync("http://www.example.com").ContinueWith((task) =>
{
var html = task.Result;
MessageBox.Show(String.Format(
"WebBrowser.Size: {0}, Document.Window.Size: {1}, Document.Body.ScrollRectangle: {2}\n\n{3}",
this.webBrowser.Size,
this.webBrowser.Document.Window.Size,
this.webBrowser.Document.Body.ScrollRectangle.Size,
html));
this.webBrowser.Size = this.webBrowser.Document.Body.ScrollRectangle.Size;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
async Task<string> DownloadAsync(string url)
{
TaskCompletionSource<bool> onloadTcs = new TaskCompletionSource<bool>();
WebBrowserDocumentCompletedEventHandler handler = null;
handler = delegate
{
this.webBrowser.DocumentCompleted -= handler;
// attach to subscribe to DOM onload event
this.webBrowser.Document.Window.AttachEventHandler("onload", delegate
{
// each navigation has its own TaskCompletionSource
if (onloadTcs.Task.IsCompleted)
return; // this should not be happening
// signal the completion of the page loading
onloadTcs.SetResult(true);
});
};
// register DocumentCompleted handler
this.webBrowser.DocumentCompleted += handler;
// Navigate to url
this.webBrowser.Navigate(url);
// continue upon onload
await onloadTcs.Task;
// the document has been fully loaded, can access DOM here
// return the current HTML snapshot
return ((dynamic)this.webBrowser.Document.DomDocument).documentElement.outerHTML.ToString();
}
To resize your usercontrol you first need to get the size needed by the content. This can be achived with TextRender.MeasureText, like so:
public static int GetContentHeight(string content, Control contentHolder, Font contentFont)
{
Font font = (contentFont != null) ? contentFont : contentHolder.Font;
Size sz = new Size(contentHolder.Width, int.MaxValue);
int padding = 3;
int borders = contentHolder.Height - contentHolder.ClientSize.Height;
TextFormatFlags flags = TextFormatFlags.WordBreak;
sz = TextRenderer.MeasureText(content, contentHolder.Font, sz, flags);
int cHeight = sz.Height + borders + padding;
return cHeight;
}
In your case it's a bit more tricky, as the text contains HTML-tags wich needs to be filtered away, to get the correct height.. I belive this can be achived with RegEx or a simple algo wich removes all content between < and > from a string.. You may also have to create special handlig for some HTML-tags (I.E Lists)

dynamically handling events for an image

I'm currently working on a program where there are multiple "icons" dynamically added to a window. Each one of these "icons" (which is actually a image with a label underneath it) represents a device in a network. When the user double clicks on an icon, it needs to open a window where the user can change the properties of that specific device.
I already have the window created, along with the code to dynamically add images to a window. My problem is that since each icon is dynamically added, they use the same event handler. since each icon uses the same handler, it seems impossible to have each icon open its respective device.
Here is a part of the code which is run when a user adds a new device along with the event handler it uses:
//create new device
devices.Add(new Device(ipaddress, hn, un, pw, cm, lx, ly, tp, pl, nt, dn));
images.Add(new Image());
//create image for main window
images[images.Count - 1].Width = 50;
images[images.Count - 1].Height = 35;
images[images.Count - 1].Stretch = Stretch.Fill;
BitmapImage logo = new BitmapImage();
logo.BeginInit();
logo.UriSource = new Uri(deviceImages[tp], UriKind.Relative);
logo.CacheOption = BitmapCacheOption.OnLoad;
logo.EndInit();
images[images.Count - 1].Source = logo;
images[images.Count - 1].Cursor = Cursors.Hand;
images[images.Count - 1].Margin = new System.Windows.Thickness(lx-25, ly-25, 0, 0);
images[images.Count - 1].VerticalAlignment = System.Windows.VerticalAlignment.Top;
images[images.Count - 1].HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
images[images.Count - 1].MouseDown += new MouseButtonEventHandler(deviceIcon_MouseDown);
ConnexMainWindow.grid1.Children.Add(images[images.Count - 1]);
public void deviceIcon_MouseDown(object sender, MouseButtonEventArgs e)
{
ConnexDeviceWindow deviceWindow = new ConnexDeviceWindow();
deviceWindow.Show();
}
As you can see, I'm not currently passing the device window the device since there isn't really any way to do so from the event handler.
My question is: Is there a way to dynamically create an event handler, for each image i add, so that i can pass the function the proper device that each image represents?
One common strategy to identify the control which fired the event is to use the Tag property of the control to store some information.
I don't know what your device model looks like, but let's assume the ip-address is unique among all the devices. You can attach that to the Image control:
images[images.Count - 1].Tag = ipaddress
In your event handler you can grab that tag to find the device (assuming the devices collection is available on class-level):
public void deviceIcon_MouseDown(object sender, MouseButtonEventArgs e)
{
Image image = e.Source as Image;
if (image != null)
{
string ipAddress = (string)image.Tag;
Device device = devices.FirstOrDefault(d => d.IpAddress == ipAddress);
if (device != null)
{
ConnexDeviceWindow deviceWindow = new ConnexDeviceWindow(device);
deviceWindow.Show();
}
}
}

Categories