Bind a collection to a WPF ListBox - c#

Update: I've updated the code based on your help so far, and still no luck. When the application loads the ListBox has no items. I assign junk values to Customers in the windows's contructor, and then am also trying to set the ListBox's DataContext as follows:
CustomerList.DataContext = Customers;
--- Original Question (with updated code) ---
I'm having trouble with databinding in a WPF project.
I have a class, Customer, as follows:
public class Customer
{
public String Name { get; set; }
public String Email { get; set; }
}
In my XAML's code behind I have a collection of customers as follows:
public List<Customer> Customers { get; set; }
I'm trying to bind each customer to a ListBox with a ListItemTemplate displaying the customer's information (name/email) in TextBoxes along with a button which locks/unloacks the TextBoxes (sets the IsEnabled property to true or false).
What's the best way to go about this?
So far I've been tryingt he following with no success.
In the XAML I currently have the following (ignoring the toggle part for now, I'm just trying to get the collection itself to be listed.):
<Window.Resources>
<CollectionViewSource x:Key="Customers" Source="{Binding Path=Customers, Mode=TwoWay}"/>
<DataTemplate x:Key="Customer">
<StackPanel Orientation="Horizontal">
<TextBox Content="{Binding Name}" />
<TextBox Content="{Binding Email}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource Customers}}"
ItemTemplate="{StaticResource ResourceKey=Customer}"
Name="CustomerList"
Height="300" />
</StackPanel>

You need to change
ItemsSource="{Binding Source=Customers}"
to
ItemsSource="{Binding Source={StaticResource Customers}}" DataContext="{StaticResource Customers}"

Code similar to the updated one works for me after changing
<TextBox Content="{Binding Name}" />
to
<TextBox Text="{Binding Name}" />
As TextBox doesn't have Content property(like a Label), the former refused to compile in VS.
Well, it is set to Text in definition:
[ContentPropertyAttribute("Text")]
public class TextBox : TextBoxBase, IAddChild
But I thought it is only used between the brackets(<TextBox>Like so</TextBox>)?
Could this be the source of the problem?

Try setting the ItemsSource of your CustomerList as follows: ItemsSource="{Binding}". You've set the DataContext of the ListBox to the list of customers, you need to set the ItemsSource to the same collection, hence, the direct binding.
Another thing that you can do, in case you prefer to use the CollectionViewSource, is to set the DataContext of your window to the same class DataContext=this, because without this, the resource definition won't be able to locate the "Customers" collection that you defined in the code behind. If you do this, however, you don't need CustomerList.DataContext = Customers; because you're directly assigning the ItemsSource to a static resource, not relatively to the DataContext.
One more thing. I think you should give the CollectionViewSource and the corresponding collection in the code behind different names. This isn't going to cause a runtime issue, but it makes it hard to maintain the code ;)
Hope this helps :)

Related

UWP x:bind issue - Invalid binding path 'dpl' : Property 'dpl' not found on type 'DataTemplate'

I have a class PricingData and PricingSchedule. Where PricingSchedule is a List<> inside PricingData class. I want to bind data of this class to UWP controls.
Sample code is available to download here : https://github.com/jigneshdesai/SampleOfBindingIssue1.git
How Code looks: i have a start page(mainpage) that hosts ListView control, Listview has PricingUserControl within it. PricingUserControl looks like this
<TextBlock x:Name="lblPriceHeader" Text="{Binding PricingTitle}" Margin="0,0,50,0" />
<ComboBox x:Name="cbPriceValueList" ItemsSource="{x:Bind dpl}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" SelectedValue="{Binding DisplayPricing}" />
<ListView x:Name="lbPriceChangeSchedule" ItemsSource="{Binding PricingScheduleList}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="cbSchedulePriceValueList" ItemsSource="{x:Bind dpl}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" />
<TextBlock Text="{Binding SchedulePricingTimeZone }" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What i want to achieve: Combobox should populate a list of values (eg. 1USD, 2USD, 3USD etc.). Then when you provide List of records from database, the listbox will repeat PricingUserControl and combobox within it should set its value property (SelectedValue) as per record.
Issue:
ComboBox x:Name="cbPriceValueList" uses x:bind dpl where dpl is a local variable of PricingUserControl. It populates the list properly. The trouble is ComboBox x:Name="cbSchedulePriceValueList" it also has x:bind dpl but during compilation it display error "Invalid binding path 'dpl' : Property 'dpl' not found on type 'DataTemplate'."
I am wondering why x:bind dpl does not work at this point. ?
I have now realized that your problem is in fact that you need to reach to a Page property from within the DataTemplate, so here is a updated answer.
You cannot use x:Bind if you need to access an outside element's property from within a DataTemplate. Instead, you can use classic {Binding} expression. First add a name to your page:
<Page
...
x:Name="Page">
And now refer to this name from within the DataTemplate:
<ComboBox
x:Name="cbSchedulePriceValueList"
ItemsSource="{Binding ElementName=Page, Path=dpl}"
DisplayMemberPath="PriceValue"
SelectedValuePath="PriceValue" />
Original answer
To be able to use x:Bind inside of a DataTemplate, you must specify the data type the individual items of the control will have, using x:DataType. Suppose your PricingScheduleList is a List<MyApp.Models.MyType>, then you will first need to add this XML namespace to the <Page> element:
xmlns:models="using:MyApp.Models"
And then set the x:DataType attribute as follows:
<DataTemplate x:DataType="models:MyType">
...
</DataTemplate>
You can confirm this works by the fact that IntelliSense should now suggest you the properties of MyType when you start writing the x:Bind expression.
By checking your code, the reason why SelectedValue does not take effect is when you choose the item from ComboBox, you didn't notify your DisplayPricing to change. So you need to implement INotifyPropertyChanged interface in your PricingData. Do the same behavior in PricingSchedule.
public class PricingData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
......
public string DisplayPricing
{
get => $"{PricingValue} {PricingCurrency}";
set
{
var sp = value.Split(' ');
PricingValue = sp.First();
PricingCurrency = sp.Last();
OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
PScheduleUserControl.xaml:
<ComboBox x:Name="cbPriceValueList" ItemsSource="{x:Bind myList}" DisplayMemberPath="PriceValue" SelectedValuePath="PriceValue" SelectedValue="{Binding DisplayPricing,Mode=TwoWay}" />

No update of ListBox.ItemsSource after implementing editable ListBox items via DataTemplate

I implemented editable ListBox items like it is posted in this answer Inline editing TextBlock in a ListBox with Data Template (WPF)
.
But the new value does not get updated in the ItemsSource object of my ListBox.
This is the XAML:
<ListBox Grid.Row="2" Name="ds_ConfigProfiles" ItemsSource="{Binding ConfigProfiles}" SelectedItem="{Binding ActiveConfigProfile}" IsSynchronizedWithCurrentItem="True" Panel.ZIndex="-1">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- TODO: this is meant for allowing edit of the profile names, but the new name does not get stored back to ConfigProfiles -->
<local:TextToggleEdit Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="40" Height="23" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is the ConfigProfiles property in the view model:
/// <summary>Configuration profiles that were found in the active storage path</summary>
public ObservableCollection<string> ConfigProfiles { get; private set; } = new ObservableCollection<string>();
Did I understand something wrong?
May it be the reason, that the items source is of type ObservableCollection<string> instead of ObservableCollection<ProperClassImplementation> (which is of legacy reasons).
I am relatively new to WPF and am out of ideas on how to debug this.
May it be the reason, that the items source is of type ObservableCollection<string> instead of ObservableCollection<ProperClassImplementation> (which is of legacy reasons).
Yes, exactly. You can't modify a string since it is immutable. You need to bind to a string property of a class which means that you need to replace the ObservableCollection<string> with an ObservableCollection<ProperClassImplementation>.
I am afraid the binding engine won't replace the string in the ObservableCollection<string> with a new string for you if that's what you had hoped for.

Binding from one ListBox to another ListBox?

I am trying to bind a ListBox to another ListBox within the same window. The left hand sided Listbox has data in it that one can select. But I want a user to be able to click on the item(s) in the left hand listbox and those same item(s) would be displayed in the other listbox on the right hand side.
EDITED: Of course you can bind a UI property to another UI property (Dependency Property actually) using ElementName, but I recommend to bind the properties to one view model. See a simplified example below.
View model:
public ObservableCollection<ItemObject> Items { get; set; }
public ObservableCollection<ItemObject> SelectedItems { get; set; }
Left:
<ListBox ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}" />
(Note that there is no SelectedItems dependency property actually. See question like: Select multiple items from a DataGrid in an MVVM WPF project)
Right:
<ListBox ItemsSource="{Binding SelectedItems}" />
This works fine. Furthermore, with this approach, the list on the right hand can be customized with ease (eg order, filter, ... by using CollectionView).
private ICollectionView _collectionView;
private ICollectionView _CollectionView {
get { return _collectionView
?? (_collectionView = CollectionViewSource.GetDefaultView(SelectedItems)); }
}
public ICollectionView FilteredItems {
get { _CollecitionView.Filter(...); }
}
<ListBox ItemsSource={"Binding FilteredSelectedItems"} />
Such an MVVM approach is sometimes laborious, but eventually found as beneficial.
You name the first listbox, then any other control on the xaml will bind to that control using it's name in the ElementName attribute of the binding.
For example there are two listboxes and one text box. The top listbox has multiselections and those selection(s) are shown on the lower listbox. While the textbox only gets the first item selected.
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<converters:PathToFilenameConverter x:Key="FilenameConverter" />
<x:Array x:Key="FileNames" Type="system:String">
<system:String>C:\Temp\Alpha.txt</system:String>
<system:String>C:\Temp\Beta.txt</system:String>
<system:String>C:\Temp\Gamma.txt</system:String>
</x:Array>
</StackPanel.Resources>
<ListBox Name="lbFiles"
SelectionMode="Multiple"
ItemsSource="{StaticResource FileNames}"
Margin="10"/>
<ListBox ItemsSource="{Binding SelectedItems, ElementName=lbFiles }" Margin="10" />
<TextBlock Text="{Binding SelectedItem,
ElementName=lbFiles,
Converter={StaticResource FilenameConverter}}"
Margin="10" />
</StackPanel>
Note...the code is binding using the SelectedItems property for the lower list box and not SelectedItem used by the TextBlock.
As an aside, another answer has the use of an ObservableCollection, that is not needed unless the array is dynamically changing; otherwise any array can be used. Depending on loading, say from a VM, it may need to adheres to the INotifyPropertyChanged.

Binding a List within a bound ObservableCollection

In my WPF application I have an Observablecollection "CollOfPersons" of Persons, where each Person Object has a property "NotesOnPerson" of type
List<Notes>
(among other properties). Now I bind "CollOfPersons" to a listbox lb in code via
lb.ItemsSource = CollOfPersons;
Now I have set up a template how to display a person, namely I wrap each person in a 'Expander' and display the basic properties (e.g., Name, Age) in Expander.header, and this works fine, e.g.,
<Expander.Header>
<StackPanel Orientation="Horizontal">
...
<TextBlock Text="{Binding Path=Name}"/>
...
</StackPanel>
</Expander.Header>
However, now I'd like to bind the NotesOnPerson list of notes to the Expander.Content. But since this is again a list of varying size I don't know how to do it. Same strategy as above does not work, because I don't know the name of the Expander (as I knew the name 'lb' of the big listbox in which all the stuff is). Something like
<Expander.Content>
<ListBox ItemTemplate="{StaticResource NoteTemplate}"
ItemsSource="{Binding Path=NotesOnPerson}"/>
</Expander.Content>
doesn't seem to work. I seem to be confused about code and XAML binding. How should I solve this?
Is this what you're looking for?
<Expander.Content>
<ListBox ItemTemplate="{StaticResource NoteTemplate}"
ItemsSource="{Binding NotesOnPerson}"/>
</Expander.Content>
I'm not familiar with the Expander, but since NotesOnPerson is (presumably) a property of Person and not of Name, that's the syntax you should use. (the Path= is optional, since just putting it in like that is another way to declare the Path)

TextBlock Text-Binding from ListView items

How can I change the Text of a TextBlock when the selection in my ListView changes?
I don't want do this manually...
All Items of the ListView are LogEntry's (class)... Can I use Binding in the Text-Attribute of the TextBlock to get a specific property of the selected Item?
Yes, in fact there are multiple solutions, i give you the most "WPF" like answer, but imo also the least flexible.
First you need to set the IsSynchronizedWithCurrentItem="True" property
Now if you select an item, the bound CollectionView will set the item as the CurrentItem.
Now your TextBox/Block can bind to this specific item via a special binding syntax using a '/'.
For Example:
<TextBlock Text="{Binding LogEntries/}"/>
of course you can get a specific property from the current item via binding aswell
<TextBlock Text="{Binding LogEntries/WarningMessage}"/>
Hope that helps.
assuming you have a listview like this:
<ListView ItemSource="{Binding LogEntries}" Name="logs" IsSynchronizedWithCurrentItem="True">
</ListView>
<ContentControl Content="{Binding ElementName=logs, Path=SelectedItem}" ContentTemplate="{StaticResource logTemplate}"/>
Now you need to provide that logTemplate in the Resources.
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:LogEntry}">
<TextBlock Text="{Binding Path=LogText}"/> <-- This is a Property-Binding of your custom class
</DataTemplate>
</UserControl.Resources>
The last thing missing is to provide the namespace to your local class LogEntry. If you use an awesome tool like Resharper, it will insert the namespace for you. Otherwise, here a sample declaration:
<UserControl xmlns:local="clr-namespace:My.App.Namespace.LogEntry;assembly=My.App"
... (rest of namespace declarations)

Categories

Resources