WPF/C# Binding custom object list data to a ListBox? - c#

I've ran into a bit of a wall with being able to bind data of my custom object list to a ListBox in WPF.
This is the custom object:
public class FileItem
{
public string Name { get; set; }
public string Path { get; set; }
}
And this is the list:
private List<FileItem> folder = new List<FileItem>();
public List<FileItem> Folder { get { return folder; } }
The list gets populated and maintained by a FileSystemWatcher as files get moved around, deleted, renamed, etc. All the list does is keeps tracks of names and paths.
Here's what I have in the MainWindow code-behind file (it's hard coded for testing purposes for now):
FolderWatcher folder1 = new FolderWatcher();
folder1.Run(#"E:\MyApp\test", "*.txt");
listboxFolder1.ItemsSource = folder1.Folder;
Here's my XAML portion:
<ListBox x:Name="listboxFolder1" Grid.Row="1" BorderThickness="0"
ItemsSource="{Binding}"/>
Unfortunately, the only thing that gets displayed is MyApp.FileItem for every entry. How do I display the specific property such as name?

You will need to define the ItemTemplate for your ListBox
<ListBox x:Name="listboxFolder1" Grid.Row="1" BorderThickness="0"
ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

The easiest way is to override ToString on your FileItem, (The listbox uses this to populate each entry)
public override string ToString()
{
return Name;
}

Each item in the list that ListBox shows automatically calls the ToString method to display it, and since you didn't override it, it displays the name of the type.
So, there are two things you can do here.
Override the ToString method like Sayse suggested.
Use DataTemplate and bind each of your properties seperatly
In your resource add the template with a key
<DataTemplate x:Key="fileItemTemplate">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Path}"/>
</StackPanel>
</DataTemplate>
and give it as your listbox ItemTemplate
<ListBox x:Name="listboxFolder1" Grid.Row="1" BorderThickness="0" ItemsSource="{Binding}" ItemTemplate="{StaticResource fileItemTemplate}">

In case anyone comes across this now via search, I just encountered pretty much the same issue in a C# UWP app.
While the XAML bits in Nitin's answer above were necessary, they didn't fix the issue alone -- I also had to change my equivalent of Folder to be an ObservableCollection, rather than a List, to get the ListBox to show the property I needed.

Related

How to bind string property to TextBox within ListBox

I'm binding a List<string> to my ListBox in WPF using MVVM
At the moment I have
<ListBox ItemsSource="{Binding FileContents}"></ListBox>
File Contents in my ViewModel is simply
public List<string> FileContents {get;set;}
And the FileContents values are set in the constructor of the ViewModel, as such there is no need to worry about INotifyProperty
Everything works fine so far. I can see the list displayed in my ListBox as desired.
Now I need to provide a template! This is where it goes wrong
<ListBox ItemsSource="{Binding FileContents}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is where it all goes wrong! My understanding is that I only need to do <TextBox Text = "{Binding}" because the ListBox is already bound to the List<string> property (called FileContents)
However, when I run the above Visual Studio gives me
The application is in break mode
If I update the code to
<TextBox Text = "Some String Value"
then it works fine
I don't understand what I've done wrong.
Set the Mode of the Binding to OneWay:
<TextBox Text="{Binding Path=., Mode=OneWay}" />
The default binding mode for the Text property of a TextBox is TwoWay but this won't work when you bind to a string in a List<string>.
Binding to a string directly is only possible one way. This means you are only able to bind read only like
<TextBox Text="{Binding Mode=OneWay}"/>
or
<TextBox Text="{Binding .}"/>
The reason is simple: Changing the string means you are removing and adding an item to your list. This is simply not possible by changing the string in a TextBox.
A solution is to wrap the content in a class like
public class FileContent
{
public string Content { get; set; }
}
and bind to a list of List<FileContent> by using <TextBox Text="{Binding Content}"/> as template.

It is possible to Have ObservableCollection of ObservableCollections in viewModel using MVVM pattern

I wonder to know if it is possible to have an ObservableCollection of ObservableCollections in viewModel like this:
ObservableCollection<ObservableCollection<EditingMetadataViewModel>> MetadatasList = new ObservableCollection<ObservableCollection<EditingMetadataViewModel>>();
each ObservableCollection shows a list of metadata when it is binded to the view. In the case I have more than one file selected I want to have the same metadata lists number as selected files number (e.g if I select three files and I want to edit their metadata, I want to have three lists of metadata list).
Yes you can create a ItemsControl(or Panel controls) of ListView, ListBox, Grid, ... as child items:
<ItemsControl ItemsSource="{Binding MetadatasList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding MetaData}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I don't see why not and would be very easy for you to verify this. You could quickly create a ListBox that has a data template that contains a ListBox.
For clarity however you may want to define a class FileMetadata. In that case the first observable collection will be declared as
public ObservableCollection<FileMetadata> FileMetadataList {get; private set; }
Class FileMetadata would contain a member:
public ObservableCollection<EditingMetadataViewModel>> MetadatasList {get; private set; }
This is equivalent with your code but will may make certain parts easier to read and manage.

WPF, Datatemplates, and Data binding

Not a specific code question of any sort, I'm just looking to better understand exactly how data binding works in a DataTemplate. Here's just an example block of code; I have defined a Client class with three attributes (the purpose of these attributes is irrelevant to the question)
public class Client
{
public bool Powered { get; set; }
public bool clientAlive { get; set; }
public bool updaterAlive { get; set; }
}
I populate a ListView using a list of clients:
List<Client> clientList = new List<Client>();
//populate the list from JSON url, code omitted
listView1.ItemsSource = clientList;
And here's the block of XAML code that holds the template for displaying the items in the ListView:
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Powered: " FontWeight="Bold" />
<TextBlock Text="{Binding Powered}" />
<TextBlock Text=", " />
<TextBlock Text="clientAlive: " FontWeight="Bold" />
<TextBlock Text="{Binding clientAlive}" />
<TextBlock Text=", " />
<TextBlock Text="updaterAlive: " FontWeight="Bold" />
<TextBlock Text="{Binding updaterAlive}" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
The code runs fine and everything displays as expected, I was just wondering if anyone could explain how data binding in WPF works. As far as I'm concerned, there's nothing in the XAML that references the Client class and I'm just confused as to how the XAML knows to display the property the binding specifies. Does the Text = "{Binding = Powered}" just look for an attribute that matches the binding within the item type that populates the list?
Does the Text = "{Binding = Powered}" just look for an attribute that matches the binding within the item type that populates the list?
Basically, yes. If the item that your populated the list with didn't have that attribute, you would see binding errors (look in the console while debugging).
You can also supply a type to your DataTemplate which will allow you to have multiple templates that will be applied depending on the specific type of the object in your collection.
When the collection get bound to the listview, each list item container will be generated with Content set to Client object. So the visual present inside the data template has Client object as its data context.
The line "{Binding Powered}" will look up the datacontext and find the property named "Powered" and resolve its value. Just remove the word "Powered" and leave it as "{Binding}", WPF will display the fully qualified name of your datacontext object.

Use of DataTemplate

I'm quite new to C# and Windows Phone 7 for that sake, but none the less, I've thrown myself into trying to make a small app for myself. Here's my problem:
I'm trying to set up a DataTemplate that will position my Name and Drinks variables that I've declared in MainPage.xaml.cs. Here's my action when button1 is clicked:
private void button1_Click(object sender, RoutedEventArgs e)
{
string Name = participantName.Text;
int Drinks = 0;
listBox1.Items.Add(Name + Drinks);
}
And here is my DataTemplate from MainPage.xaml
<ListBox Height="Auto" HorizontalAlignment="Stretch" Margin="7,74,0,0" Name="listBox1" VerticalAlignment="Stretch" Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="132">
<TextBlock Text="{Binding Path=Name}" FontSize="35" />
<StackPanel Width="370">
<TextBlock Text="{Binding Path=Drinks}" FontSize="35" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The problem is that my data is not shown. It works perfectly without the DataTemplate, but as soon as I use it, my text simply doesn't get through. Your help is very much appreciated.
The template itself is ok. The bindings on the template, though, are currently incorrect.
When you add a new item to the list box, you are just adding a plain old string (which is currently missing a space, BTW.) Your bindings, though, expect the object in the list to have a Name property and a Drinks property, which of course the string class does not have.
The usual solution here is to logically separate your data model from your presentation, by creating a class to store the data itself (probably PersonDrink, with the appropriate Name and Drinks properties) and then adding those objects to the list.
You should read up on the MVVM pattern, as it provides an excellent way to ensure that changes in your data are reflected in your view, and visa versa.
http://amarchandra.wordpress.com/2011/12/18/binding-multiple-object-in-wp7-using-listbox/
Here is a sample for binding data using a datatemplate. I hope this might help you.

Binding ListBox to List (Collection) in XAML

I'm learning WPF, so I'm kind of n00b in this.
I saw some examples about how to do what I want to do, but nothing exactly...
The question: I want to bind List to ListBox. I want to do it in XAML, w/o coding in code behind. How can I achieve that?
Right now I do it that way:
XAML
<ListBox x:Name="FileList">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind
public MainWindow()
{
// ...
files = new List<string>();
FileList.ItemsSource = files;
}
private void FolderBrowser_TextChanged(object sender, RoutedEventArgs e)
{
string folder = FolderBrowser.Text;
files.Clear();
files.AddRange(Directory.GetFiles(folder, "*.txt", SearchOption.AllDirectories));
FileList.Items.Refresh();
}
But I want to get rid of FileList.ItemsSource = files; and FileList.Items.Refresh(); in C# code.
Thanks
First, setup the binding in your listbox:
<ListBox x:Name="FileList" ItemsSource="{Binding Files}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
or
<ListBox x:Name="FileList" ItemsSource="{Binding Files}" DisplayMemberPath="."/>
Next, make sure "Files" is a property in your DataContext (or code behind). (You can't bind to fields, only properties...)
Ideally, you'll want to make Files an ObservableCollection<T> instead of a List<T>, as well. This will allow the binding to handle adding or removing elements correctly.
If you do these two things, it should just work correctly.
Two tricks to add to Reed's answer:
1) If all you're displaying in your list box items is a string, you can avoid the ListBox.ItemTemplate folderol by just setting ListBox.DisplayMemberPath.
2) You can set the window's DataContext to itself. For instance, give the window a name of MyWindow and set its DataContext to {Binding ElementName=MyWindow}. Now you can bind to any of its public properties. (I'm pretty sure Reed's who I learned that trick from in the first place.)

Categories