Why doesn't a ListBox bound to an IEnumerable update? - c#

I have the following XAML:
<Window x:Class="ListBoxTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ListBoxTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Model />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Items}" Grid.Row="0"/>
<Button Content="Add" Click="Button_Click" Grid.Row="1" Margin="5"/>
</Grid>
</Window>
and the following code for the Model class, which is put into main window's DataContext:
public class Model : INotifyPropertyChanged
{
public Model()
{
items = new Dictionary<int, string>();
}
public void AddItem()
{
items.Add(items.Count, items.Count.ToString());
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Items"));
}
private Dictionary<int, string> items;
public IEnumerable<string> Items { get { return items.Values; } }
public event PropertyChangedEventHandler PropertyChanged;
}
and main window's code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var model = this.DataContext as Model;
model.AddItem();
}
}
When pressing the button, the contents of the list are not being updated.
However, when I change the getter of the Items property to this:
public IEnumerable<string> Items { get { return items.Values.ToList(); } }
it starts to work.
Then, when I comment out the part which sends PropertyChanged event it stops working again, which suggests that the event is being sent correctly.
So if the list receives the event, why can't it update its contents correctly in the first version, without the ToList call?

Raising the PropertyChanged event for the Items property is only effective if the property value has actually changed. While you raise the event, the WPF binding infrastructure notices that the collection instance returned by the property getter is the same as before and does nothing do update the binding target.
However, when you return items.Values.ToList(), a new collection instance is created each time, and the binding target is updated.

Related

Loaded event is not fired for newly set content of ContentControl after it has been fully loaded

I want to be able to bind Content property of ContentPresenter to a ViewModel property, so that when ViewModel changes the value of its property, Content of ContentPresenter is determined based on DataTemplate and Loaded event is fired for newly rendered View (that is chosen based on DataTemplate). Following code example demonstrates the problem, which is, Loaded event is not fired when Content is changed.
As far as I understand Loaded event is not fired when Content of ContentPresenter is changed and I'm OK with that. The problem is that Loaded event for newly loaded context is not fired, but it gets rendered nonetheless. Is there any way to achieve this behavior?
MainWindow
<Window x:Class="WpfLoadProblem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfLoadProblem"
Height="450"
Title="MainWindow"
Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ContentViewModel}">
<local:ContentUserControl/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Command="{Binding Path=ChangeContentCommand}" Grid.Row="0">Change Content</Button>
<ContentPresenter Content="{Binding Path=Content}" Grid.Row="2"/>
</Grid>
</Window>
MainWindowViewModel
internal sealed class MainWindowViewModel : INotifyPropertyChanged
{
private object content;
public MainWindowViewModel()
{
ChangeContentCommand = new DelegateCommand(ChangeContent);
}
public ICommand ChangeContentCommand { get; }
public object Content
{
get => content;
private set
{
if (content != value)
{
content = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Content)));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void ChangeContent()
{
// After setting this property Loaded event is not fired for the 2nd and consecutive times.
Content = new ContentViewModel();
}
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
ContentUserControl
<UserControl x:Class="WpfLoadProblem.ContentUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors">
<i:Interaction.Triggers>
<i:EventTrigger x:Name="Loaded">
<i:InvokeCommandAction Command="{Binding Path=LoadedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox ItemsSource="{Binding Path=Numbers}"/>
</UserControl>
ContentViewModel
internal sealed class ContentViewModel
{
private readonly ICollection<int> numbers = new ObservableCollection<int>();
public ContentViewModel()
{
LoadedCommand = new DelegateCommand(Load);
}
public ICommand LoadedCommand { get; }
public IEnumerable<int> Numbers => numbers;
private void Load()
{
int start = numbers.Count == 0 ? 0 : numbers.Max();
numbers.Clear();
for (int number = start; number < start + 5; ++number)
{
numbers.Add(number);
}
}
}
The Loaded event won't be fired unless the control is unloaded. And it's not when the Content changes. The applied DateTemplate is cached and only the data context and bindings are updated.
Instead of relying on the Loaded event being raised, you should invoke the LoadedCommand or call Load() directly in your call ChangeContent() method right after you have set the Content property:
private void ChangeContent()
{
var viewModel = = new ContentViewModel();
Content = viewModel;
viewModel.LoadedCommand.Execute(null);
}
This is just a guess, but since the type of Content never changes, it's possible that the DataTemplate being used never changes either. Instead, it just continues to use the same DataTemplate. What would change, though, would be the DataContext. Try using the DataContextChanged event instead of Loaded and see if that works.

Data binding of ListBox to a generic List that changes

I want to be able to bind a ListBox to a List<> or ObservableCollection<> and be able to change the List itself while keeping the binding.
In ViewModel:
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private ObservableCollection<string> items = new ObservableCollection<string>();
public ObservableCollection<string> Items
{
get
{
return items;
}
set
{
items = value;
RaisePropertyChangedEvent(nameof(Items));
}
}
private void FillItems()
{
Items1 = new ObservableCollection<string>();
Items.Add("1");
Items.Add("2");
}
in View:
<ListBox x:Name="listBox" ItemsSource="{Binding Items}"/>
Now when I call the FillItems() function, the list won't show items. But if I change the code like below it will work:
private void FillItems()
{
Items.Clear();
Items.Add("1");
Items.Add("2");
}
Insure that the view model implements INotifyPropertyChanged and notifies the view when ever the target property changes.
Using the following simple View
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ListBox x:Name="listBox" ItemsSource="{Binding Items}"/>
<Button Grid.Row="1" Content="Fill" Click="Button_Click" />
</Grid>
</Window>
Along with the following code behind and view model
namespace WpfApplication1 {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
private MainViewModel viewModel;
public MainWindow() {
InitializeComponent();
viewModel = new MainViewModel() {
Items = new List<string>() {
"a", "b"
}
};
this.DataContext = viewModel;
}
private void Button_Click(object sender, RoutedEventArgs e) {
viewModel.FillItems();
}
}
public class MainViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Random random = new Random();
private List<string> items = new List<string>();
public List<string> Items {
get {
return items;
}
set {
items = value;
RaisePropertyChangedEvent(nameof(Items));
}
}
public void FillItems() {
var list = Enumerable.Range(0, 10).Select(i => random.Next(100).ToString()).Distinct().ToList();
Items = new List<string>(list);
}
}
}
Every time the button was clicked the ListBox was updated as expected for both List and ObservableCollection.
You should review your code again and make sure that the common practices that may be affecting your code are followed because what you provided in the example should work.

UI not updating for bound element

My property updates just fine, but my user interface is not updated.
What am i doing wrong?
I also tried setting the DataContext not in XAML, but in the code behind constructor, but that didn't work either.
ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
TestCommand = new RelayCommand(UpdateTest);
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(null, new PropertyChangedEventArgs(propertyName));
}
#endregion
private string _test;
public string Test
{
get { return _test; }
set
{
_test = value;
NotifyPropertyChanged();
}
}
public ICommand TestCommand { get; set; }
public void UpdateTest()
{
Test += "test ";
}
}
View:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Test}" />
<Button Grid.Row="1" Content="Test 2" Command="{Binding TestCommand}" />
</Grid>
</Window>
You are not implementing PropertyChanged correctly. The event model for .NET requires that the sender argument of the invoked delegates is set to the reference of the object actually raising the event. You set that value to null. Your code should use this instead:
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Note that for thread safety, you should also not use the "check and raise" pattern on the event field itself. You should instead store the field in a local variable, check the local variable, and then raise the event from that variable if non-null. The above, which uses the ?. operator ("null conditional operator") effectively does this; the compiler generates the local variable implicitly for you and ensures that the reference won't change between the time you check it for null and the time you actually try to use it.

Exception Occurring Infinitely in Content Control

First, this is a simplified version from a wizard control using MVVM. The problem is just easier to reproduce as described below
After much narrowing down, I have resolved an infinite exception in my code to be due to the WPF ContentControl. However, I have yet to figure out how to handle it, other than try-catch wrapping all of my possible instantiation code. Here is sample code that reproduces this...any help on how to keep this infinite exception from occurring would be greatly appreciated.
Additional Details
To sum up, the problem is that if the content control changes its contents, and the thing being loaded in throws an exception, then it will throw, then retry the load, causing the throw again and again.
MainWindow.xaml
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name ="Main">
<Grid>
<ContentControl Name="bar" Content="{Binding ElementName=Main, Path=foo}"/>
<Button Click="ButtonBase_OnClick" Margin="20" Width="50"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private UserControl _foo;
public UserControl foo
{
get { return _foo; }
set { _foo = value; OnPropertyChanged("foo"); }
}
public MainWindow()
{
InitializeComponent();
foo = new UserControl1();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
foo = new UserControl2();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
UserControl1 is blank and all default
UserControl2.xaml.cs
public UserControl2()
{
InitializeComponent();
throw new Exception();
}
Do not bind ContentControl to MainWindow. Instead use DataTemplates to select the content for the MainWindow. One example-contrived way of doing it is to bind the ContentControl's Content to the DataContext of the MainWindow.
First some observable test data is needed. The specifics of this data are not important. The main point is to have two different classes of test data from which to choose - TestData.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace fwWpfDataTemplate
{
// Classes to fill TestData
public abstract class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Student : Person { }
public class Employee : Person
{
float _salary;
public float Salary
{
get { return _salary; }
set
{
_salary = value;
OnPropertyChanged("Salary");
}
}
}
public class TestData : ObservableCollection<Person>
{
public TestData()
: base(new List<Person>()
{
new Student { Name = "Arnold" },
new Employee { Name = "Don", Salary = 100000.0f }
}) { }
}
}
Then add DataTemplates to MainWindow's resources - MainWindow.xaml:
<Window x:Class="fwWpfDataTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:fwWpfDataTemplate"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type me:Student}">
<StackPanel>
<TextBlock Text="Student"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type me:Employee}">
<StackPanel>
<TextBlock Text="Employee"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="Salary"/>
<TextBlock Text="{Binding Salary}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Change Data Context" Click="Button_Click" />
<ContentControl Grid.Row="1" Content="{Binding}"/>
</Grid>
</Window>
Note: instead of the StackPanels the contents of the DataTemplates could be UserControl1, UserControl2, etc.
Then add some code to change the data context - MainWindow.cs:
using System.Windows;
namespace fwWpfDataTemplate
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
TestData testData = new TestData();
int testIndex = -1;
private void Button_Click(object sender, RoutedEventArgs e)
{
testIndex = (testIndex + 1) % testData.Count;
this.DataContext = testData[testIndex];
}
}
}
Enjoy.

Listbox Not binding to a BindingList<T>

I have a ListBox on a form that is bound to a BindingList<T> in code behind but is not displaying the items within the BindingList<T>.
XAML:
<Window x:Class="MessageServer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MessageServer"
Name="mainWindow" Title="Message Exchange Server"
Height="350" Width="525" Closing="Window_Closing">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListBox Name="OutputList" Grid.Row="0" />
<ListBox Name="Connected" Grid.Row="1" ItemsSource="{Binding ElementName=mainWindow, Path=ConnectedClients}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=FullIPAddress}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
CodeBehind:
private BindingList<Client> _ConnectedClients;
public BindingList<Client> ConnectedClients
{
get { return _ConnectedClients; }
set { _ConnectedClients = value; }
}
public class Client : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TcpClient _tcpClient;
public TcpClient tcpClient
{
get { return _tcpClient; }
set
{
_tcpClient = value;
NotifyPropertyChanged();
}
}
public string FullIPAddress
{
get { return _tcpClient.Client.RemoteEndPoint.ToString(); }
}
public string IPAddress
{
get { return _tcpClient.Client.RemoteEndPoint.ToString().Split(':').ElementAt(0); }
}
public string PortNumber
{
get { return _tcpClient.Client.RemoteEndPoint.ToString().Split(':').ElementAt(1); }
}
public Client(TcpClient tcpClient)
{
this.tcpClient = tcpClient;
NotifyPropertyChanged();
}
private void NotifyPropertyChanged()
{
NotifyPropertyChanged("tcpClient");
NotifyPropertyChanged("FullIPAddress");
NotifyPropertyChanged("IPAddress");
NotifyPropertyChanged("PortNumber");
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Any Ideas why the list box is not displaying the items?
Not sure if this is worth mentioning but When added items to the BindingList this is done on a seperate thread to the UI Thread. but I have tried using Dispatcher.BeginInvoke() but still does not work...
It sounds like you really want to use ObservableCollection. It sounds like BindingList should work, but on this SO post they seem say ObservableCollection is for WPF and BindingList for Winforms: Differences between BindingList and ObservableCollection
Try using an ObservableCollection<T>. It was designed specifically for WPF.
You are trying to bind to Window.ConnectedClients, which is a property that doesn't exist.
You need to change your binding to DataContext.ConnectedClients to bind to Window.DataContext.ConnectedClients
ItemsSource="{Binding ElementName=mainWindow, Path=DataContext.ConnectedClients}"

Categories

Resources