I'm binding a List of a custom class (which implements the INotifyPropertyChanged) into a ListBox.
When I add any item to the List, the ListBox doesn't update, but if I scroll, the ListBox gets updated.
The class
class MyClass : INotifyPropertyChanged
{
private string name = string.Empty;
public string Name { /* INotifyPropertyChanged */ }
}
The property
private List<MyClass> loaded;
public List<MyClass> Loaded { /* INorutyPropertyChanged */ }
The ListBox
<ListBox ItemsSource={Binding Loaded} />
If I force override the List property, it works fine:
Loaded = new List<MyClass>() { new MyClass { Name = "test"; } }
Update to:
public ObservableCollection<MyClass> Loaded { get; private set; }
and
<ListBox ItemsSource={Binding Loaded, UpdateSourceTrigger=PropertyChanged} />
Also, you do not need to use INotiftyPropertyChanged for your Loaded property. If the binding happens once, and the data-source does not change, there's no need.
Edit:
Here's a working example.
MainWindow.xaml
<Window x:Class="WpfApplication3.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"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Width="200" ItemsSource="{Binding Items, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Width="100" Height="75" HorizontalAlignment="Right" Content="Add" Command="{Binding AddItem}"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new DataContext();
}
}
DataContext.cs
namespace WpfApplication3
{
public class DataContext
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public DataContext()
{
Items = new ObservableCollection<Item>
{
new Item
{
Value = "test"
}
};
AddItem = new RelayCommand(() =>
{
Items.Add(new Item
{
Value = "new item"
});
}, () => true);
}
}
public class Item
{
public string Value { get; set; }
}
}
RelayCommand.cs
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
Let me know if you have any questions.
Related
Models
public class EmployeeDetails
{
public string Name { get; set; }
public int Age {get;set;}
}
public class AddressDetails
{
public EmployeeDetails EmployeeName { get; set; }
public string City { get; set; }
}
View
<Window x:Class="ClassCollection.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:ClassCollection"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Source={StaticResource loc},Path=ViewModel}"
>
<Grid>
<StackPanel Margin="0 20 0 0">
<TextBox x:Name="txt1" Width="90" Height="20" Text="{Binding Details.EmployeeName}"/>
<TextBox x:Name="txt2" Width="90" Height="20" Text="{Binding Details.City}" Margin="0 20 0 0"/>
</StackPanel>
<Button x:Name="btn" Width="90" Height="25" Content="Add" Command=" {Binding AddCommand}"/>
</Grid>
</Window>
ViewModel
public class Viewmodel
{
public ObservableCollection<AddressDetails> EmployeeList;
public Viewmodel()
{
EmployeeList = new ObservableCollection<AddressDetails>();
LoadCommand();
}
private AddressDetails _details;
public AddressDetails Details
{
get { return _details; }
set
{
_details = value;
}
}
// Commands
public ICommand AddCommand { get; set; }
private void LoadCommand()
{
AddCommand = new CustomCommand(Add, CanAdd);
}
private bool CanAdd(object obj)
{
return true;
}
private void Add(object obj)
{
EmployeeList.Add(new AddressDetails { EmployeeName = Details.EmployeeName, City = Details.City });
}
}
Locator
public class Locator
{
private static Viewmodel viewmodel = new Viewmodel();
public static Viewmodel ViewModel
{
get { return viewmodel; }
}
}
How to add TextBox value to collection list using MVVM?
The Above is my code that I have tried. It shows null reference exception if I do like above. What would be the problem?
Update
I have two fields in EmployeeDetails class. So I must give input for these two field when add to collection. But I need only one field Name to insert to the collection. How to do it?
Analysis
It seems the _details field is not «initialized».
Solution
Please consider introducing the appropriate field initialization, for example:
private readonly AddressDetails _details = new AddressDetails
{
EmployeeName = new EmployeeDetails()
};
I made an example as simple as possible.
I have a class ViewModelMain whose will implement several viewmodels.
I am trying to bind my slider value on a viewmodel in my ViewModelMain.
Here my code:
MainWindow.xaml.cs
I set the datacontext here, don't know if it is realy a good idea.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
VMMain vm = new VMMain();
this.DataContext = vm;
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.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">
<Grid>
<Slider Height="23" Name="page_slider" Width="100" Value="{Binding Path=p.NbrLine}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Minimum="0" Maximum="10"/>
<TextBox Text="{Binding Value, ElementName=page_slider, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="28" HorizontalAlignment="Stretch" Name="Voiture1Label" VerticalAlignment="Stretch" Margin="0,110,0,172"></TextBox>
</Grid></Window>
ViewModelMain.cs
ViewModelBase : the class which implement the INotifyPropertyChanged
ModelPage : my model
MyPage : my sub viewmode which is the viewmodel of ModelPage
ViewModelMain : my final viewmodel which will implement more viewmodel
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
public class ModelPage
{
public int NbrLine { get; set; }
public int NbrCapsLock { get; set; }
}
public class MyPage : ViewModelBase
{
private ModelPage _model;
public MyPage(ModelPage m)
{
_model = m;
}
public int NbrLine
{
get { return (_model.NbrLine); }
set
{
if (_model.NbrLine == value) return;
_model.NbrLine = value;
OnPropertyChanged("NbrLine");
}
}
public int NbrCapsLock
{
get { return (_model.NbrCapsLock); }
set
{
if (_model.NbrCapsLock == value) return;
_model.NbrCapsLock = value;
OnPropertyChanged("NbrCapsLock");
}
}
}
public class ViewModelMain
{
public MyPage p;
public ViewModelMain()
{
p = new MyPage(new ModelPage(){NbrLine = 5, NbrCapsLock = 1});
}
}
when i launch it, my slider is still on 0 doesn't understand why it is not on 5.
p is a field, not a property. You should only bind to properties:
public MyPage p { get; set; }
Actually you chould transform p into property like that. WPF can not bind to simple attributes.
public class ViewModelMain
{
public MyPage p { get; set; }
public ViewModelMain()
{
p = new MyPage(new ModelPage() { NbrLine = 5, NbrCapsLock = 1 });
}
}
The code below creates two list boxes. When an item in the first list box is selected, I am trying to select the corresponding item in the second list box when there is a match between NameOne and NameTwo. However, it does not select my item in the second list box. Why is that?
XAML:
<Window x:Class="ListBoxTesting.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox Name="ListBoxOne" Grid.Row="0" ItemsSource="{Binding ListOne}" SelectedItem="{Binding SelectedTypeOne}" DisplayMemberPath="NameOne"/>
<ListBox Name="ListBoxTwo" Grid.Row="1" ItemsSource="{Binding ListTwo}" SelectedItem="{Binding SelectedTypeTwo}" DisplayMemberPath="NameTwo"/>
</Grid>
</Window>
Code:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public class TypeOne : INotifyPropertyChanged
{
public string NameOne { get; set; }
public TypeOne(string name)
{
NameOne = name;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class TypeTwo : INotifyPropertyChanged
{
public string NameTwo { get; set; }
public TypeTwo(string name)
{
NameTwo = name;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
TypeOne m_SelectedTypeOne;
public TypeOne SelectedTypeOne
{
get
{
return m_SelectedTypeOne;
}
set
{
m_SelectedTypeOne = value;
SelectedTypeOne.NotifyPropertyChanged("NameOne");
foreach (TypeTwo typeTwo in ListTwo)
{
if (typeTwo.NameTwo == value.NameOne)
{
SelectedTypeTwo = typeTwo;
}
}
}
}
TypeTwo m_SelectedTypeTwo;
public TypeTwo SelectedTypeTwo
{
get
{
return m_SelectedTypeTwo;
}
set
{
m_SelectedTypeTwo = value;
SelectedTypeTwo.NotifyPropertyChanged("NameTwo");
}
}
public List<TypeOne> ListOne { get; set; }
public List<TypeTwo> ListTwo { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
ListOne = new List<TypeOne>();
ListOne.Add(new TypeOne("Mike"));
ListOne.Add(new TypeOne("Bobby"));
ListOne.Add(new TypeOne("Joe"));
ListTwo = new List<TypeTwo>();
ListTwo.Add(new TypeTwo("Mike"));
ListTwo.Add(new TypeTwo("Bobby"));
ListTwo.Add(new TypeTwo("Joe"));
}
}
Create a "Container" ViewModel which implements INotifyPropertyChanged instead of using the Window itself as a container.
Doing DataContext = this; is not recommended.
public class ViewModel: INotifyPropertyChanged
{
public TypeOne SelectedTypeOne
{
get { return m_SelectedTypeOne; }
set
{
m_SelectedTypeOne = value;
NotifyPropertyChanged("SelectedTypeOne");
//foreach (TypeTwo typeTwo in ListTwo)
//{
// if (typeTwo.NameTwo == value.NameOne)
// {
// SelectedTypeTwo = typeTwo;
// }
//}
//these kind of horrible for loops from 500 years ago are not needed in C#. Use proper LINQ:
SelectedTypeTwo = ListTwo.FirstOrDefault(x => x.NameTwo == value.NameOne);
}
}
TypeTwo m_SelectedTypeTwo;
public TypeTwo SelectedTypeTwo
{
get { return m_SelectedTypeTwo; }
set
{
m_SelectedTypeTwo = value;
NotifyPropertyChanged("SelectedTypeTwo");
}
}
}
Then, in the UI:
DataContext = new ViewModel();
Ok maybe i'm a little bit stupid but i can't find a way to Add an Item to my Combobox and get it as SelectedItem. So what do i miss?
Current Code
LoginV.XAML
<ComboBox Height="23" Margin="102,2,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="155"
IsEditable="True"
DisplayMemberPath="Loginname"
ItemsSource ="{Binding alleBenutzer}"
SelectedItem="{Binding selectedBenutzer}"/>
LoginVM.CS
public List<User> alleBenutzer{ get; set; }
public User selectedBenutzer
{
get { return _selectedBenutzer; }
set
{
_selectedBenutzer = value;
if (selectedBenutzer != null)
{
//do stuff
}
RaisePropertyChanged(() => Reg(() => benutzerEinrichtungen));
}
}
User.cs
public class User
{
public int Id { get; set; }
public string Loginname { get; set; }
}
Summary
How can i provide following behavior?
User runes the App added the Word "Admin" in the Combobox Control which will result in an SelectedItem != null so that i can do if(selectedBenutzer.Loginname =="Admin") DoStuff;
Use ObservableCollection instead of List for alleBenutzer:
private ObservableCollection<string> _alleBenutzer;
public ObservableCollection<string> alleBenutzer
{
get
{
return _alleBenutzer;
}
set
{
_alleBenutzer= value;
RaisePropertyChanged("alleBenutzer");
}
}
Add a button Add user.
Add AddUser() method to your ViewModel:
public void AddUser()
{
alleBenutzer.Add(new User {...});
}
Assign a RelayCommand to Click event of the button.
Assign the same RelayCommand to AddUser() method of your ViewModel
ok after some further research it turns out that i need to bind to the Text Property which contains the Value i was looking for
here an simple example
XAML
<ComboBox Height="23" HorizontalAlignment="Left" Margin="89,23,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120"
IsEditable="True"
ItemsSource="{Binding mySimpleItems}"
SelectedItem="{Binding mySimpleItem}"
Text="{Binding myNewSimpleItem}"/>
Code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new SimpleVM();
}
}
public class SimpleVM
{
private string _mySimpleItem;
private string _myNewSimpleItem;
private bool isNew = true;
#region properties
public ObservableCollection<string> mySimpleItems { get; set; }
public string mySimpleItem
{
get { return _mySimpleItem; }
set
{
_mySimpleItem = value;
if (_mySimpleItem != null)
{
isNew = false;
MessageBox.Show(_mySimpleItem);
}
else
isNew = true;
}
}
public string myNewSimpleItem
{
get { return _myNewSimpleItem; }
set
{
_myNewSimpleItem = value;
//if SelectedItem == null
if (isNew)
if (_myNewSimpleItem == "Super")
{
mySimpleItem = _myNewSimpleItem;
mySimpleItems.Add(_myNewSimpleItem);
}
}
}
#endregion
#region cTor
public SimpleVM()
{
var ObCol = new ObservableCollection<string>();
ObCol.Add("Max");
ObCol.Add("Dennis");
ObCol.Add("Lucas");
mySimpleItems = ObCol;
}
#endregion
}
I have a listbox that is bound to an observablecollection. The observable collection contains a list of objects, each with it's own observablecollection. What i want is to click an item in the first listbox and have it's list of things displayed in the second listbox. Can I do this in pure WPF?
Just bind the ItemsSource of the second listbox to the SelectedItem of the first list box.
Edit: here is some code.
public partial class MainWindow : Window
{
public MainWindow()
{
TestItems = new ObservableCollection<Test>();
InitializeComponent();
for (int i = 0; i < 5; i++)
TestItems.Add(InitTest(i));
}
public ObservableCollection<Test> TestItems { get; set; }
private Test InitTest(int index)
{
Test test = new Test();
test.Name = "Test" + index.ToString();
test.Test2Items = new ObservableCollection<Test2>();
for (int i = 0; i <= index; i++)
{
Test2 test2 = new Test2();
test2.Label = test.Name + "_label" + i.ToString();
test.Test2Items.Add(test2);
}
return test;
}
}
public class Test
{
public string Name { get; set; }
public ObservableCollection<Test2> Test2Items { get; set; }
public override string ToString()
{
return Name;
}
}
public class Test2
{
public string Label { get; set; }
public override string ToString()
{
return Label;
}
}
Xaml
<Window x:Class="WpfApplication1.MainWindow"
x:Name="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF Example" Height="300" Width="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="ListBox1" Grid.Column="0" ItemsSource="{Binding TestItems, ElementName=MyWindow}" />
<ListBox Grid.Column="1" ItemsSource="{Binding SelectedItem.Test2Items, ElementName=ListBox1}" />
</Grid>
</Window>
Your view models could look something like this: (I am using my BindableBase here)
class MainViewModel : Bindablebase {
public ObservableCollection<ItemViewModel> Items { get; private set; }
private ItemViewModel _selectedItem;
public ItemViewModel SelectedItem {
get { return _selectedItem; }
set { SetProperty(ref _selectedItem, value, "SelectedItem"); }
}
}
class ItemViewModel : BindableBase {
public ItemViewModel (string name) {
Name = name;
Items = new ObservableCollection<string>();
}
public string Name { get; private set; }
public ObservableCollection<string> Values { get; private set; }
private string _selectedValue;
public string SelectedValue {
get { return _selectedValue; }
set { SetProperty(ref _selectedValue, value, "SelectedValue"); }
}
}
And then your view would have:
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<!--
Note that the DataContext here could be ommitted
and the bindings would be like {Binding SelectedItem.Values}
-->
<ComboBox DataContext="{Binding SelectedItem}"
ItemsSource="{Binding Values}"
SelectedItem="{Binding SelectedValue}"/>