The following is part of my View in which I have bound an Image to a property in my ViewModel:
<Image Source="{Binding Image}" Grid.Column="2" Grid.ColumnSpan="2"/>
My ViewModel is this:
public class MainWindowViewModel : INotifyPropertyChanged
{
public BitmapImage Image
{
get { return _image; }
set
{
_image = value;
OnPropertyChanged();
}
}
Action _makeScannerAlwaysOnAction;
private BitmapImage _image;
public MainWindowViewModel()
{
AddNewPersonCommand = new RelayCommand(OpenFrmAddNewPerson);
FingerPrintScannerDevice.FingerPrintScanner.Init();
MakeScannerAlwaysOn(null);
}
private void MakeScannerAlwaysOn(object obj)
{
_makeScannerAlwaysOnAction = MakeScannerOn;
_makeScannerAlwaysOnAction.BeginInvoke(Callback, null);
}
private void Callback(IAsyncResult ar)
{
FingerPrintScannerDevice.FingerPrintScanner.UnInit();
var objFingerPrintVerifier = new FingerPrintVerifier();
objFingerPrintVerifier.StartVerifingProcess();
var ms = new MemoryStream();
ms.Position = 0;
objFingerPrintVerifier.MatchPerson.Picture.Save(ms, ImageFormat.Png);
var bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
Thread.Sleep(2000);
Dispatcher.CurrentDispatcher.Invoke(() => Image = bi);
//Image = bi;
_makeScannerAlwaysOnAction.BeginInvoke(Callback, null);
}
private void MakeScannerOn()
{
while (true)
{
if (FingerPrintScannerDevice.FingerPrintScanner.ScannerManager.Scanners[0].IsFingerOn)
{
return;
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
My Problem:
The problem is when I want to bind the Image it gives me the error
Must create DependencySource on same Thread as the DependencyObject
I have googled a lot and I have seen the post in SO but neither of them worked for me.
any kind of help would be very appreciated.
BitmapImage is DependencyObject so it does matter on which thread it has been created because you cannot access DependencyProperty of an object created on another thread unless it's a Freezable object and you can Freeze it.
Makes the current object unmodifiable and sets its IsFrozen property to true.
What you need to do is call Freeze before you update Image:
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
bi.Freeze();
Dispatcher.CurrentDispatcher.Invoke(() => Image = bi);
as pointed out by #AwkwardCoder here is Freezable Objects Overview
While bi.Freeze(); worked for me in one case, I've seen no difference from adding/removing
Dispatcher.CurrentDispatcher.Invoke(() => Image = bi);
The second time I used DataTemplate in xaml and all the same classes as in the first case, but I kept getting the same Error.
This was a thing that helped:
Application.Current.Dispatcher.Invoke(() => Image = bi);
Maybe accepted answer can be improved because Dispatcher.CurrentDispatcher don't actually give you UI thread.
Related
I'm trying to load a image asynchronously.
MainWindow code
public partial class MainWindow : Window
{
private Data data = new Data();
public MainWindow()
{
InitializeComponent();
this.DataContext = data;
}
private async void button_Click(object sender, RoutedEventArgs e)
{
data.Image = await Data.GetNewImageAsync();
}
}
Data class
public class Data : INotifyPropertyChanged
{
private BitmapImage _Image = new BitmapImage();
public BitmapImage Image { get { return _Image; } set { _Image = value; OnPropertyChanged("Image"); } }
public static BitmapImage GetNewImage()
{
return new BitmapImage(new Uri("http://www.diseno-art.com/news_content/wp-content/uploads/2012/09/2013-Jaguar-F-Type-1.jpg"));
}
public async static Task<BitmapImage> GetNewImageAsync()
{
return await Task.Run(() => GetNewImage());
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
WPF code
<Button Name="button" Click="button_Click">Image</Button>
<Image Grid.Row="1" Source="{Binding Path=Image, UpdateSourceTrigger=PropertyChanged}"></Image>
Problem
I get the exception:
System.ArgumentException: "Must create DependencySource on same Thread
as the DependencyObject."
... in this row:
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
But if i change BitmapImage to string this code works fine.
What am I doing wrong?
When a BitmapImage is created in a background thread, you have to make sure that it gets frozen before it is used in the UI thread.
You would have to load it yourself like this:
public static async Task<BitmapImage> GetNewImageAsync(Uri uri)
{
BitmapImage bitmap = null;
var httpClient = new HttpClient();
using (var response = await httpClient.GetAsync(uri))
{
if (response.IsSuccessStatusCode)
{
using (var stream = new MemoryStream())
{
await response.Content.CopyToAsync(stream);
stream.Seek(0, SeekOrigin.Begin);
bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();
}
}
}
return bitmap;
}
Or shorter with BitmapFrame.Create, which returns an already frozen BitmapSource:
public static async Task<BitmapSource> GetNewImageAsync(Uri uri)
{
BitmapSource bitmap = null;
var httpClient = new HttpClient();
using (var response = await httpClient.GetAsync(uri))
{
if (response.IsSuccessStatusCode)
{
using (var stream = new MemoryStream())
{
await response.Content.CopyToAsync(stream);
stream.Seek(0, SeekOrigin.Begin);
bitmap = BitmapFrame.Create(
stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
}
}
return bitmap;
}
Note that the second method requires to change the type of your Image property to BitmapSource (or even better, ImageSource), which would provide greater flexibility anyway.
An alternative method without any manual download might look like shown below. It also does not require to freeze the BitmatImage, because it is not created in a Task thread.
public static Task<BitmapSource> GetNewImageAsync(Uri uri)
{
var tcs = new TaskCompletionSource<BitmapSource>();
var bitmap = new BitmapImage(uri);
if (bitmap.IsDownloading)
{
bitmap.DownloadCompleted += (s, e) => tcs.SetResult(bitmap);
bitmap.DownloadFailed += (s, e) => tcs.SetException(e.ErrorException);
}
else
{
tcs.SetResult(bitmap);
}
return tcs.Task;
}
Hello I'm new around here, used to be rather passive reader.
I'm learning WPF + MVVM, and I think I'm missing something in the whole binding concept.
I'm using flycapture2 SDK for point grey camera, according to attached examples I should call _ProgressChanged on image receive event and bind received image to the image.source property
private void m_worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
BitmapSource image = (BitmapSource)e.UserState;
this.Dispatcher.Invoke(DispatcherPriority.Render,
(ThreadStart)delegate ()
{
image.Source = image;
}
);
}
But that doesn't look good to me, because we just overwrite the source image every time a new image arrives.
What I tried to do instead (following some online tutorials) was to bind the Image control source property through ViewModel property Images_s with the window code behind (setting DataContext to ViewModel) and then I would expect that everytime I change the viewModel.Images_s it should update the UI.
Unfortunately that doesn't work and instead I got an empty window.
Also - Do I need to dispatch this task? As I imagine binding should update UI itself on the event of image variable change in the Window code behind (Or am I overestimating WPF super powers?)
Thanks,
<Image Name="myImage" Source="{Binding Image_s}" Stretch="UniformToFill"/>
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private BitmapSource img;
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public BitmapSource Image_s
{
get { return this.img; }
set
{
this.img = value;
this.NotifyPropertyChanged("Image_s");
}
}
Window code:
viewModel = new ViewModel();
this.DataContext = viewModel;
ViewModel:
private void m_worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
BitmapSource image = (BitmapSource)e.UserState;
this.Dispatcher.Invoke(DispatcherPriority.Render,
(ThreadStart)delegate ()
{
viewModel.Image_s = image;
}
);
}
I'm working in a WPF application where I show my images in two places which means the same image gets loaded in two places. In one of the place the image will be shown along with other few images in a slider where it will be able to edit and save. If there is no image available in the location I should be showing a separate image Image not found which is not editable.
When I started working on the functionality I got the Used by another process exception during edit and save. So after searching I came up with a solution and now at a rare time I get the Out of memory exception when I click the Next or Previous or First or Last in slider. The slider is just an Image control with 4 buttons. When the buttons are clicked the below method is called. I'm not sure if there is any memory leaks.
bool NoImage = true;
private static readonly object _syncRoot = new object();
private BitmapSource LoadImage(string path)
{
lock (_syncRoot) //lock the object so it doesn't get executed more than once at a time.
{
BitmapDecoder decoder = null;
try
{
//If the image is not found in the folder, then show the image not found.
if (!File.Exists(path) && (path != null))
{
System.Drawing.Bitmap ss = XXXX.Resources.ImageNotFound;
var stream = new System.IO.MemoryStream();
if (!File.Exists(Path.GetTempPath() + "ImageNotFound.jpg"))
{
FileStream file = new FileStream(Path.GetTempPath() + "ImageNotFound.jpg", FileMode.Create, FileAccess.Write);
ss.Save(stream, ImageFormat.Jpeg);
stream.Position = 0;
stream.WriteTo(file);
file.Close();
stream.Close();
}
path = Path.Combine(Path.GetTempPath(), "ImageNotFound.jpg");
NoImage = false;
}
else
{
if (!EnableForEdit)
NoImage = false;
else
NoImage = true;
}
if (!string.IsNullOrEmpty(path) && (!NoImage || File.Exists(path)))
{
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
return decoder.Frames.FirstOrDefault();
}
else
return null;
}
catch (OutOfMemoryException ex)
{
MessageBox.Show("Insufficient memory to handle the process. Please try again later.", "Application alert");
return null;
}
catch (Exception ex)
{
// Error handling.
throw new ApplicationException(ex.Message);
}
finally
{
decoder = null;
GC.WaitForFullGCComplete(1000);
GC.Collect(0, GCCollectionMode.Forced);
}
}
}
<Image x:Name="viewImage" Grid.Row="2" Height="100" Width="135" Source="{Binding DisplayImage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" />
If my approach is wrong, let me know where should I do the change or if there is any simpler way to do. Kindly help.
Note: The images which are loaded is above 5Mb
Firstly when ever you create a stream you need to dispose of it once you are finished with it (Note Close does not Dispose, but Dispose does close), if not then the stream stays in memory consuming resources
so your code should look as follows
using(var stream = new System.IO.MemoryStream())
{
if (!File.Exists(Path.GetTempPath() + "ImageNotFound.jpg"))
{
using(FileStream file = new FileStream(Path.GetTempPath() + "ImageNotFound.jpg", FileMode.Create, FileAccess.Write))
{
ss.Save(stream, ImageFormat.Jpeg);
stream.Position = 0;
stream.WriteTo(file);
}
}
}
Second you need to reduce your apps memory impact
to do that i would suggest leveraging the functionality already in WPF here is a quick example of how you should do this
Your Model
public class ImageItem
{
public Uri URI{ get; set; }
private BitmapSource _Source;
public BitmapSource Source
{
get
{
try
{
if (_Source == null) _Source = new BitmapImage(URI);//lazy loading
}
catch (Exception)
{
_Source = null;
}
return _Source;
}
}
public void Save(string filename)
{
var img = BitmapFrame.Create(Source);
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(img);
using(var saveStream = System.IO.File.OpenWrite(filename))
encoder.Save(saveStream)
}
}
Your View Model
public class ImageList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ImageItem> Images { get; } = new ObservableCollection<ImageItem>();
private int _SelectedIndex;
// c# >= 6
public static readonly PropertyChangedEventArgs SelectedIndexProperty = new PropertyChangedEventArgs(nameof(SelectedIndex));
// c# < 6
// public static readonly PropertyChangedEventArgs SelectedIndexProperty = new PropertyChangedEventArgs("SelectedIndex");
public int SelectedIndex
{
get { return _SelectedIndex; }
set
{
_SelectedIndex = value;
// c# >= 6
PropertyChanged?.Invoke(this, SelectedIndexProperty);
PropertyChanged?.Invoke(this, CurrentImageProperty);
// c# < 6
// var handler = PropertyChanged;
// if(handler !=null)
// {
// handler (this, SelectedIndexProperty);
// handler (this, CurrentImageProperty);
// }
}
}
// c# >= 6
public static readonly PropertyChangedEventArgs CurrentImageProperty = new PropertyChangedEventArgs(nameof(CurrentImage));
// c# < 6
// public static readonly PropertyChangedEventArgs CurrentImageProperty = new PropertyChangedEventArgs("CurrentImage");
public ImageItem CurrentImage => Images.Count>0 ? Images[SelectedIndex] : null;
public void Next()
{
if (SelectedIndex < Images.Count - 1)
SelectedIndex++;
else
SelectedIndex = 0;
}
public void Back()
{
if (SelectedIndex == 0)
SelectedIndex = Images.Count - 1;
else
SelectedIndex--;
}
public void Add(string Filename)
{
Images.Add(new ImageItem() { URI= new Uri(Filename) });
// c# >= 6
PropertyChanged?.Invoke(this, CurrentImageProperty);
// c# < 6
// var handler = PropertyChanged;
// if(handler !=null)
// {
// handler (this, CurrentImageProperty);
// }
}
}
and Finally your View
<Window x:Class="ImageDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ImageDemo"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BitmapImage x:Key="NotFound" UriSource="C:\...\NotFound.png"/>
</Window.Resources>
<Window.DataContext>
<local:ImageList/>
</Window.DataContext>
<DockPanel>
<Button Content="<" Click="Back_Click"/>
<Button DockPanel.Dock="Right" Content=">" Click="Next_Click"/>
<Image Source="{Binding CurrentImage.Source, Mode=OneWay,
TargetNullValue={StaticResource NotFound},
FallbackValue={StaticResource NotFound}}"/>
</DockPanel>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//c# >= 6
private ImageList list => DataContext as ImageList;
//c# < 6
//private ImageList list {get{ return DataContext as ImageList;}}
private void Next_Click(object sender, RoutedEventArgs e)
{
list.Next();
}
private void Back_Click(object sender, RoutedEventArgs e)
{
list.Back();
}
}
note:
because the Model is separate to the View you can show the same image in several places with no issue at all
also System.Drawing.Bitmap is not WPF compatible so you should use the WPF classes in System.Windows.Media.Imaging
I have the following XAML code :
<Image x:Name="armImage" Source="{Binding PlayerImage}">
</Image>
Here's what's happenning behind the scenes.I have this property :
private BitmapImage playerImage;
public BitmapImage PlayerImage
{
get { return playerImage; }
set
{
this.playerImage = value;
this.PropertyChanged(this, new PropertyChangedEventArgs("PlayerImage"));
}
}
I set it like this :
private void GameStarted(object sender, EventArgs.GameStartedEventArgs e)
{
if (e.IsUIHidden)
{
MainPlayer = new Player(0, new Uri("/Images/arm.bmp", UriKind.Relative));
this.PlayerImage = DisplayImage(MainPlayer.ImageUri);
}
}
Where the DisplayImage method looks like this :
private BitmapImage DisplayImage(Uri imageUri)
{
if (imageUri != null)
{
return new BitmapImage(imageUri);
}
else
{
throw new InvalidOperationException();
}
}
The issue is the following - the image in the UI doesn't change when i set the property PlayerImage.I tried doing it without the MVVM pattern and the Uri works and the image is displayed,but when I try to do it that way it doesn't work?
Your property name is PlayerImage not PlayerImageUri change it in PropertyChanged call
this.PropertyChanged(this, new PropertyChangedEventArgs("PlayerImageUri"));
should be
this.PropertyChanged(this, new PropertyChangedEventArgs("PlayerImage"));
I am trying to bind an image in a wpf application. I am using vs2010.
I am pasting code below and explain what I have done, what works and what doesn't.
XAML code:
<Image Name="newImage" ImageFailed="newImage_ImageFailed" HorizontalAlignment="Right" Width="auto" Height="auto" Margin="5" Source="{Binding imgSource}">
C# code:
public MainWindow()
{
InitializeComponent();
arraytoImage atim = new arraytoImage();
newImage.DataContext = atim;
}
Code below is in different namespace, where arraytoImage class is implemented. This class takes a cuda array, creates a bitmap and then converts it in into a bitmapimage using memorystream. For now, I am setting a random color to all the pixels, just to see if that binding works. But it doesn't. Below I have pasted a code that displays the image.
I am sure that bitmapimage is correctly created. I think the issue is incorrect binding.
class arraytoImage : INotifyPropertyChanged
{
// displays images (focused files)
private BitmapImage bitmapImage = new BitmapImage();
private BitmapImage testim = new BitmapImage();
public BitmapImage arraytoImageCon(cuFloatComplex[] dataIn, int wid, int ht)
{
//code that generates bitmapimage
}
public BitmapImage imgSource
{
get { return testim1; }
set
{
if (testim1 != value)
{
testim1 = 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
}
Edit: calling arrayToImageCon:
public class ReadRawFiles
{
//Tons of code
public void focusdata()
{
//tons of code
arraytoImage atoi = new arraytoImage();
BitmapImage tmp= atoi.arraytoImageCon(datafft_azi, nazimuth,nrange);
atoi.imgSource=tmp;
}
}
My question is, what am I doing wrong.
Thanks a lot in advance. Kindly ask further details if I missed something.
Regards
binding is set to one instance. I was making multiple instances.