I have problem with converter from Uri to BitmapImage. Uri is url of image on web. I use this converter on item in listbox.
I download image from webpage and create from this stream BitampImage
Problem is if listbox consist about 100 - 250 items, app freeze, I try call WebRequestMethod in another thread but it don’t work.
Here is root part of code:
private static BitmapImage GetImgFromAzet(int sex, Uri imgUri)
{
try
{
if (imgUri == null)
{
if (sex == (int)Sex.Man)
{
return new BitmapImage(new Uri(#"pack://application:,,,/Spirit;Component/images/DefaultAvatars/man.jpg",
UriKind.RelativeOrAbsolute));
}
else
{
return new BitmapImage(new Uri(#"pack://application:,,,/Spirit;Component/images/DefaultAvatars/woman.jpg",
UriKind.RelativeOrAbsolute));
}
}
else
{
BitmapImage image = null;
Task.Factory.StartNew(() =>
{
WebRequest webRequest = WebRequest.CreateDefault(imgUri);
webRequest.ContentType = "image/jpeg";
WebResponse webResponse = webRequest.GetResponse();
image = new BitmapImage();
image.CreateOptions = BitmapCreateOptions.None;
image.CacheOption = BitmapCacheOption.OnLoad;
image.BeginInit();
image.StreamSource = webResponse.GetResponseStream();
image.EndInit();
return image;
//((System.Action)(() =>
//{
// //webResponse.Close();
//})).OnUIThread();
});
return image;
}
}
catch (Exception)
{
//default
return
new BitmapImage(new Uri(PokecUrl.Avatar,UriKind.RelativeOrAbsolute));
}
}
My aim is download image from web, create BitamImage object from him and return as Source of Image control, but I need avoid app freezing. Also problem is if I close webResponse it broke all code.
EDITED:
I try this:
BitmapImage image;
WebRequest req = WebRequest.CreateDefault(imgUri);
req.ContentType = "image/jpeg";
using (var res = req.GetResponse())
{
image = new BitmapImage();
image.CreateOptions = BitmapCreateOptions.None;
image.CacheOption = BitmapCacheOption.OnLoad;
image.BeginInit();
image.UriSource = imgUri;
image.StreamSource = res.GetResponseStream();
image.EndInit();
}
but somewhere must be bug, code is broken.
Any advice?
Binding converter is always executed on UI thread. So you could start other thread in Convert method but eventually (as you need feedback from this thread) you have to wait until it completes, thereby you're blocking your app.
In order to solve this problem, for example, you could use Binding.IsAsync property:
public class ListItemViewData
{
private readonly Uri _uri;
private readonly Sex _sex;
ListItemViewData(Uri uri, Sex sex)
{
this._uri = uri;
this._sex = sex;
}
public BitmapSource Image
{
get
{
// Do synchronous WebRequest
}
}
}
Usage in xaml (inside DataTemplate of listbox item):
<Image Source="{Binding Path=Image, IsAsync=True}"/>
EDITED
I've dived into BitmapImage class and have found out that it has pretty ctor with Uri parameter, that works asynchronously.
So you shouldn't execute WebRequest by yourself. Do just like this:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var uri = (Uri)value;
return new BitmapImage(uri) { CacheOption = BitmapCacheOption.None };
}
EDITED 2
Your view data class.
public class ListItemViewData : INotifyPropertyChanged
{
public ListItemViewData(Uri uri)
{
this._uri = uri;
}
private readonly Uri _uri;
public Uri Uri
{
get
{
return this._uri;
}
}
private BitmapSource _source = null;
public BitmapSource Image
{
get
{
return this._source;
}
set
{
this._source = value;
this.OnPropertyChanged("Image");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string p)
{
var pc = this.PropertyChanged;
if (pc!=null)
{
pc(this, new PropertyChangedEventArgs(p));
}
}
}
Helper, that executes images downloading:
public static class WebHelper
{
public static Stream DownloadImage(Uri uri, string savePath)
{
var request = WebRequest.Create(uri);
var response = request.GetResponse();
using (var stream = response.GetResponseStream())
{
Byte[] buffer = new Byte[response.ContentLength];
int offset = 0, actuallyRead = 0;
do
{
actuallyRead = stream.Read(buffer, offset, buffer.Length - offset);
offset += actuallyRead;
}
while (actuallyRead > 0);
File.WriteAllBytes(savePath, buffer);
return new MemoryStream(buffer);
}
}
}
When you are filling model - you should start separate thread, which will download files and set up images source.
this._listItems.Add(new ListItemViewData(new Uri(#"http://lifeboat.com/images/blue.ocean.jpg")));
//...
var sc = SynchronizationContext.Current;
new Thread(() =>
{
foreach (var item in this._listItems)
{
var path = "c:\\folder\\"+item.Uri.Segments.Last();
var stream = WebHelper.DownloadImage(item.Uri, path);
sc.Send(p =>
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = (Stream)p;
bi.EndInit();
item.Image = bi;
}, stream);
}
}).Start();
Related
I have a view that contains an image control that is binded to this property:
private System.Drawing.Image _sigImage;
public System.Drawing.Image sigImage
{
get { return _sigImage; }
set { _sigImage = value; RaisePropertyChanged(); }
}
I am busy implementing a a signature pad using mvvm, and want the signature to display in the image control. However i cant get it to display.
The code for die signature pad is:
DynamicCapture dc = new DynamicCaptureClass();
DynamicCaptureResult res = dc.Capture(sigCtl, "Who", "Why", null, null);
if (res == DynamicCaptureResult.DynCaptOK)
{
sigObj = (SigObj)sigCtl.Signature;
sigObj.set_ExtraData("AdditionalData", "C# test: Additional data");
try
{
byte[] binaryData = sigObj.RenderBitmap("sign", 200, 150, "image/png", 0.5f, 0xff0000, 0xffffff, 10.0f, 10.0f, RBFlags.RenderOutputBinary | RBFlags.RenderColor32BPP) as byte[];
using (MemoryStream memStream = new MemoryStream(binaryData))
{
System.Drawing.Image newImage = System.Drawing.Image.FromStream(memStream);
sigImage = newImage;
// work with image here.
// You'll need to keep the MemoryStream open for
// as long as you want to work with your new image.
memStream.Dispose();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
The image is stored as a bitmap in the variable newImage.
How can i bind that image to the image control of sigImage?
System.Drawing.Image is not an appropriate type for the Source property of an Image element. It is WinForms, not WPF.
Use System.Windows.Media.ImageSource instead
private ImageSource sigImage;
public ImageSource SigImage
{
get { return sigImage; }
set { sigImage = value; RaisePropertyChanged(); }
}
and assign a BitmapImage or BitmapFrame to the property, which is directly created from the MemoryStream. BitmapCacheOption.OnLoad has to be set in order to enable closing the stream immediately after decoding the bitmap.
var bitmapImage = new BitmapImage();
using (var memStream = new MemoryStream(binaryData))
{
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = memStream;
bitmapImage.EndInit();
}
bitmapImage.Freeze();
SigImage = bitmapImage;
The Binding would look like shown below, provided an instance of the class with the SigImage property is assigned to the DataContext of the view.
<Image Source="{Binding SigImage}"/>
Since WPF has built-in type conversion from string, Uri and byte[] to ImageSource, you may as well declare the source property as byte[]
private byte[] sigImage;
public byte[] SigImage
{
get { return sigImage; }
set { sigImage = value; RaisePropertyChanged(); }
}
and assign a value like
SigImage = binaryData;
without manually creating a BitmapImage or BitmapFrame, or changing the Binding.
So, i have a function that will load an image from disk async in an other thread ( big images will be loaded and I don't want the UI THread to be locked while loading).
Loading is done like this
public override void LoadFile()
{
using (var imageStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
Decoder = new TiffBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
InitializeFile();
}
}
Then I want to use the Decoder on the main thread
public List<ThumbnailModel> LoadPages()
{
var result = new List<ThumbnailModel>();
foreach (var frame in Decoder.Frames) <--// this line throws exception
{
result.Add(new ThumbnailModel
{
Name = _metadataLoader.GetPageName((BitmapMetadata)frame.Metadata),
Bitmap = new WriteableBitmap(frame)
});
}
return result;
}
Now here is the problem, whenever I reach the line where I try to access the Decoder.Frames it throws exception (The calling thread cannot access this object because a different thread owns it.)
Is there a way I can use my Decoder in the main thread if not, the only possible solution is to load all the image information in the other thread?
Full code version :
// this is the task, that calls the imageFactory LoadFile method - NewThread
private async Task OpenFileAsync(string strFilePath)
{
var newFile = _imageFileFactory.LoadFile(strFilePath);
if (newFile != null)
{
_imagefile = newFile;
}
}
//image factory load file - NewThread
public IImageFile LoadFile(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
{
return null;
}
var fileExtension = Path.GetExtension(filePath); // .tiff or .jpeg
var file = new ImageFileTiff(filePath, _metatadaFactory, _metadataVersioner);
file.LoadFile();
return file;
}
// ImageFileTiff LoadFile will create a decoder - NewThread
public override void LoadFile()
{
using (var imageStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
Decoder = new JpegBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
InitializeFile();
}
}
After we have an IImageFile we call on MainThread(UIThread)
var pages = _imagefile.LoadPages();
Where LoadPages is the place where the app breaks. also called on UIThread
public List LoadPages()
{
var result = new List();
foreach (var frame in Decoder.Frames)
{
result.Add(new ThumbnailModel
{
Name = _metadataLoader.GetPageName((BitmapMetadata)frame.Metadata),
Bitmap = new WriteableBitmap(frame)
});
}
return result;
}
I thought you could simply return the decoder from the thread to be able to access it but your decoder is a TiffBitmapDecoder which inherits from DispatcherObject (https://learn.microsoft.com/en-gb/dotnet/api/system.windows.threading.dispatcherobject?view=netcore-3.1).
So you won't be able to access it from a different thread than the one where it was created msdn:"Only the thread that the Dispatcher was created on may access the DispatcherObject directly"
What you could do instead is use the decoder in it's thread and return the final result:
I couldn't build on your sample since there was to much missing for me to test it but I built a similar project to give an exemple:
public partial class MainWindow : Window
{
public MainWindow()
{
}
public TiffBitmapDecoder LoadFile()
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.InitialDirectory = "c:\\";
openFileDialog.Filter = "tiff files (*.tif)|*.tif|All files (*.*)|*.*";
openFileDialog.FilterIndex = 2;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == true && !string.IsNullOrEmpty(openFileDialog.FileName))
{
//I didn't bother to check the file extension since it's just an exemple
using (var imageStream = openFileDialog.OpenFile())
{
return new TiffBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
}
}
else
{
//User cancelled
return null;
}
}
public List<ThumbnailModel> LoadPages(TiffBitmapDecoder decoder)
{
//TiffBitmapDecoder" inherits from DispatcherObject/>
//https://learn.microsoft.com/en-gb/dotnet/api/system.windows.threading.dispatcherobject?view=netcore-3.1
var result = new List<ThumbnailModel>();
if (decoder != null)
{
try
{
foreach (var frame in decoder.Frames)
{
result.Add(new ThumbnailModel
{
//set the variables
});
}
}
catch(InvalidOperationException e)
{
MessageBox.Show(e.Message, "Error");
}
}
else
{
//Nothing to do
}
return result;
}
private async Task AsyncLoading()
{
this.thumbnailModels = await Task.Run<List<ThumbnailModel>>(() =>
{
var decoder = this.LoadFile();
return this.LoadPages(decoder);
});
}
private List<ThumbnailModel> thumbnailModels = null;
private async void AsyncLoadingButton_Click(object sender, RoutedEventArgs e)
{
await this.AsyncLoading();
}
}
public class ThumbnailModel
{
}
Content of MainWindow.xaml just in case:
<Grid>
<StackPanel Orientation="Vertical">
<Button x:Name="NoReturnButton" Margin="10" HorizontalAlignment="Center" Content="Call AsyncLoadingNoReturn" Click="AsyncLoadingButton_Click" />
</StackPanel>
</Grid>
Hello I'm writing a WPF program that gets has thumbnails inside a ThumbnailViewer. I want to generate the Thumbnails first, then asynchronously generate the images for each thumbnail.
I can't include everything but I think this is whats relevant
Method to generate the thumbnails.
public async void GenerateThumbnails()
{
// In short there is 120 thumbnails I will load.
string path = #"C:\....\...\...png";
int pageCount = 120;
SetThumbnails(path, pageCount);
await Task.Run(() => GetImages(path, pageCount);
}
SetThumbnails(string path, int pageCount)
{
for(int i = 1; i <= pageCount; i ++)
{
// Sets the pageNumber of the current thumbnail
var thumb = new Thumbnail(i.ToString());
// Add the current thumb to my thumbs which is
// binded to the ui
this._viewModel.thumbs.Add(thumb);
}
}
GetImages(string path, int pageCount)
{
for(int i = 1; i <= pageCount; i ++)
{
Dispatcher.Invoke(() =>
{
var uri = new Uri(path);
var bitmap = new BitmapImage(uri);
this._viewModel.Thumbs[i - 1].img.Source = bitmap;
});
}
}
When I run the code above it works just as if I never add async/await/task to the code. Am I missing something? Again What I want is for the ui to stay open and the thumbnail images get populated as the GetImage runs. So I should see them one at a time.
UPDATE:
Thanks to #Peregrine for pointing me in the right direction. I made my UI with custom user controls using the MVVM pattern. In his answer he used it and suggested that I use my viewModel. So what I did is I add a string property to my viewModel and made an async method that loop though all the thumbnails and set my string property to the BitmapImage and databound my UI to that property. So anytime it would asynchronously update the property the UI would also update.
The Task that runs GetImages does virtually nothing but Dispatcher.Invoke, i.e. more or less all your code runs in the UI thread.
Change it so that the BitmapImage is created outside the UI thread, then freeze it to make it cross-thread accessible:
private void GetImages(string path, int pageCount)
{
for (int i = 0; i < pageCount; i++)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(path);
bitmap.EndInit();
bitmap.Freeze();
Dispatcher.Invoke(() => this._viewModel.Thumbs[i].img.Source = bitmap);
}
}
You should also avoid any async void method, excpet when it is an event handler. Change it as shown below, and await it when you call it:
public async Task GenerateThumbnails()
{
...
await Task.Run(() => GetImages(path, pageCount));
}
or just:
public Task GenerateThumbnails()
{
...
return Task.Run(() => GetImages(path, pageCount));
}
An alternative that altogether avoids async/await is a view model with an ImageSource property whose getter is called asynchronously by specifying IsAsync on the Binding:
<Image Source="{Binding Image, IsAsync=True}"/>
with a view model like this:
public class ThumbnailViewModel
{
public ThumbnailViewModel(string path)
{
Path = path;
}
public string Path { get; }
private BitmapImage îmage;
public BitmapImage Image
{
get
{
if (îmage == null)
{
îmage = new BitmapImage();
îmage.BeginInit();
îmage.CacheOption = BitmapCacheOption.OnLoad;
îmage.UriSource = new Uri(Path);
îmage.EndInit();
îmage.Freeze();
}
return îmage;
}
}
}
It looks as though you've been mislead by the constructor of BitmapImage that can take a Url.
If this operation really is slow enough to justify using the async-await pattern, then you would be much better off dividing it into two sections.
a) Fetching the data from the url. This is the slow part - it's IO bound, and would benefit most from async-await.
public static class MyIOAsync
{
public static async Task<byte[]> GetBytesFromUrlAsync(string url)
{
using (var httpClient = new HttpClient())
{
return await httpClient
.GetByteArrayAsync(url)
.ConfigureAwait(false);
}
}
}
b) Creating the bitmap object. This needs to happen on the main UI thread, and as it's relatively quick anyway, there's no gain in using async-await for this part.
Assuming that you're following the MVVM pattern, you shouldn't have any visual elements in the ViewModel layer - instead use a ImageItemVm for each thumbnail required
public class ImageItemVm : ViewModelBase
{
public ThumbnailItemVm(string url)
{
Url = url;
}
public string Url { get; }
private bool _fetchingBytes;
private byte[] _imageBytes;
public byte[] ImageBytes
{
get
{
if (_imageBytes != null || _fetchingBytes)
return _imageBytes;
// refresh ImageBytes once the data fetching task has completed OK
Action<Task<byte[]>> continuation = async task =>
{
_imageBytes = await task;
RaisePropertyChanged(nameof(ImageBytes));
};
// no need for await here as the continuations will handle everything
MyIOAsync.GetBytesFromUrlAsync(Url)
.ContinueWith(continuation,
TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith(_ => _fetchingBytes = false)
.ConfigureAwait(false);
return null;
}
}
}
You can then bind the source property of an Image control to the ImageBytes property of the corresponding ImageItemVm - WPF will automatically handle the conversion from byte array to a bitmap image.
Edit
I misread the original question, but the principle still applies. My code would probably still work if you made a url starting file:// but I doubt it would be the most efficient.
To use a local image file, replace the call to GetBytesFromUrlAsync() with this
public static async Task<byte[]> ReadBytesFromFileAsync(string fileName)
{
using (var file = new FileStream(fileName,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
4096,
useAsync: true))
{
var bytes = new byte[file.Length];
await file.ReadAsync(bytes, 0, (int)file.Length)
.ConfigureAwait(false);
return bytes;
}
}
Rather than involving the the dispatcher and jumping back and forth, I'd do something like this:
private Task<BitmapImage[]> GetImagesAsync(string path, int pageCount)
{
return Task.Run(() =>
{
var images = new BitmapImage[pageCount];
for (int i = 0; i < pageCount; i++)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(path);
bitmap.EndInit();
bitmap.Freeze();
images[i] = bitmap;
}
return images;
}
}
Then, on the UI thread calling code:
var images = await GetImagesAsync(path, pageCount);
for (int i = 0; i < pageCount; i++)
{
this._viewModel.Thumbs[i].img.Source = images[i];
}
I've saved an image in my DataBase in a parameter type image and I've done this with:
private void btnAceptar_Click(object sender, RoutedEventArgs e)
{
UsuariosBLL bll = new UsuariosBLL();
UsuariosBO user = new UsuariosBO();
PerfilBO perfil = cmbperf.SelectedItem as PerfilBO;
//........
user.Imagen = ConvertImageToByteArray(ruta);
bll.InsertarFilaUsuarios(user);
MessageBox.Show("Se insertó");
//.......
}
where the method ConvertToByteArray convert the image selected in a Byte Array
public byte[] ConvertImageToByteArray(string path)
{
byte[] ImageByte=null;
try
{
FileStream fs = new FileStream(path,FileMode.Open,FileAccess.Read);
BinaryReader br = new BinaryReader(fs);
ImageByte = br.ReadBytes((int)fs.Length);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return ImageByte;
}
and now I just wanna retrieve the image selecting a different user in my combobox.
I've tried like this:
private void cmbUsuarios_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UsuariosBO user = e.AddedItems[0] as UsuariosBO;
//.....
MemoryStream ms = new MemoryStream(user.Imagen);
System.Drawing.Image img = System.Drawing.Image.FromStream(ms);
imgFoto = img;
usuario = user;
}
But a error born:
Can not implicitly convert type 'System.Drawing.Image' to 'System.Windows.Controls.Image'
I understand what it means but I don't know how to fix it...
thanks !!
Instead of creating a System.Drawing.Image from the byte stream, you should create a WPF ImageSource and assign that to the Source property of your ImageControl.
One way to do this is by creating a BitmapImage:
using (var ms = new MemoryStream(user.Imagen))
{
var img = new BitmapImage();
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.StreamSource = ms;
img.EndInit();
imgFoto.Source = img;
}
Or alternatively a BitmapFrame:
using (var ms = new MemoryStream(user.Imagen))
{
imgFoto.Source = BitmapFrame.Create(ms,
BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
I want to show Image in my WPF Window.
I have put this code to do so.
<Image x:Name="ImageControl" Stretch="Fill" Margin="2" Source="{Binding imgSource}"/>
and in code behind I have put,
public ImageSource imgSource
{
get
{
logo = new BitmapImage();
logo.BeginInit();
logo.UriSource = new Uri(#"C:\MyFolder\Icon.jpg");
logo.EndInit();
return logo;
}
}
This code shows image fine but I also should be able to change image runtime, That is, I want to replace Icon.jpg with another Image.
MyFolder is the folder path that will contain an Image "Icon.jpg" (Name would always be same).
So whenever I try to replace Icon.jpg with anyother Image, I get an error That Image file in Use
Can Anyone suggest how to overcome this issue. Please let me know if I need to clear my question.
Thanks in Anticipation.
Implement INotifyPropertyChanged in the class.
Change your property to a "get" "set"
And don't forget to set the DataContext.
Here is the code:
public class MyClass : INotifyPropertyChanged
{
private string imagePath;
public string ImagePath
{
get { return imagePath; }
set
{
if (imagePath != value)
{
imagePath = value;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(ImagePath);
bitmapImage.EndInit();
imgSource = bitmapImage;
}
}
}
public BitmapImage logo;
public ImageSource imgSource
{
get { return logo; }
set
{
if (logo != value)
{
logo = value;
OnPropertyChanged("imgSource");
}
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
UPDATE
BitmapImage is known to keep the file loaded when passing the path using string.
Load with a FileStream instead. BitmapImage as on demand loading capability set by default. To foce the bitmap to load the image on EndInit you have to change the ChacheOption:
using (FileStream stream = File.OpenRead(#"C:\MyFolder\Icon.jpg"))
{
logo = new BitmapImage();
logo.BeginInit();
logo.StreamSource = stream;
logo.CacheOption = BitmapCacheOption.OnLoad;
logo.EndInit();
}