Reputation: 189
I have a ListBox
bound to a list of objects and for each item in the ListBox
there is a TextBlock
bound with a converter but when the properties of the objects change, I can't get those TextBlock
updated while I can get them updated if not bound with a converter.
The xmal of the window is as follows:
<Window x:Class="BindingPropertyChanged.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BindingPropertyChanged"
mc:Ignorable="d"
Title="MainWindow" Height="400" Width="600">
<Window.Resources>
<local:LocationToTextConverter x:Key="LocationToTextConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<ListBox
Grid.Row="2"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="NameText"
Text="{Binding Converter={StaticResource LocationToTextConverter}, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock x:Name="DescriptionText"
Grid.Row="1" Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label HorizontalAlignment="Right" Content="Place" />
<ComboBox Grid.Column="1"
ItemsSource="{Binding Places}"
SelectedValue="{Binding SelectedItem.PlaceId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsEditable="False"/>
<Label Grid.Row="1" HorizontalAlignment="Right" Content="Description" />
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding SelectedItem.Description,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnNotifyDataErrors=True}"
VerticalAlignment="Center"/>
</Grid>
</Grid>
</Window>
On the left is the aforementioned ListBox
and on the right are a TextBox
and a ComboBox
for authoring the selected item in the ListBox
. In ListBox.ItemTemplate
, DescriptionText
can get updated if I alter the text of TextBox
on the right while NameText
cannot get updated because it uses a converter in the binding.
The following code is the classes of the domain model:
public class DomainBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberNameAttribute] string propertyName = "None")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Location : DomainBase
{
private int _placeId;
private string _description;
public int PlaceId
{
get { return _placeId; }
set
{
if (_placeId == value) return;
_placeId = value;
OnPropertyChanged();
}
}
public string Description
{
get { return _description; }
set
{
if (_description == value) return;
_description = value;
OnPropertyChanged();
}
}
}
public class Place : DomainBase
{
public int Id { get; set; }
public string Name { get; set; }
}
There is a Repository
class for dummy data:
public class Repository
{
public static ICollection<Place> _places;
public static ICollection<Place> Places
{
get { return _places; }
}
public static ICollection<Location> _locations;
public static ICollection<Location> Locations
{
get { return _locations; }
}
static Repository()
{
_places = new List<Place>();
_places.Add(new Place() { Id = 1, Name = "Downtown Center" });
_places.Add(new Place() { Id = 2, Name = "Headquarter" });
_locations = new List<Location>();
_locations.Add(new Location() { PlaceId = 1, Description = "Room 101" });
_locations.Add(new Location() { PlaceId = 2, Description = "B06" });
}
}
And the ViewModel
class set to the DataContext
of the window:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Location> _items = new ObservableCollection<Location>(Repository.Locations);
private Location _selectedItem;
public ObservableCollection<Location> Items
{
get
{
return _items;
}
private set
{
if (_items == value) return;
_items = value;
OnPropertyChanged();
OnPropertyChanged("SelectedItem");
}
}
public Location SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem == value) return;
_selectedItem = value;
OnPropertyChanged();
}
}
public ObservableCollection<Place> Places
{
get
{
return new ObservableCollection<Place>(Repository.Places);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "None")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Finally, the converter:
class LocationToTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string result = "";
Location location = value as Location;
if (location != null)
{
Place place = Repository.Places.Single(o => o.Id == location.PlaceId);
if (place != null)
{
string placeName = place.Name;
if (!String.IsNullOrWhiteSpace(placeName))
{
result = placeName + ", ";
}
}
result += location.Description;
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
What I want is to display the Place
's Name
with the Location
's Description
in the ListBox
and when I alter the selected Location
's Description
, the display name in the ListBox
should be changed instantly as well. Does anyone know how to achieve that?
Upvotes: 0
Views: 77
Reputation: 45096
why not just have a property and bind to it?
public class Location : DomainBase
{
private int _placeId;
private string _description;
public int PlaceId
{
get { return _placeId; }
set
{
if (_placeId == value) return;
_placeId = value;
OnPropertyChanged();
}
}
public string Description
{
get { return _description; }
set
{
if (_description == value) return;
_description = value;
OnPropertyChanged(); // make sure this Notify RepPlusDescription
}
}
public string RepPlusDescription
{
get
{
string result;
Place place = Repository.Places.FirstOrDefault(o => o.Id == placeId);
if (place != null)
{
string placeName = place.Name;
if (!String.IsNullOrWhiteSpace(placeName))
{
result = placeName + ", ";
}
}
return result += location.Description;
}
}
}
Upvotes: 0
Reputation: 1108
You problem is here
<TextBlock x:Name="NameText"
Text="{Binding Converter={StaticResource LocationToTextConverter}, UpdateSourceTrigger=PropertyChanged}" />
In your binding you didn't specify a path. When you did this, the item that gets binded will be a location object. Since you are changing the Descrition property of the Location object and not the Location object itself, TextBlock is not getting the property changed notification.
You only need to change a few lines of code, use a MultiBinding instead.
<TextBlock.Text>
<MultiBinding>
<Binding />
<Binding Path=Description />
//dont forget to specify the converter
....
class LocationToTextConverter : IMultiValueConverter
{
//in the Convert method set, ignore value[1] and change location to values[0]
Location location = values[0] as Location;
now your converter should get called anytime you change the description property.
Upvotes: 3
Reputation: 3968
The problem is that the Location
is not changed so the NotifyPropertyChanged
is not invoked. The easiest solution is to create property on the Location
. So there are two option how you can do it:
OnPropertyChanged("Location")
from Location
so that it is triggeredLocation
and bind to this property instead of the Location
Upvotes: 0