WPF, Datatemplates, and Data binding - c#

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.

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.

WPF organizing data templates

I'm writing a WPF application. I want it to display data in ListBox from different sources. I want to make some common source interface like
interface IDataSource<T>
{
ObservableCollection<T> Elements { get; set; }
DataTemplate ElementDataTemplate { get; set; }
}
But I don't know which is the best type or types which I should user for IDataSource. I can make it UserControl, but it seems to be unnecessary, because my DataSource is not user control. The main problem is with ElementDataTemplate. How can I properly manage it not from UserControl class? Should I care another helper UserCntrol class and call something like (new MyUserControl).FindResource("ElementsDataTemplate") to obtain datatemplate or there is more fine way to keep and get DataTemplate?
You can simply apply a data template for a specific type in the resource section of the corresponding view:
<!-- Items may be of type ViewModel1 and ViewModel2 -->
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type vm:ViewModel1}">
<TextBlock Text="{Binding PropertyA}" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ViewModel2}">
<TextBlock Text="{Binding PropertyB}" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
So there is no need for the interface.

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

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.

Binding to Datagrid's ItemsSource collection properties instead of single items

I'm confused while trying to bind some properties housed inside a collection rather than the properties of the elements.
I'm not even sure how to phrase it right... code might explain better: here are the types (not actual code, I've shortened it to the basics):
public class myType
{
public int P {get;set;}
}
public class myTypeCollection : ObservableCollection<myType>
{
public int Ptotal {get { return this.Items.Select(i=>i.P).Aggregate((c,t)=>t = t + c); }}
public int Pmin { get { this.Items.Min(i => i.P); } } //concept
public int Pmax { get { this.Items.Max(i => i.P); } } //concept
}
They're being used in a templated control, whose XAML looks like this:
(adding comments to make it as clear as i'm able to)
<!-- myGridObject = new myTemplatedControl(); -->
<!-- myGridObject.DataContext = new myTypeCollection(); -->
<!-- NOTE: collection obviously is NOT empty in the real code -->
<sdk:DataGrid ItemsSource={Binding DataContext}>
<sdk:DataGridTemplateColumn Width="Auto">
<sdk:DataGridTemplateColumn.HeaderStyle>
<Style TargetType="sdk:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- ?????? write out Ptotal in the header of the column ??????? -->
<!-- This throws a binding-related ArgumentException -->
<TextBlock Text="{Binding ???? Ptotal ?????}" />
<!-- closing tags cut off -->
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding P}" />
<!-- closing tags cut off once more-->
{Binding P} works as expected, since P is a property of the items, but how do I access the collection's properties like Ptotal, Pmin, etc. ?
Thanks for taking the time to read this. If any info is missing just point it out and I'll post it.
I think the issue is that the DataGrid is bound to the collection, and each row is bound to an individual item, not the collection. You need to gain access a level up the chain (back to the collection itself).
If you're running Silverlight 4+ you can use a relativesource. For instance:
<TextBlock Text="{Binding Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType=sdk:DataGrid, AncestorLevel=1}, Path=DataContext.Count}"
Otherwise perhaps create a static access to the context to access it via the binding Source
So you need the collection object as your binding source.
You need these:
RelativeSource MarkupExtension
Binding.RelativeSource Property
Something like this (not tested):
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type myTemplatedControl}}, Path=DataContext.Ptotal}" />
If the DataGrid is inside your custom myTemplatedControl. It not totally clear for me what is myGridObject exactly. The main idea is:
As the MSDN documentation says: Binding.RelativeSource Gets or sets the binding source by specifying its location relative to the position of the binding target.
If you stuck with the x:Type extension, here is a link about it, so you can use it with your custom control:
X:Type
Another approach is if you name your container element (where your collection is the datacontext), then you can set that element as the binding source:
<TextBlock Text="{Binding ElementName=yourElementName, Path=DataContext.Ptotal}" />
Turns out the customer changed his mind about the grid headers, he doesn't want total displayed in the header anymore.
By the way, I must have tried 20 different approached including patching in various types of converters but I haven't been able to accomplish this not-as-simple-as-it-looks-apparently task.
Thanks again for the nonetheless interesting suggestions.

Editable WPF ListBox

I have a ObservableCollection that's bound to a ListBox in WPF. I want the ListBox to be editable, and for the editing changes to be saved to the collection. Since WPF doesnt provide an editable listbox, I've tried creating my own by changing the ListBox.ItemTemplate.
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="EditableText" Text="{TemplateBinding Content}"/>
</DataTemplate>
</ListBox.ItemTemplate>
Changing the ItemTemplate gives me editable boxes, but any changes to the textboxes dont get saved to the ObservableCollection. Is there a way to have an editable ListBox with two way binding?
You cannot do it this way.
To achieve that kind of trick, you would need your items to be "holder classes" that expose a property you can bind your textbox to.
To understand it, imagine the following pseudo sequence of calls:
class ListBox
{
Bind(Items)
{
foreach(var item in Items)
{
DataTemplate Template = LoadTemplateForItem(item.GetType()); // this is where your template get loaded
Template.Bind(item); //this is where your template gets bound
}
}
}
Your template (the DataTemplate with the listbox) is loaded and the item (which I assume is a string in your case) gets passed in.
At this point, it only knows the string, and cannot influence anything upwards. A two-way binding cannot influence the collection because the template does not know in which context it is being used, so it cannot reach back to the original collection and modify its contents.
For that matter, this is the same thing for the TextBox. If it is not given a conainer and a property name, it has nowhere to "store back" the changes.
This basically the same as passing a string into a function call. The function cannot change which string was passed in (ignoring tricks such as by-reference argument passing).
To get back to your case, you need to build a collection of objects which expose a property containing the value that needs to be edited:
public class MyDataItem
{
string Data { get; set;}
}
Then you can bind your ListBox to a collection of those items and modifiy your datatemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="EditableText" Text="{Binding Data, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
Bind to a model property -- i.e. a property of the data object -- rather than to a view property such as Content. For example:
// model class
public class Widget : INotifyPropertyChanged
{
public string Description { ... }
}
<!-- view -->
<DataTemplate>
<TextBox Text="{Binding Description}" />
</DataTemplate>
Note this will not work if your ItemsSource is ObservableCollection (because there's no property to bind to).

Categories