Populate Listbox with images from directory - c#

I'm kinda new at both programming, and WPF. I know that this is pretty easy, but I nothing I've tried as worked so far.....
I want to fill a Listbox with images from a folder. I also need to know how to force my listbox from allowing scrolling to the side. So far I haven't stumbled upon anything that seems to work.
Here is my C# code that adds the files in chosen folder to a List Basically I want the Listbox to be use to keep a history log of the pictures that the user has chosen as backgrounds.
IList<Bitmap> HistoryImages = new List<Bitmap>();
foreach(String imagefile in Directory.GetFiles( #"C:\ProgramData\etc" ))
{
HistoryImages.Add( new Bitmap( imagefile) );
}

Found something that worked for me!
XAML Code:
<ListBox Name="ImageLog" Background="Transparent" IsEnabled="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ItemsSource="{Binding Path=Image}" BorderThickness="0"
SelectionChanged="ImageLog_SelectionChanged_1">
</ListBox>
C# Code:
foreach(string myFile in Directory.GetFiles( #"C:\ProgramData\MyApp" ) )
{
System.Windows.Controls.Image myLocalImage = new System.Windows.Controls.Image(); ;
myLocalImage.Height = 200;
myLocalImage.Margin = new Thickness( 5 );
BitmapImage myImageSource = new BitmapImage();
myImageSource.BeginInit();
myImageSource.UriSource = new Uri( #"file:///" + myFile );
myImageSource.EndInit();
myLocalImage.Source = myImageSource;
filePath.Add( myFile );
ImageLog.Items.Add(myLocalImage);
}

This requires the basics of data binding and data templating. (If you read and understood all that you should be able to do it.)
About the scrolling, set ScrollViewer.HorizontalScrollBarVisibility as attached property on the ListBox to Disabled

Related

WPF Image won't update programmatically

I have an application where I want it to load an image when a command is invoked. But the problem is that nothing loads and nothing breaks either. I just dont see my image. I also made sure that I was setting the data context to my view model.
XAML:
<Image Grid.Column="3" Source="{Binding Path=LoadingImage, Mode=TwoWay}" Width="35" Height="35"/>
ViewModel:
private Image _loadingImage = new Image();
public Image LoadingImage
{
get => _loadingImage;
set
{
_loadingImage = value;
RaisePropertyChanged(nameof(LoadingImage));
}
}
//Method called by the command... i debugged it and it gets here just fine
private void GetDirectories()
{
FolderBrowserDialog folderBrowseDialog = new FolderBrowserDialog();
DialogResult result = folderBrowseDialog.ShowDialog();
if (result == DialogResult.OK)
{
//This is how I am getting the image file
LoadingImage.Source = new BitmapImage(new Uri("pack://application:,,,/FOONamespace;component/Resources/spinner_small.png"));
//More code below
}
}
Some other settings, my .png file has the following properties:
Build Action: Resource
Copy to Output Directory: Copy if newer
This is head scratcher for me. What am I doing wrong? Many thanks.
You can't use an Image element as the value of the Source property of another Image element.
Change the property type to ImageSource:
private ImageSource _loadingImage;
public ImageSource LoadingImage
{
get => _loadingImage;
set
{
_loadingImage = value;
RaisePropertyChanged(nameof(LoadingImage));
}
}
and assign the property like this:
LoadingImage = new BitmapImage(
new Uri("pack://application:,,,/FOONamespace;component/Resources/spinner_small.png"));
Besides that, setting the Binding's Mode to TwoWay is pointless
<Image Source="{Binding LoadingImage}" />
and copying to the output directory is also unnecessary, because the Build Action Resource makes the image file an assembly resource that is compiled into the assembly.

Out of Memory Exception when Populating ListView with Images (Windows Phone 8.1)

So I am able to display images from my custom folder in the Pictures Library to my ListView in the app. However, when that custom folder has 3 or more images, it's either Out of Memory Exception occurs or the app just crashes and Visual Studio doesn't even realize that the app have crashed. My question is how can I make this work?
Here are my codes...
In the xaml.cs file:
List<StorageFile> FileList = (await temp.GetFilesAsync()).ToList();
List<ImageItem> ImageList = new List<ImageItem>();
for (int i = 0; i < FileList.Count; i++)
{
using (IRandomAccessStream FileStream = await FileList[i].OpenAsync(FileAccessMode.Read))
{
using(StorageItemThumbnail thumbnail = await file.GetThumbnailAsync(ThumbnailMode.PicturesView))
{
if (thumbnail != null && thumbnail.Type == ThumbnailType.Image)
{
BitmapImage bitmap = new BitmapImage();
await bitmap.SetSourceAsync(FileStream);
ImageList.Add(new ImageItem() { ImageData = bitmap });
}
}
}
}
this.PhotoListView.DataContext = ImageList;
Here is my Helper Class:
public class ImageItem
{
public BitmapImage ImageData { get; set; }
}
Here is my xaml ListView code:
<ListView Grid.Column="1"
Grid.Row="0"
x:Name="PhotoListView"
Grid.RowSpan="1"
ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImageData}"
Margin="10"/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
The problem with your code is that when you use BitmapImage you didn't specify the DecodePixelHeight and DecodePixelWidth, you can solve the issue in 2 ways:
the first is to specify the DecodePixelHeight and DecodePixelWidth, the second is to pass the path of the image to the list view using this code:
List<StorageFile> FileList = (await temp.GetFilesAsync()).ToList();
List<string> ImageList = new List<string>();
foreach(var file in FileList)
{
ImageList.Add(file.Path);
}
this.PhotoListView.DataContext = ImageList;
the Image control is able to do all the stuff for you, and takes care of the memory management as well.
I think your main problem is settings the ItemsPanelTemplate to Stackpanel. This kills virtualization. There is no reason for you to override the default item panel.
Also as frenk91 mentions, adding DecodePixelHeight and DecodePixelWidth to your XAML may be useful.

XAML, Image control, local storage image

The first is the first, I'm very newbie on .NET, I was developed un Visual Basic 6, but now I'm trying to make an application to Windows Phone 8.0.
At this time I'm trapped with a (maybe simple) problem.
I have a xaml page with control, and it is part of LongListSelector thats implements an Observable Collection of "Prenda" class.
...
var prendasData = from r in db.Prendas select r;
PrendasItems = new ObservableCollection<Prenda>(prendasData);
llsPrendas.ItemsSource = PrendasItems;
...
The XAML portion is the code bellow, please, i know that severals things maybe are wrong, but I'm learning alone, be patient with me :D
<phone:LongListSelector x:Name="llsPrendas" Margin="0,0,-12,0" ItemsSource="{Binding Prendas}" SelectionChanged="llsPrendasSelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Width="100" Height="100" Margin="5,0,0,0" Source="{Binding PrendaImageURI}" ImageFailed="errcargaimg"/>
<StackPanel Orientation="Vertical">
<TextBlock FontWeight="Normal" Text="{Binding Nombre}" Margin="10,0,0,0" />
<TextBlock FontWeight="Normal" Text="{Binding Precio}" Margin="10,0,0,0" />
</StackPanel>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
The problem is the Image Control. It doesn't show anything and if I debug it, the error the message is: "AG_E_NETWORK_ERROR", googled this error and I know that is (in this case) same as "File not found." But I'm sure thats file exists. because I seen it with IsoStoreSpy, at /Shared/Media/ShellContent/WP_XXX.jpg, i think that the root of the Isolated Storage is called isostore:/ and the complete URI must be: isostore:/Shared/Media/ShellContent/WP_XXX.jpg.
This string is saved as string column in the class, and I create a property thats use this string to make an Uri to use to bind the Source property of Image control at design time.
(portion of class declaration)
[Column]
public string Foto
{
get
{
return foto;
}
set
{
if (foto != value)
{
foto = value;
NotifyPropertyChanged("Foto");
}
}
}
public Uri PrendaImageURI
{
get
{
return new Uri(this.Foto, UriKind.Absolute);
}
}
I'm going crazy, because I cannot understand why it's doesn't work. Can somebody help me? (Sorry for my bad english)
You can not read from Isolated storage using URI, You have to read using IsolatedStorageFile class:
private static BitmapImage GetImageFromIsolatedStorage(string imageName)
{
var bimg = new BitmapImage();
using (var iso = IsolatedStorageFile.GetUserStoreForApplication())
{
using (var stream = iso.OpenFile(imageName, FileMode.Open, FileAccess.Read))
{
bimg.SetSource(stream);
}
}
return bimg;
}
You can find more details from below Posts:
How get image from isolated storage
How to load an image from isolated storage into image control on windows phone?
I solved it with Pratik Goyal help (thank you, very much!), making a BitmapImage Property in the class "Prendas", taking The Foto string data. Later I will take more careful with the exception control, but is a good start, I think.
public BitmapImage ImageFoto
{
get
{
return GetImageFromIsolatedStorage(Foto);
}
}
public BitmapImage GetImageFromIsolatedStorage(string imageName)
{
var bimg = new BitmapImage();
using (var iso = IsolatedStorageFile.GetUserStoreForApplication())
{
using (var stream = iso.OpenFile(imageName, FileMode.Open, FileAccess.Read))
{
bimg.SetSource(stream);
}
}
return bimg;
}
Just for the children:
Silverlight notifies this with an AG_E_NETWORK_ERROR / HRESULT=0x80131500. Which of course can't be found anywhere on the web and of course means jack and shit to me and not only to me.
Is the UI being notified of the PrendaImageURI property changing?
Try adding
NotifyPropertyChanged("PrendaImageURI");
when your Foto property is set.

Add Items to ComboBox Programmatically

I have been working with WPF all of 2 days, coming from ASP.NET so bear with me!
I am populating a ComboBox with xml filenames from a directory and adding a icon to each item. I have everything working just fine but I am wondering if there is a "better", more "efficient" way of doing this. As I stated, I am just getting started with WPF and I want to go about things the "right" way. My working code is below, can or should I be going about this a different way? Thanks in advance for any pointers!
<ComboBox Height="24" HorizontalAlignment="Left" Margin="153,138,0,0" Name="cmbFiles" VerticalAlignment="Top" Width="200" //>
private void FillSrFileCombo()
{
string[] dirFiles = Directory.GetFiles(#"D:\TestFiles", "*.xml");
foreach (string datei in dirFiles)
{
string fileName = System.IO.Path.GetFileName(datei);
System.Windows.Controls.StackPanel stkPanel = new StackPanel();
stkPanel.Orientation = Orientation.Horizontal;
cmbFiles.Items.Add(stkPanel);
System.Windows.Controls.Image cboIcon = new Image();
BitmapImage bitMap = new BitmapImage();
bitMap.BeginInit();
bitMap.UriSource = new Uri(#"tag.jpg", UriKind.Relative);
bitMap.EndInit();
cboIcon.Source = bitMap;
cboIcon.Height = 15;
stkPanel.Children.Add(cboIcon);
System.Windows.Controls.TextBlock cboText = new TextBlock();
cboText.Text = " - " + fileName;
stkPanel.Children.Add(cboText);
}
}
I have answered a similar question an hour ago see here :http://stackoverflow.com/questions/9637514/add-usercontrol-to-listbox-wpf.
I will recap the most important parts here based on your example
In the XAML you need to create a "DataTemplate", that is the XAML representation of your file object - in your case an image + file name. You can create this Datatemplate as a resource and assign it to your ComboBox or simply create it in the combobox if you don't plan to reuse it
<ComboBox ItemsSource="{Binding Files}">
<ComboBox.ItemTemplate>
<StackPanel>
<Image Source="{Binding FileImage}" Height="16" Width="16"/>
<TextBlock Margin="5" Text="{Binding FileName}" />
</StackPanel>
</ComboBox.ItemTemplate>
</ComboBox>
In your Codebehind, you need to create a structure that represets the data you want to present in your combobox - let's say a "FileInfo" class. The FileInfo class needs to expose the "FileImage" and "FileName" as properties so you can bind to them (as seen above).
Next, you need to create a collection of such objects in the code-behind of the xaml you put your ComboBox in. The collection needs to be an ObservableCollection.
So you would have smth like this:
public class FileInfo
{
public ImageSource FileImage { get; set; }
public string FileName { get; set; }
}
and then in the MainWindow.xaml.cs
public ObservableCollection Files { get; private set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Files = new ObservableCollection();
foreach (string datei in dirFiles)
{
var fName = System.IO.Path.GetFileName(datei);
BitmapImage bitMap = new BitmapImage();
bitMap.BeginInit();
bitMap.UriSource = new Uri(#"tag.jpg", UriKind.Relative);
bitMap.EndInit();
Files.Add(new FileInfo(){FileName=fName, FileImage = bitMap});
}
}
You will still need to read a lot about why this will work. I recomend reading about DataTemplates DataBinding, ObservableCollection and in the end, read about MVVM, a pattern that ties all this stuff nicely and allows you to harness all the WPF power and decouple yor logic from the UI.
One way that you should consider for WPF/Silverlight/WP7 apps is the MVVM design pattern.
In this instance you would have a view model containing the collection of items for your ComboBox, and you would use a binding expression to set the ItemsSource of the ComboBox. You would then template the ComboBox to display your item images.
Look into data binding and data templating, the only C# code you should need here is to get the files (even though you could also do that in XAML using something like an ObjectDataProvider)

WPF Binding: Where a property contains the path to the value

I've got an expander with a couple of TextBlocks in the top bar which i'm using to give a title and a piece of key information.
Ideally i want to set the path to key piece of information, but i can't work out how to bind the path of the binding to another path (i apologise if i'm not making much sense!)
In the following xaml the first bit works, the second bit is what i'm struggling with.
<TextBlock Text="{Binding Path=Header.Title}"/>
<TextBlock Text="{Binding Path={Binding Path=Header.KeyValuePath}}"/>
KeyValuePath might contain something like "Vehicle.Registration" or "Supplier.Name" depending on the Model.
Can anyone point me in the right direction please? Any help gratefully received!
I don't think it can be done in pure XAML... Path is not a DependencyProperty (and anyway Binding is not a DependencyObject), so it can't be the target of a binding
You could modify the binding in code-behind instead
I haven't found a way to do this in XAML but I did this in code behind. Here is the approach I took.
Firstly, I wanted to do this for all items in an ItemsControl. So I had XAML like this:
<ListBox x:Name="_events" ItemsSource="{Binding Path=Events}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Events:EventViewModel}">
<TextBlock Name="ActualText" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then, in code behind construction I subscribe to the ItemContainerGenerator:
InitializeComponent();
_events.ItemContainerGenerator.StatusChanged
+= OnItemContainerGeneratorStatusChanged;
This method looks like:
private void OnItemContainerGeneratorStatusChanged(object sender, EventArgs e)
{
if (_events.ItemContainerGenerator.Status!=GeneratorStatus.ContainersGenerated)
return;
for (int i = 0; i < _viewModel.Events.Count; i++)
{
// Get the container that wraps the item from ItemsSource
var item = (ListBoxItem)_events.ItemContainerGenerator.ContainerFromIndex(i);
// May be null if filtered
if (item == null)
continue;
// Find the target
var textBlock = item.FindByName("ActualText");
// Find the data item to which the data template was applied
var eventViewModel = (EventViewModel)textBlock.DataContext;
// This is the path I want to bind to
var path = eventViewModel.BindingPath;
// Create a binding
var binding = new Binding(path) { Source = eventViewModel };
textBlock.SetBinding(TextBlock.TextProperty, binding);
}
}
If you only have a single item to set the binding upon, then the code would be quite a bit simpler.
<TextBlock x:Name="_text" Name="ActualText" />
And in code behind:
var binding = new Binding(path) { Source = bindingSourceObject };
_text.SetBinding(TextBlock.TextProperty, binding);
Hope that helps someone.

Categories