Why I don't have access to my wpf-image? [duplicate] - wpf

i know there's a lot of these type of questions. i wanted to post so that i can share my specific prob because im getting frustrated.
im running a thread which query path from db and put it in the image element.problem is, i created the image in xaml so when i run this thread it throws the cannot access this object error which it cant access the image element.
then how do i set it without using xaml??here's my code snippet:
public partial class Window1 : Window
{
Thread Frame1;
public Window1()
{
InitializeComponent();
intializeDb();
#region start frame 1 thread
Frame1 = new Thread(frame1);
Frame1.SetApartmentState(ApartmentState.STA);
Frame1.IsBackground = true;
Frame1.Start();
#endregion
}
public void frame1()
{
string k;
command.CommandText = "SELECT * FROM imageframe1";
sqlConn.Open();
Reader = command.ExecuteReader();
while (Reader.Read())
{
BitmapImage logo = new BitmapImage();
logo.BeginInit();
k = (string)(Reader.GetValue(1));
logo.UriSource = new Uri(k);
logo.EndInit();
image1.Source = logo; //THROWS THE ERROR HERE.IT CANT ACCESS image1
Thread.Sleep(1000);
}
sqlConn.Close();
Reader.Close();
}
how would i access image1 then? if i create a new one within the thread,i will have to put as child of a panel,an then im gonna get an error which it cant access the panel.
any way around this?glad if someone can write an example based on my snippet.
edited with still no success and producing the same error:
public partial class Window1 : Window
{
public readonly SynchronizationContext mySynchronizationContext;
public Window1()
{
InitializeComponent();
mySynchronizationContext = SynchronizationContext.Current;
Frame1 = new Thread(frame1);
Frame1.SetApartmentState(ApartmentState.STA);
Frame1.IsBackground = true;
Frame1.Start();
}
public void frame1()
{
string k;
command.CommandText = "SELECT * FROM imageframe1";
sqlConn.Open();
Reader = command.ExecuteReader();
while (Reader.Read())
{
BitmapImage logo = new BitmapImage();
logo.BeginInit();
k = (string)(Reader.GetValue(1));
logo.UriSource = new Uri(k);
logo.EndInit();
SendOrPostCallback callback = _ =>
{
image1.Source = logo;
};
mySynchronizationContext.Send(callback, null);
//image1.Source = logo;
Thread.Sleep(1000);
}
sqlConn.Close();
Reader.Close();
}
}

As Jon Skeet said, you can use Dispatcher.Invoke to assign the image, but it's not enough, because the BitmapImage has been created on another thread. To be able to use it on the UI thread, you need to Freeze it before:
logo.Freeze();
Action action = delegate { image1.Source = logo; };
image1.Dispatcher.Invoke(action);

You use the Dispatcher associated with the control you want to update:
Action action = delegate { image1.Source = logo; };
image1.Dispatcher.Invoke(action);
Note that using Thread.Sleep like this to perform animation is unlikely to give a very good experience... especially as the display thread then has to fetch the URI in order to display the image. It's not going to be very smooth.

i think this is because you didnt marshal the call to the UI thread. you can do something line this:
save the context in the constructor,
// this is a class member variable
public readonly SynchronizationContext mySynchronizationContext;
// in ctor
MySynchronizationContext = SynchronizationContext.Current;
// in your method , to set the image:
SendOrPostCallback callback = _=>
{
image1.Source = logo;
};
mySynchronizationContext.Send(callback,null);
by the way, its a good practice to use using statements with the SqlConnection and SqlDataReader. as in:
using (SqlConnection conn = new SqlConnection("conn string here"))
{
using (SqlDataReader reader = cmd.ExecuteReader())
{
// db access code
}
}

Related

Refreshing Live Chart on button click event

I have Live Chart that I am trying to perform a fresh of the values on Button_Click event but the Chart is not refreshing.
I have two TextBoxes where the user can select the start and end date they would like to review and use the butn_ExecuteQuery_Click to display the data.
public HBDBreakdown()
{
InitializeComponent();
ChartValues();
}
private void ChartValues()
{
try
{
// Defines the variable for differnt lines.
List<double> SmallCommercialIndustValues = new List<double>();
List<double> ResidentialValues = new List<double>();
List<string> AnalystName = new List<string>();
SqlConnection connection = new SqlConnection("Data Source=WINDOWS-B1AT5HC\\MSSQLSERVER2;Initial Catalog=CustomerRelations;user id=sa; password=Westside2$; Integrated Security=False;");
string selectQuery = ("SELECT Users.TX_EMPLOYEE, SUM(CASE WHEN d .REV_CLS <> 2 THEN 1 ELSE 0 END) AS Residential, SUM(CASE WHEN d .REV_CLS = 2 THEN 1 ELSE 0 END) AS SmallCommercialIndust FROM hb_Disputes AS d INNER JOIN Users ON d.ASSGNTO = Users.KY_USER_ID WHERE(d.OPENED >=#OPENED) AND(d.OPENED < #CLOSED) GROUP BY Users.TX_EMPLOYEE; ");
connection.Open();
SqlCommand command = new SqlCommand(selectQuery, connection);
command.Parameters.Add("#OPENED", SqlDbType.DateTime).Value = dtepicker_Open.Text;
command.Parameters.Add("#CLOSED", SqlDbType.DateTime).Value = dtepicker_DateResolved.Text;
SqlDataReader sqlReader = command.ExecuteReader();
while (sqlReader.Read())
{
// Check for DBNull and then assign the variable
if (sqlReader["SmallCommercialIndust"] != DBNull.Value)
SmallCommercialIndustValues.Add(Convert.ToInt32(sqlReader["SmallCommercialIndust"]));
// Check for DBNull and then assign the variable
if (sqlReader["Residential"] != DBNull.Value)
ResidentialValues.Add(Convert.ToInt32(sqlReader["Residential"]));
// Check for DBNull and then assign the variable
AnalystName.Add(Convert.ToString(sqlReader["TX_EMPLOYEE"]));
}
SeriesCollection = new SeriesCollection
{
new StackedColumnSeries
{
Title = "Residential",
Values = new ChartValues<double>(ResidentialValues),
StackMode = StackMode.Values, // this is not necessary, values is the default stack mode
DataLabels = true
},
new StackedColumnSeries
{
Title = "Small Commercial Indust",
Values = new ChartValues<double>(SmallCommercialIndustValues),
StackMode = StackMode.Values,
DataLabels = true
}
};
Labels = AnalystName.ToArray();
//Formatter = value => value + " Disputes";
DataContext = this;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public SeriesCollection SeriesCollection { get; set; }
public string[] Labels { get; set; }
public Func<double, string> Formatter { get; set; }
private void butn_ExecuteQuery_Click(object sender, RoutedEventArgs e)
{
// Refresh Chart
ChartValues();
}
Set DataContext = null; before you call ChartValues() or implement INotifyPropertyChanged and raise the PropertyChanged event from the setters of the source properties.
Better never reset the DataContext only to update the data binding of certain properties. This results in very bad performance as the complete view will be forced to render again. The DataContext is also inherited down the element tree.
Better write cleaner code and handle dynamic data more gracefully and as intended by the framework (see Data Binding Overview).
Inside a control (or on a DependencyObject in general) you should always implement all properties that are involved in data binding as DependencyProperty.
They will automatically refresh the target and depending on the Binding.Mode also the source of the specific Binding.
Since you are reassigning SeriesCollection and Labels, both properties should be a DependencyProperty.
In your case, you can go without implementing dependency properties, because you are only updating collections. So simply avoid reassigning a new collection on each refresh and instead use IList.Clear and IList.Add on collections that implement INotifyCollectionChanged.
SeriesCollection already implements INotifyCollectionChanged, so you only need to change the type of the Labels property from string[] to ObservableCollection<string>. This way IList.Clear and IList.Add operations will be automatically reflected by the binding target (the chart control):
public SeriesCollection SeriesCollection { get; set; }
public ObservableCollection<string> Labels { get; set; }
public HBDBreakdown()
{
InitializeComponent();
// Set the `DataContext` once in the constructor
this.DataContext = this;
// Initialize the collection to prepare them for dynamic clear/add
this.SeriesCollection = new SeriesCollection();
this.Labels = new ObservableCollection<string>();
}
private void ChartValues()
{
try
{
...
this.SeriesCollection.Clear();
this.SeriesCollection.Add(
new StackedColumnSeries
{
Title = "Residential",
Values = new ChartValues<double>(ResidentialValues),
StackMode = StackMode.Values, // this is not necessary, values is the default stack mode
DataLabels = true
});
this.SeriesCollection.Add(
new StackedColumnSeries
{
Title = "Small Commercial Indust",
Values = new ChartValues<double>(SmallCommercialIndustValues),
StackMode = StackMode.Values,
DataLabels = true
});
this.Labels.Clear();
AnalystName.ForEach(this.Labels.Add);
}
// Code smell: never catch 'Exception'.
// Only catch explicitly those exception you can handle.
// A user can't handle exceptions, so in a real business application you wouldn't show the exception message to the user.
catch (Exception ex)
{
// Bad practice
MessageBox.Show(ex.Message);
}
}
Please also read: Exceptions and Exception Handling, especially Exceptions Overview and Design Guidelines for Exceptions.

Async Program still freezing up the UI

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];
}

Update WPF UI from Different Thread VB.net [duplicate]

Here's my problem.
I'm loading a few BitmapImages in a BlockingCollection
public void blockingProducer(BitmapImage imgBSource)
{
if (!collection.IsAddingCompleted)
collection.Add(imgBSource);
}
the loading happens in a backgroungwork thread.
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
String filepath; int imgCount = 0;
for (int i = 1; i < 10; i++)
{
imgCount++;
filepath = "Snap";
filepath += imgCount;
filepath += ".bmp";
this.Dispatcher.BeginInvoke(new Action(() =>
{
label1.Content = "Snap" + imgCount + " loaded.";
}), DispatcherPriority.Normal);
BitmapImage imgSource = new BitmapImage();
imgSource.BeginInit();
imgSource.UriSource = new Uri(filepath, UriKind.Relative);
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
blockingProducer(imgSource);
}
}
debugging this part of the code everything looks okay, the problem comes now ...
after finishing loading the images I want to show them in UI one by one. I'm using a dispatcher to do so but I always get the message telling me that the called Thread can not access the object because it belongs to a different Thread.
public void display(BlockingCollection<BitmapImage> results)
{
foreach (BitmapImage item in collection.GetConsumingEnumerable())
{
this.Dispatcher.BeginInvoke(new Action(() =>
{
this.dstSource.Source = item;
Thread.Sleep(250);
}), DispatcherPriority.Background);
}
}
debug accuses that the error is here
this.dstSource.Source = item;
I'm trying everything but cant find out what’s wrong. Anyone has any idea?
You have to call Freeze after loading the images in order to make them accessible to other threads:
BitmapImage imgSource = new BitmapImage();
imgSource.BeginInit();
imgSource.UriSource = new Uri(filepath, UriKind.Relative);
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
imgSource.Freeze(); // here
As far as I have understood the BitmapCacheOption.OnLoad flag, it is only effective when a BitmapImage is loaded from a stream. The Remarks section in BitmapCacheOption says:
Set the CacheOption to BitmapCacheOption.OnLoad if you wish to close a
stream used to create the BitmapImage. The default OnDemand cache
option retains access to the stream until the image is needed, and
cleanup is handled by the garbage collector.
A BitmapImage created from a Uri may be loaded asynchronously (see the IsDownloading property). Consequently, Freeze may not be callable on such a BitmapImage, as downloading may still be in progress after EndInit. I guess it nevertheless works in your case because you are loading BitmapImages from file Uris, which seems to be done immediately.
To avoid this potential problem you may just create the BitmapImage from a FileStream:
var imgSource = new BitmapImage();
using (var stream = new FileStream(filepath, FileMode.Open))
{
imgSource.BeginInit();
imgSource.StreamSource = stream;
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
imgSource.Freeze();
}
For the further future readers, here is the code I used to fix my problem.
public void display(BlockingCollection<BitmapImage> collection)
{
if (collection.IsCompleted || collection.Count != 0)
{
BitmapImage item = collection.Take();
this.Dispatcher.BeginInvoke(new Action(() =>
{
this.dstSource.Source = item;
}), DispatcherPriority.Normal);
}
else
{
dispatcherTimer.Stop();
}
}
public void dispatcherTimer_Tick(object sender, EventArgs e)
{
display(collection);
}
public void configureDispatcherTimer()
{
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
TimeSpan interval = TimeSpan.FromMilliseconds(150);
dispatcherTimer.Interval = interval;
}

WPF loading serialized image

In an app I need to serialize an image through a binarywriter, and to get it in an other app to display it.
Here is a part of my "serialization" code :
FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write);
BinaryWriter bin = new BinaryWriter(fs);
bin.Write((short)this.Animations.Count);
for (int i = 0; i < this.Animations.Count; i++)
{
MemoryStream stream = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(Animations[i].Image));
encoder.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
bin.Write((int)stream.GetBuffer().Length);
bin.Write(stream.GetBuffer());
stream.Close();
}
bin.Close();
And here is the part of my deserialization that load the image :
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
BinaryReader bin = new BinaryReader(fs);
int animCount = bin.ReadInt16();
int imageBytesLenght;
byte[] imageBytes;
BitmapSource img;
for (int i = 0; i < animCount; i++)
{
imageBytesLenght = bin.ReadInt32();
imageBytes = bin.ReadBytes(imageBytesLenght);
img = new BitmapImage();
MemoryStream stream = new MemoryStream(imageBytes);
BitmapDecoder dec = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
img = dec.Frames[0];
stream.Close();
}
bin.Close();
When I use this method, I load the image (it seems to be stored in the "img" object) but it can't be displayed.
Has somedy an idea?
Thanks
KiTe
UPD :
I already do this : updating my binding, or even trying to affect it directly through the window code behing. None of these approaches work :s
However, when I add this :
private void CreateFile(byte[] bytes)
{
FileStream fs = new FileStream(Environment.CurrentDirectory + "/" + "testeuh.png", FileMode.Create, FileAccess.Write);
fs.Write(bytes, 0, bytes.Length);
fs.Close();
}
at the end of you function, it perfectly create the file, which can be read without any problems ... So I don't know where the problem can be.
UPD 2 :
A weird things happens.
Here is the binding I use :
<Image x:Name="imgSelectedAnim" Width="150" Height="150" Source="{Binding ElementName=lstAnims, Path=SelectedItem.Image}" Stretch="Uniform" />
(the list is itself binded on an observableCollection).
When I create manually the animation through the app, it works (the image is displayed).
But when I load it, it is not displayed (I look at my code, there are not any "new" which could break up my binding, since there are others properties binded the same way and they does not fail).
Moreover, I've put a breakpoint on the getter/setter of the properties Image of my animation.
When it is created, no problems, it has the good informations.
But when it is retreived through the getter, it return a good image the first time, and then and image with pixelWidth, pixelHeight, width, height of only 1 but without going through the setter anymore !
Any idea?
UPD3
tried what you said like this :
Added a property TEST in my viewModel :
private BitmapSource test;
public BitmapSource TEST { get { return test; } set { test = value; RaisePropertyChanged("TEST"); } }
then did it :
img = getBmpSrcFromBytes(bin.ReadBytes(imageBytesLenght));
TEST = img;
(in the code showed and modified before)
and my binding :
<Image x:Name="imgSelectedAnim" Width="150" Height="150" Source="{Binding Path=TEST}" Stretch="Uniform" />
(datacontext is set to my ViewModel)
And it still doesn't work and does the weird image modification (pixW, pixH, W and H set to 1)
FINAL UPD:
It seems I finally solved the problem. Here is simply what I did :
byte[] bytes = bin.ReadBytes(imageBytesLenght);
MemoryStream mem = new MemoryStream(bytes);
img = new BitmapImage();
img.BeginInit();
img.StreamSource = mem;
img.EndInit();
the strange thing is that it didn't work the first time, maybe it is due to an architectural modification within my spriteanimation class, but I don't think it is.
Well, thank you a lot to Eugene Cheverda for his help
At the first, I think you should store your images in a list of BitmapSource and provide reverse encoding of image for using stream as BitmapImage.StreamSource:
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
BinaryReader bin = new BinaryReader(fs);
int animCount = bin.ReadInt16();
int imageBytesLenght;
byte[] imageBytes;
List<BitmapSource> bitmaps = new List<BitmapSource>();
for (int i = 0; i < animCount; i++)
{
imageBytesLenght = bin.ReadInt32();
imageBytes = bin.ReadBytes(imageBytesLenght);
bitmaps.Add(getBmpSrcFromBytes(imageBytes));
}
bin.Close();
UPD
In code above use func written below:
private BitmapSource getBmpSrcFromBytes(byte[] bytes)
{
using (var srcStream = new MemoryStream(bytes))
{
var dec = new PngBitmapDecoder(srcStream, BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.Default);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(dec.Frames[0]);
BitmapImage bitmapImage = null;
bool isCreated;
try
{
using (var ms = new MemoryStream())
{
encoder.Save(ms);
bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = ms;
bitmapImage.EndInit();
isCreated = true;
}
}
catch
{
isCreated = false;
}
return isCreated ? bitmapImage : null;
}
}
Hope this helps.
UPD #2
Your binding is incorrect. You may be should bind selected item to for example CurrentImageSource. And then this CurrentImageSource bind to Image control.
Code:
public class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<BitmapSource> ImagesCollection { get; set; }
private BitmapSource _currentImage;
public BitmapSource CurrentImage
{
get { return _currentImage; }
set
{
_currentImage = value;
raiseOnPropertyChanged("CurrentImage");
}
}
private void raiseOnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then:
<ListBox ItemsSource="{Binding ImagesCollection}" SelectedItem="{Binding CurrentImage}"/>
<Image Source="{Binding CurrentImage}"/>
ADDED
Here is the sample project in which implemented technique as I described above.
It creates animations collection and represent them in listbox, selected animation is displayed in Image control using its Image property.
What I want to say is that if you cannot obtain image as it should be, you need to search problems in serialization/deserialization.

BitmapFrame in another thread

I am using a WPF BackgroundWorker to create thumbnails. My worker function looks like:
private void work(object sender, DoWorkEventArgs e)
{
try
{
var paths = e.Argument as string[];
var boxList = new List<BoxItem>();
foreach (string path in paths)
{
if (!string.IsNullOrEmpty(path))
{
FileInfo info = new FileInfo(path);
if (info.Exists && info.Length > 0)
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 200;
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.UriSource = new Uri(info.FullName);
bi.EndInit();
var item = new BoxItem();
item.FilePath = path;
MemoryStream ms = new MemoryStream();
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bi));
encoder.Save(ms);
item.ThumbNail = ms.ToArray();
ms.Close();
boxList.Add(item);
}
}
}
e.Result = boxList;
}
catch (Exception ex)
{
//nerver comes here
}
}
When this function is finished and before the BackgroundWorker "Completed" function is started, I can see on the output window on Vs2008, that a exception is generated. It looks like:
A first chance exception of type 'System.NotSupportedException' occurred in PresentationCore.dll
The number of exceptions generates equals the number of thumbnails to be generated.
Using "trial and error" method I have isolated the problem to:
BitmapFrame.Create(bi)
Removing that line (makes my function useless) also removes the exception.
I have not found any explanation to this, or a better method to create thumbnails in a background thread.
Lasse, I believe the problem arises because you are performing actions outside of the UI thread that need to be done within the UI thread. Creating UI elements (BitmapImage, BitmapFrame) and adding to UI Containers, I believe, should be done on the UI thread. (Someone correct me if I'm wrong here).
There are a few ways to create those elements on the UI thread without blocking the application for an excessive period of time. The easiest is probably using the BackgroundWorker's ProgressChanged event. ProgressChanged is invoked on the UI thread, which makes it perfect for this situation.
You can use the worker's ProgressChanged event and pass it the path needed to load a thumbnail in the UserState argument.
Thanks for your input. It made start to look for another solutions and I come up with this.
try
{
var paths = e.Argument as string[];
var boxList = new List<BoxItem>();
foreach (string path in paths)
{
using (Image photoImg = Image.FromFile(path))
{
int newWidth = 200;
int width = newWidth;
int height = (photoImg.Height * newWidth) / photoImg.Width;
var thumbnail = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage((System.Drawing.Image)thumbnail))
{
g.DrawImage(photoImg, 0, 0, width, height);
using (var ms = new System.IO.MemoryStream())
{
var item = new BoxItem();
item.FilePath = path;
thumbnail.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
item.ThumbNail = ms.ToArray();
boxList.Add(item);
}
}
}
}
e.Result = boxList;
}
catch (Exception exp)
{
}
Not using any UI elements .. and works nicely.
Thanks.
//lasse

Resources