I have a GridView in my app that basically contains images as following:
<GridView x:Name="ImageGridView">
<Image x:Name="Image_1"/>
<Image x:Name="Image_2"/>
<Image x:Name="Image_3"/>
</GridView>
with this in codebehind for the images to Load:
public void SetImages()
{
StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
Image_1.Source= new BitmapImage(new Uri(localFolder.Path + "/Image_1.png"));
Image_1.Source= new BitmapImage(new Uri(localFolder.Path + "/Image_2.png"));
Image_1.Source= new BitmapImage(new Uri(localFolder.Path + "/Image_3.png"));
}
The GridView shows the images just fine. However, I also have a Class that changes the colors of those Pictures based on existing values as fallowing:
This means that the Images sources don't change, and the paths to those images also remain as it is. The only thing changing are the colors in those images as demonstrated in the pictures.
However, when the colors-changing task is finished and the images are updated in the App's LocalState folder, they are not updated in the GridView images.
I have also tried creating an observable collection with the images in question. Binded the GridView to this collection, and at each color update I would include this:
GridView.ItemsSource = null;
GridView.ItemsSource = ImagesCollection;
It would work by changing some Images but not others. this happens randomly. If there it a way to await the first before executing the second, this might work out!
The question is: how to I ask the GridView to reload its content? Update its content? clear cache and re-fetch the Images?
Put this code into a method:
public void SetImages()
{
StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
Image_1.Source= new BitmapImage(new Uri(localFolder.Path + "/Image_1.png"));
Image_2.Source= new BitmapImage(new Uri(localFolder.Path + "/Image_2.png"));
Image_3.Source= new BitmapImage(new Uri(localFolder.Path + "/Image_3.png"));
}
Call this method once at the beginning and when you modified the images.
Don't be afraid to use MVVM. In your xaml bind the image sources to properties in your viewmodel.
XAML:
<GridView>
<Image Source="{Binding ImageSource1}"/>
<Image Source="{Binding ImageSource2}"/>
</GridView>
ViewModel:
public string ImageSource1 => Windows.Storage.ApplicationData.Current.LocalFolder + "/Image_1.png";
public string ImageSource2 => Windows.Storage.ApplicationData.Current.LocalFolder + "/Image_2.png";
// invoke the NotifyPropertyChanged event to enforce an update.
public void UpdateImages() {
InvokePropertyChanged(nameof(ImageSource1));
InvokePropertyChanged(nameof(ImageSource2));
}
Well I found an indirect dirty way to do it. to update the images in the GridView.
I started with clearing the ObservableCollection that the GridView binded to:
ThumnailsCollection.Clear();
Navigate to the folder where the thumbnails are saved to. each type of thumbnails are grouped in one folder. each time the color is changed where the thumbnails for it does not exist, new ones are generated. otherwise, it recalls the ones already on hand:
StorageFolder localFolder =
Windows.Storage.ApplicationData.Current.LocalFolder;
//Check if the main folder where the images are saved exist
IStorageItem storageItem = await localFolder.TryGetItemAsync("Thumnails");
if (storageItem != null)
{
//If it does, look into the subfolders. each image group of the same
//type and different colors are saved to tier own folder
StorageFolder ImagesFolder =
await localFolder.GetFolderAsync("Thumnails");
var queryOption =
new QueryOptions { FolderDepth = FolderDepth.Deep };
var flatWallpaperSubFolders =
await ImagesFolder .CreateFolderQueryWithOptions(queryOption).GetFoldersAsync();
foreach (StorageFolder subFolder in subFolders)
{
IStorageItem thumnail =
await subFolder.TryGetItemAsync($"{AlphaColorID} - {BetaColorID}.png");
if (thumnail != null)
{
//in each of the folders, look if the needed image
//exist or not
//If it does, add it to the ObservableCollection
BitmapImage icon =
new BitmapImage(new Uri(subFolder.Path + $"/{AlphaColorID} - {BetaColorID}.png"));
ThumnailsCollection.Add(new ThumnailsBitmap(icon));
}
else
{
//If the Image with the requested colors does not exist
//Render a new image with the needed colors
//the rendered image is added to the same subfolder with the other
// similar images
await ApplyColor(new Uri($"ms-appx:///Images/{subFolder.Name}-Thumnail.png"), $"{AlphaColor} - {BetaColor}.png", $"{subFolder.Name}");
BitmapImage icon =
new BitmapImage(new Uri(flatWallpaperSubFolder.Path + $"/{AlphaColor} - {BetaColor}.png"));
ThumnailsCollection.Add(new ThumnailsBitmap(icon));
}
}
}
else
{
//when run for the first time, all folders and images are missing
//through ApplyColor task, all Images are rendered and folders created
await ApplyColor(new Uri("ms-appx:///Images/Number-1-Thumnail.png"), $"{AlphaColor} - {BetaColor}.png", "Number-1");
await ApplyColor(new Uri("ms-appx:///ModernWallpaperGrayscale/Number-2-Thumnail.png"), $"{AlphaColor} - {BetaColor}.png", "Number-2");
//All needed to do is to all of those Images to the
//ObservableCollection
StorageFolder ImagesFolder = await localFolder.GetFolderAsync("Thumnails");
var queryOption =
new QueryOptions { FolderDepth = FolderDepth.Deep };
var flatWallpaperSubFolders =
await ImagesFolder.CreateFolderQueryWithOptions(queryOption).GetFoldersAsync();
foreach (StorageFolder subFolder in subFolders)
{
BitmapImage icon =
new BitmapImage(new Uri(subFolder.Path + $"/{AlphaColor} - {BetaColor}.png"));
ThumnailsCollection.Add(new ThumnailsBitmap(icon));
}
}
Related
Here is the problem.
I have a view, that should display an image and some controls.
User add new images, changes some options and click "finish".
Images are large and very large (400-1500 MB Tiff)
User should see the preview of image, but it is ok if it loading for 10-15 sec or even more, he have a job for this time.
Image is binding through MVVM pattern like simple string (file will be always in local folder)
<Image Name="ImagePreview" Source="{Binding SFilePathForPreview,
FallbackValue={StaticResource DefaultImage},
TargetNullValue={StaticResource DefaultImage}}"
HorizontalAlignment="Center" Width="200" Height="200"
VerticalAlignment="Center" />
Problem is that all is hangs when user try to add a file for loading time.
I understand that this case should be solved through multithreading - but have no idea how to implement this.
I tryed to update image from view in different thread like this:
Thread newThread = new Thread(LazyLoad);
newThread.Name = "LazyLoad";
newThread.Start(SFilePathForPreview);
public void LazyLoad(object SFilePath)
{
try
{
string path = (string)SFilePath;
BitmapImage t_source = new BitmapImage();
t_source.BeginInit();
t_source.UriSource = new Uri(path);
t_source.DecodePixelWidth = 200;
t_source.EndInit();
t_source.Freeze();
this.Dispatcher.Invoke(new Action(delegate
{
ImagePreview.Source = t_source;
}));
}
catch
{
//...
}
}
But anyway at point
ImagePreview.Source = t_source;
everything hangs up until image fully loaded.
Is there a way to load a preview in the background and show it without those terrible hangs?
The probably most simple way of asynchronously loading an image is via an asynchronous Binding. You would not have to deal with Threads or Tasks at all.
<Image Source="{Binding Image, IsAsync=True}"/>
A possible view model could look like shown below, where you must make sure that the Image property getter can be called from a background thread.
public class ViewModel : ViewModelBase
{
private string imagePath;
private BitmapImage image;
public string ImagePath
{
get { return imagePath; }
set
{
imagePath = value;
image = null;
OnPropertyChanged(nameof(ImagePath));
OnPropertyChanged(nameof(Image));
}
}
public BitmapImage Image
{
get
{
lock (this)
{
if (image == null &&
!string.IsNullOrEmpty(imagePath) &&
File.Exists(imagePath))
{
using (var stream = File.OpenRead(imagePath))
{
image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.DecodePixelWidth = 200;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
}
}
}
return image;
}
}
}
As you already mentioned, you are blocking the UI thread with the image load. You can use a WriteableBitmap class instance as the source for your Image. This will let you load the image on a background thread or async task. Here is a quick guide (not mine) on the issue.
https://www.i-programmer.info/programming/wpf-workings/527-writeablebitmap.html
Another option would be using priortybinding with the highest priorty to the full image and a lower priority to the faster-loading preview image. MS has documented priority binding here:
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-prioritybinding
I'm building a simple application that shows an image bitmap using the Image class in UWP.
When i change something on the image, the program won't refresh the image displayed.
I've tried to change source using another temp image but the problem is still the same.
This is the object in the XAML file
<Image x:Name="image" HorizontalAlignment="Left" Height="128" VerticalAlignment="Top" Width="128" AutomationProperties.AccessibilityView="Raw" ManipulationMode="All"/>
This is the code that changes the source of the image
private void ChangeImage_BTN_Click(object sender, RoutedEventArgs e)
{
readImage();
}
private void readImage()
{
switch (nimg)
{
case 1:
image.Source = new BitmapImage(new Uri("ms-appx:///Assets/France.bmp"));
nimg = 3;
break;
case 2:
//image.Source = new BitmapImage(new Uri("ms-appx:///Assets/Inghilterra.bmp"));
//nimg = 3;
break;
case 3:
image.Source = new BitmapImage(new Uri("ms-appx:///Assets/Area24_128x128.bmp"));
nimg = 1;
break;
}
}
When i switch the image Area_128x128.bmp to France.bmp, I modify the bitmap file adding some draw, switching again to Area_128x128.bmp the image has the old things.
How can i see the image properly?
The "ms-appx:///" prefix in the URI refers to the app's install directory. It turns out to be not supported update edits of files at runtime.
For your user case, there are some workarounds you can have a try.
You can use ApplicationData.RoamingFolder like this:
Windows.Storage.StorageFolder roamingFolder = Windows.Storage.ApplicationData.Current.RoamingFolder;
var file = await roamingFolder.GetFileAsync("France.bmp");
using (var fileStream = (await file.OpenAsync(Windows.Storage.FileAccessMode.Read)))
{
var bitImg = new BitmapImage();
bitImg.SetSource(fileStream);
image.Source = bitImg;
}
You can use KnownFolders.PicturesLibrary like this:
var file = await KnownFolders.PicturesLibrary.GetFileAsync("France.bmp");
using (var fileStream = (await file.OpenAsync(Windows.Storage.FileAccessMode.Read)))
{
var bitImg = new BitmapImage();
bitImg.SetSource(fileStream);
image.Source = bitImg;
}
In general, the path of KnownFolders.PicturesLibrary is C:\Users\[YOUR USER NAME]\Pictures.
For more information, you can reference Store and retrieve settings and File access permissions.
I need to make a ListView where each row will have an image, then a Text, then another image.
Since to make a GridView I had to bind its DataContext to a List with all the images, and they work (almost) the same way, I think I need to make another List with the images and texts
The format is: "Store Logo. Promotion Text. Credit Card Logo" and all this data comes from an API.
I already have all the pictures saved in different folders ("Stores" and "PaymentMethods") and I get the text like
myTextBlock.text = item.promotion;
Now my questions are:
1) Is it possible to make a list with all this data? How? (or where do I have to look for it)
2) Once I have the list, how, by binding it, can I be sure that it will respect the format I mentioned above?
(Something I tried out, instead of having a ListView, was creating everything at runtime:
public async void getPromos()
{
if (Resp.searchResults.Count > 0)
{
var selected = from promo in Resp.searchResults
where (promo.store_id != null || promo.method_id != null)
select new
{
store = promo.store_id,
medio = promo.method_id,
endDay = promo.to,
promocion = promo.desc
};
foreach (var item in selected)
{
Debug.WriteLine(item.store);
await showPromos(item.store, item.medio, item.endDay, item.promocion);
}
}
}
async Task showPromos(string store, string medio, string endDay, string promocion)
{
Debug.WriteLine(store);
Debug.WriteLine("medio" + medio);
StorageFolder folder1 = await KnownFolders.PicturesLibrary.GetFolderAsync("UnicenterMediosFolder");
StorageFolder folder2 = await KnownFolders.PicturesLibrary.GetFolderAsync("UnicenterStores");
if (store != null)
{
StorageFile file = await folder2.GetFileAsync(store + ".png");
ImageProperties properties = await file.Properties.GetImagePropertiesAsync();
if (properties.Width > 0)
{
var bitmap = new WriteableBitmap((int)properties.Width, (int)properties.Height);
bitmap.SetValue(NameProperty, (string)properties.Title);
Debug.WriteLine(bitmap.PixelWidth);
using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
bitmap.SetSource(stream);
}
Color customColor = ColorHelper.FromArgb(213, 213, 213, 213);
StackPanel casa = new StackPanel();
casa.Orientation = Orientation.Horizontal;
casa.Background = new SolidColorBrush(customColor);
casa.Height = 70;
Image promoImage = new Image();
promoImage.Source = bitmap;
promoImage.Width = 70;
promoImage.Height = 70;
TextBlock thePromo = new TextBlock();
thePromo.Text = promocion;
thePromo.Foreground = new SolidColorBrush(Colors.Gray);
casa.Children.Add(promoImage);
casa.Children.Add(thePromo);
gridForPromos.Children.Add(casa);
Debug.WriteLine("aaa" + gridForPromos.Children.Count);
}
}
}
and in my xaml:
<ScrollViewer x:Name="myPromoSpace" Grid.Row="2">
<Grid x:Name="gridForPromos"/>
</ScrollViewer>
But doing this, I get each stackpanel on the previous one, instead of having them one under the other)
Can you guys point me in the right direction here, please?
When you add elements to a Grid without setting Grid.Row and having Grid.RowDefinitions, all the childs will be in the same grid cell. You either need to add RowDefinitions and set the row of the child:
gridForPromos.RowDefinitions.Add(new RowDefinition(...))
Grid.SetRow(casa, gridForPromos.RowDefinitions.Count);
or replace your Grid with a StackPanel:
<StackPanel x:Name="gridForPromos" />
But I would prefer to define a DataTemplate for your Items. With this you can define the visual apperance of your items in XAML and simple bind a StackPanel to your items. Here a link that shows you how to do: http://www.wpftutorial.net/datatemplates.html
I have an image control which is showing the preview image. If the user delete the image (which resides in folder) it should show the newly taken image.
but the image control shows the deleted image instead of showing new image.
// clear the image source before deleting the image.
// save image in the directory
public string globalFilePath;
int imageCount = Directory.GetFiles(imgDir, "*", SearchOption.TopDirectoryOnly).Length;
string filePath = Path.Combine(imgDir, "IMAGE_" + ++imageCount + ".jpeg");
globalFilePath = filePath;
// setting image control source
var strUri = new Uri(WebCamControl.globalFilePath, UriKind.Relative);
previewImage.Source = BitmapFromUri(strUri);
//Method
public static ImageSource BitmapFromUri(Uri source)
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = source;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
return bitmap;
}
// delete image
previewImage.Source = null;
if (System.IO.File.Exists(WebCamControl.globalFilePath))
{
System.IO.File.Delete(WebCamControl.globalFilePath);
}
else
{
MessageBox.Show("File Not Exists");
}
After deleting the image in the directory file the image image control should show the new image, but my Image control shows the deleted image. please give me the solution.
In WPF, we generally don't need to use actual BitMapImage objects to display an Image. It's far easier to let the .NET Framework convert our string file paths to the images to the actual Image elements.
Also, it is far better to data bind the Image.Source to a property that implements the INotifyPropertyChanged interface (or a DepenedencyProperty):
<Image Source="{Binding YourImageFilePath}" ... />
Then, whenever a file path of a new image is set to the YourImageFilePath property, your displayed image will update immediately.
YourImageFilePath = filePath;
Try This one:
string path = ((BitmapImage)img.Source).UriSource.LocalPath;
img.SetValue(System.Windows.Controls.Image.SourceProperty, null);
File.Delete(path);
OR
string path = ((BitmapImage)img.Source).UriSource.LocalPath;
img.Source = null;
File.Delete(path)
In my listview I show thumbnails of small images in a certain folder. I setup the listview as follows:
var imageList = new ImageList();
foreach (var fileInfo in dir.GetFiles())
{
try
{
var image = Image.FromFile(fileInfo.FullName);
imageList.Images.Add(image);
}
catch
{
Console.WriteLine("error");
}
}
listView.View = View.LargeIcon;
imageList.ImageSize = new Size(64, 64);
listView.LargeImageList = imageList;
for (int j = 0; j < imageList.Images.Count; j++)
{
var item = new ListViewItem {ImageIndex = j, Text = "blabla"};
listView.Items.Add(item);
}
The user can rightclick on an image in the listview to remove it. I remove it from the listview and then I want to delete this image from the folder. Now I get the error that the file is in use. Of course this is logical since the imagelist is using the file.
I tried to first remove the image from the imagelist, but I keep on having the file lock.
Can somebody tell me what I am doing wrong?
Thanks!
You need to load the file into a MemoryStream, like this:
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(fileInfo.FullName)));
This way, the file will only be read once, and will not remain locked.
EDIT
You're loading the images into an ImageList.
Since the ImageList makes copies of its images, you should simply dispose the originals right away, like this:
using (var image = Image.FromFile(fileInfo.FullName))
imageList.Images.Add(image);
The image will need to be disposed of before it will unlock the file. Try calling Dispose on the image object after you remove it from the image list.
So long as you have a reference to the image object and the GC hasn't collected it, it will keep the lock. Calling Dispose should force it to give up its handle and cause the file to be unlocked.
You also have to make sure that the app didn't CopyHandle or otherwise get a second reference to the image resource before doing this.
Use GetThumbnailImage and then dispose image:
var image = Image.FromFile(fileN);
Image imgThumb = image.GetThumbnailImage(100, 100, null, new IntPtr());
imageList1.Images.Add(imgThumb);
image.Dispose();
listView1.LargeImageList = imageList1;
Now you can delete the file:
File.Delete(FileN);