Getting a WPF Listview to display ObservableCollection<T> using databinding - c#

I have a observable collection of type Project, that I want to be displayed in a ListView but nothing is added to my ListView which I really dont understand
My MainWindow.xaml
<ListView Name="ListViewProjects" Grid.Column="0" Grid.RowSpan="3" SelectionChanged="ListViewProjectsSelectionChanged" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" MinWidth="100">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Path=ProjectID}"/>
<TextBlock Text="{Binding Path=ProjectName}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My MainWindow.cs
public partial class MainWindow : Window
{
ObservableCollection<Project> Projects = new ObservableCollection<Project>();
ObservableCollection<Employee> Employees = new ObservableCollection<Employee>();
public MainWindow()
{
InitializeComponent();
DataContext = Projects;
Project pro1 = new Project(1, "Swordfish");
Projects.Add(pro1);
Employee empMads = new Employee("Mads", 1);
Employee empBrian = new Employee("Brian", 2);
Employees.Add(empMads);
Employees.Add(empBrian);
}
private void ListViewProjectsSelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
and my Project.cs which is the class file
[Serializable]
class Project : INotifyPropertyChanged
{
public Project(int id, string name)
{
ID = id;
Name = name;
}
private int id;
public int ID
{
get { return id; }
set
{
id = value;
NotifyPropertyChanged("ProjectID");
}
}
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged("ProjectName");
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
But nothing is added to my list I cant see what I am missing for it to work.
I have it as a observablecollection
I do DataContext = the collection
And I do binding in the xaml file
Edit codepart 1:
public ObservableCollection<Project> Projects { get; set; }
public ObservableCollection<Employee> Employees { get; set; }
public MainWindow()
{
InitializeComponent();
Projects = new ObservableCollection<Project>();
Employees = new ObservableCollection<Employee>();
DataContext = Projects;

It's because you use binding path like ProjectID while your Project class has property ID. The same goes for ProjectName and Name properties.
E:
When you have problems with binding it's very useful to look through Output tab in visual studio's debug mode to see what errors were returned from databinding engine. Those exceptions are normally not returned to user, but you can inspect them there.

As pointed out, there are a few problems here. One of the "gotchas" that get you as a new WPF developer is that you can't use member variables, you have to use public properties:
public ObservableCollection<Project> Projects { get; set; }
public ObservableCollection<Employee> Employees { get; set; }
These, of course, need to be initialized in your constructor.
Once you do that, you need to make sure the properties of the collection match what you're binding to on the ListView.

By default your member variables are going to be private. If what you have posted here is your actual code, then the XAML has no way of accessing it. Makes those properties public:
public ObservableCollection<Project> Projects = new ObservableCollection<Project>();
public ObservableCollection<Employee> Employees = new ObservableCollection<Employee>();

1.Your list needs to be a public property (Edit: unless you set the DataContext like you did...) (public ObservableCollection<Project> Projects {get;})
2.Your notification should be the same as the actual property name:
private int id;
public int ID
{
get { return id; }
set
{
id = value;
NotifyPropertyChanged("ID");
}
}
3.The binding needs to be that way as well:
<TextBlock Text="{Binding Path=ID}"/>
I think...

Related

Binding to an object property in ViewModel, or how to use multiple models with the same property names

I am trying to bind to a property of a Model via the ViewModel in an textbox. I created a wrapper in the ViewModel to access the property of the Model. Whenever I put a break point in the get section of this wrapper it triggers, but a break point in the set section is not executed after changing the textbox content. Code is according to the following tutorial: https://www.codeproject.com/Articles/1193164/MVVM-Sample-application-with-a-focus-in-property
The BaseViewModel implements the INPC interface. I am trying to follow the MVVM "rule of thumbs" as close as possible. So no INPC implementation in the model in this case, and the view can only bind to the VM.
What am I doing wrong?
XAML:
<TextBox Grid.Column="1" VerticalAlignment="Center" Text="{Binding Path=ProgramInfo.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Model:
public class ProgramInfo
{
private string _name;
private string _description;
public string Name
{
get => _name;
set
{
_name = value;
}
}
public string Description
{
get => _description;
set
{
_description = value;
}
}
}
ViewModel:
class ProgramInfoViewModel : BaseViewModel
{
private ProgramInfo _programInfo;
public ProgramInfo ProgramInfo
{
get => _programInfo;
set
{
_programInfo = value;
OnPropertyChanged("ProgramInfo");
}
}
}
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProgramInfoViewModel();
}
}
_programInfo is always null. you need to create an instance:
class ProgramInfoViewModel : BaseViewModel
{
private ProgramInfo _programInfo = new ProgramInfo();
public ProgramInfo ProgramInfo
{
get => _programInfo;
set
{
_programInfo = value;
OnPropertyChanged("ProgramInfo");
}
}
}
or
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProgramInfoViewModel { ProgramInfo = new ProgramInfo() };
}
}
a view model wrapper for model property will look like this:
class ProgramInfoViewModel : BaseViewModel
{
private ProgramInfo _programInfo;
private ProgramInfoViewModel(ProgramInfo programInfo)
{
_programInfo = programInfo;
}
public string ProgramInfoName
{
get => _programInfo.Name;
set
{
_programInfo.Name = value;
OnPropertyChanged("ProgramInfoName");
}
}
}
initialization:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var model = new ProgramInfo();
DataContext = new ProgramInfoViewModel(model);
}
}
binding path has to change accordingly:
<TextBox Text="{Binding Path=ProgramInfoName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

WPF OneWay binding works only once

I have a View that have two comboboxes. One is where user selects routing pipe type name, and the other where there should be a list of available diameters for the chosen pipe type.
Whenever user selects the pipe type, the other combobox should update the list of available diameters.
AvailableDiameters and RoutingPipeTypeName properties are static in Context class, that implements INotifyPropertyChanged interface. In xaml I've set the bindings to these properties, in code behind also the DataContext.
The problem is that the list of diameters get's updated only once, when the view is initialized.
When debugging I can see that the properties backing field's values are updated properly when selection on pipe type name is changed, only in the UI the available diameters list is not updated...
Context class:
public class Context : INotifyPropertyChanged
{
public static Context This { get; set; } = new Context();
public static string RoutingPipeTypeName
{
get => _routingPipeTypeName;
set
{
if (_routingPipeTypeName != value)
{
_routingPipeTypeName = value;
This.OnPropertyChanged(nameof(RoutingPipeTypeName));
}
}
}
public static List<double> AvailableDiameters
{
get => _availableDiameters;
set
{
//check if new list's elements are not equal
if (!value.All(_availableDiameters.Contains))
{
_availableDiameters = value;
This.OnPropertyChanged(nameof(AvailableDiameters));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
xaml:
<ComboBox Width="80" SelectedValue="{Binding Path=RoutingPipeTypeName, Mode=OneWayToSource}">
<ComboBoxItem Content="Example pipe type 1"></ComboBoxItem>
<ComboBoxItem Content="Example pipe type 2"></ComboBoxItem>
</ComboBox>
<ComboBox Width="80" SelectedValue="{Binding Path=RoutingDiameter, Mode=OneWayToSource}" ItemsSource="{Binding Path=AvailableDiameters, Mode=OneWay}">
</ComboBox>
code behind:
public Context AppContext => Context.This;
public MyView()
{
InitializeComponent();
Instance = this;
DataContext = AppContext;
}
And the client class that is responsible for updating the list of diameters:
public void InitializeUIContext()
{
Context.This.PropertyChanged += UIContextChanged;
if (Cache.CachedPipeTypes.Count > 0)
Context.RoutingPipeTypeName = Cache.CachedPipeTypes.First().Key;
}
private void UIContextChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Context.RoutingPipeTypeName))
{
Context.AvailableDiameters = Cache.CachedPipeTypes.First().Value.GetAvailableDiameters();
}
}
I expected such set-up would update the diameters combobox each time the selection is changed on the pipe types property.
Instead it updates it only once, when the view is initialized... Why?
Do not use static properties for binding to an object (which you have correctly passed to the DataContext of your view).
Declare the properties without the static modifier and replace This.OnPropertyChanged by OnPropertyChanged:
public string RoutingPipeTypeName
{
get => _routingPipeTypeName;
set
{
if (_routingPipeTypeName != value)
{
_routingPipeTypeName = value;
OnPropertyChanged(nameof(RoutingPipeTypeName));
}
}
}
You should also remove the static This from your Context class and simply write
public Context AppContext { get; } = new Context();

Why notification for List<T> property doesn't work

Why rising INotifypPropertyChanged for List<T> property doesn't work?
Consider this MCVE:
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string property = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public class TextWrapper
{
public string Text { get; set; }
public override string ToString() => Text;
}
public class ViewModel : NotifyPropertyChanged
{
public List<string> List { get; } = new List<string>();
public TextWrapper Text { get; } = new TextWrapper();
public void AddToList(string text)
{
List.Add(text);
OnPropertyChanged(nameof(List));
}
public void ChangeText(string text)
{
Text.Text = text;
OnPropertyChanged(nameof(Text));
}
}
public partial class MainWindow : Window
{
readonly ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _vm;
}
}
xaml:
<TextBlock Text="{Binding Text}" />
<ListBox ItemsSource="{Binding List}" />
Calling _vm.ChangeText(...) will properly update TextBlock, while calling _vm.AddToList(...) doesn't update ListBox (it will stay empty). Why?
Please note: I know about ObservableCollection<T> and I know about two possible workarounds (add setter to List and set it to e.g. null first and then back or change DataContext/ItemsSource). I am just curious what is under roof makes List<T> more special than TextWrapper.
When a WPF Binding handles the PropertyChanged event, it does not update its target property unless the effective value it produces has actually changed.
So unless the List property value actually changes (which it doesn't when you add an element), calling
OnPropertyChanged(nameof(List));
has no effect.
Replace
public List<string> List { get; } = new List<string>();
by
public ObservableCollection<string> List { get; } = new ObservableCollection<string>();
and write the AddToList method like this:
public void AddToList(string text)
{
List.Add(text);
}
For your TextWrapper class: Since you directly bind to the TextWrapper instance, the Binding calls its overridden ToString() method and hence produces a different value whenever the TextWrapper's Text property has changed.

Use DataContext binding to assign a label the value of a public automatic property in another class

In a nutshell, I want to use DataContext binding to assign a label the value of a public automatic property in another class.
So I have a class containing a public automatic property like so:
public class MyData
{
public string DogName { get; set; }
}
My WPF form looks like this:
The CodeBehind for my WPF form is as so:
public partial class MainWindow : Window
{
private MyData myData;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
myData = new MyData();
myData.DogName = "Lulu";
label1.DataContext = myData.DogName;
}
}
This isn't, however, changing the value of label1 to "Lulu", it just stays at "Label". What have I missed out?
Thanks
That is not exactly the common way to work with DataContexts (as you show it, MyData as a class serves no purpose at all).
Try this instead:
//C#
label1.DataContext = new MyData{ DogName = "Lulu" };
//xaml
<Label Content="{Binding Path=DogName}"/>
now, you could for instance also use MyData to contain the label's Width or so.
Another (probably most used) way is to set the parent's DataContext, and make the individual elements use it's properties:
//C#
class MyData
{
public strig DogName{ get; set; }
public strig CatName{ get; set; }
}
this.DataContext = new MyData{ DogName = "Lulu", CatName = "Fifi" };
//xaml
<Label Content="{Binding Path=DogName}"/>
<Label Content="{Binding Path=CatName}"/>

MVVM: class that implements ViewModel not updating its Model instance

So I've been trying to implement the MVVM pattern within a simple WPF application that has the following structure:
MODEL
public class Foobar
{
public string Foo { get; set; }
public string Bar { get; set; }
public string DoSomethingWithFoo()
{
return "The quick brown fox";
}
public string DoSomethingWithBar()
{
return "jumps over the lazy dog.";
}
}
VIEW MODEL (BASE)
public abstract class ViewModel : INotifyPropertyChanged
{
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
Debug.Fail("Invalid property name: " + propertyName);
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
VIEW MODEL (IMPL)
public class FoobarViewModel : ViewModel
{
private readonly Foobar foobar;
public string Foo
{
get
{
return this.foobar.Foo;
}
set
{
this.foobar.Foo = value;
OnPropertyChanged("Foo");
}
}
public string Bar
{
get
{
return this.foobar.Bar;
}
set
{
this.foobar.Bar = value;
OnPropertyChanged("Bar");
}
}
private FoobarCommand fooCommand;
public FoobarCommand FooCommand
{
get
{
return fooCommand;
}
set
{
fooCommand = value;
OnPropertyChanged("FooCommand");
}
}
private FoobarCommand barCommand;
public FoobarCommand BarCommand
{
get
{
return barCommand;
}
set
{
barCommand = value;
OnPropertyChanged("BarCommand");
}
}
private void DoSomethingWithFoo()
{
if (!string.IsNullOrEmpty(this.foobar.Foo))
{
this.foobar.Foo = this.foobar.DoSomethingWithFoo();
OnPropertyChanged("Foo");
}
}
private void DoSomethingWithBar()
{
if (!string.IsNullOrEmpty(this.foobar.Bar))
{
this.foobar.Bar = this.foobar.DoSomethingWithBar();
OnPropertyChanged("Bar");
}
}
///<remarks>
/// must use the parameterless constructor to satisfy <Window.Resources>
///</remarks>
public FoobarViewModel()
{
this.foobar = new Foobar()
{
Foo = "Lorem",
Bar = "Ipsum"
}
this.fooCommand = new FoobarCommand(DoSomethingWithFoo);
this.barCommand = new FoobarCommand(DoSomethingWithBar);
};
}
COMMAND
public class FoobarCommand : ICommand
{
Action action;
public FoobarCommand(Action action)
{
this.action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
this.action.Invoke();
}
}
VIEW
<Window.Resources>
<local:FoobarViewModel x:Key="FoobarViewModel" />
</Window.Resources>
<Grid DataContext="{StaticResource FoobarViewModel}">
<TextBox Name="FooTextBox" Text="{Binding Foo, Mode=TwoWay, ValidatesOnDataErrors=True}" />
<TextBox Name="BarTextBox" Text="{Binding Bar, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</Grid>
The problem with this approach is, despite that the ViewModel is binding okay with the View, the Model is not reflecting such changes (meaning the Model is not notifying-back changes to its instance at the ViewModel)
I would really appreciate any bit of advice regarding this post, thanks much you guys in advance.
EDIT
Updated snippets with the missing code (thanks Pavlo and Ben)
Committed solution to a public svn repo http://nanotaboada.svn.beanstalkapp.com/dotnet/trunk/Dotnet.Samples.Rijndael/ for anyone interested in checking out the whole project.
Modified Model and ViewModel methods, added ICommand implementation. For a full working sample please checkout revision 16.
Everything looks OK except one small, but important detail. It looks like you forgot to set DataContext of your view to the instance of the view model.
<Window ...
DataContext="{StaticResource FoobarViewModel}">
Without it your bindings will fail (look in the output window of Visual Studio when under debugger and you'll see binding errors).
Also note that the values will be updated in your view model and model when the TextBox looses focus. To make it update while you type set UpdateSourceTrigger to PropertyChanged on your bindings:
<TextBox Name="FooTextBox" Text="{Binding Foo, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
In your FooBarViewModel you are not instantiating your Model, it is left as null, since you marked it readonly, you will need to new it in a default constructor.

Categories