TreeView MVVM, how to get selection? - wpf

I am trying to create a TreeView for my application. This is the first time I am using TreeView with the MVVM structure, by all accounts the binding is working and displaying correctly.
However:
How do I get the selection so I can perform some logic after the user selects something?
I thought that the TextValue property in SubSection class would fire PropertyChanged, but it doesn't, so I am left scratching my head.
This is the most simplified set of code I could make for this question:
Using PropertyChanged setup like this in : ViewModelBase class
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
The VeiwModel:
public class ShiftManagerViewModel : ViewModelBase
{
public ShiftManagerViewModel()
{
Departments = new List<Department>()
{
new Section("Section One"),
new Section("Section Two")
};
}
private List<Section> _sections;
public List<Section> Sections
{
get{return _sections;}
set
{
_sections = value;
NotifyPropertyChanged();
}
}
}
The classes:
public class Section : ViewModelBase
{
public Section(string depname)
{
DepartmentName = depname;
Courses = new List<SubSection>()
{
new SubSection("SubSection One"),
new SubSection("SubSection One")
};
}
private List<SubSection> _courses;
public List<SubSection> Courses
{
get{ return _courses; }
set
{
_courses = value;
NotifyPropertyChanged();
}
}
public string DepartmentName { get; set; }
}
public class SubSection : ViewModelBase
{
public SubSection(string coursename)
{
CourseName = coursename;
}
public string CourseName { get; set; }
private string _vTextValue;
public string TextValue
{
get { return _vTextValue; }
set
{
_vTextValue = value;
NotifyPropertyChanged();
}
}
}
And the XAML:
<Window.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Courses}" DataType="{x:Type viewModels:Section}">
<Label Content="{Binding DepartmentName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding TextValue}" DataType="{x:Type viewModels:SubSection}">
<Label Content="{Binding CourseName}" />
</HierarchicalDataTemplate>
</Window.Resources>
Could someone point me in the right direction?

You could cast the SelectedItem property of the TreeView to a Section or a SubSection or whatever the type of the selected item is:
Section section = treeView1.SelectedItem as Section;
if (section != null)
{
//A Section is selected. Access any of its properties here
string name = section.DepartmentName;
}
else
{
SubSection ss = treeView1.SelectedItem as SubSection;
if(ss != null)
{
string ssName = ss.CourseName;
}
}
Or you could add an IsSelected property to the Section and SubSection types and bind the IsSelected property of the TreeView to it:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
Then you get the selected item by iterating through the ItemsSource of the TreeView and look for the item that has the IsSelected source property set to true.

Related

WPF, Update ComboBox ItemsSource when it's DataContext changes

I have two classes A and B which both implement an interface IThingWithList.
public interface IThingWithList
{
ObservableCollection<int> TheList;
}
TheList in A contains 1, 2, 3
TheList in B contains 4, 5, 6
I have a controller class which has a list of IThingWithList which contains A and B
public class MyControllerClass
{
public ObservableCollection<IThingWithList> Things { get; } = new ObservableCollection<IThingWithList>() { A, B };
public IThingWithList SelectedThing { get; set; }
}
Now, in xaml I have two ComboBoxes as follows
<ComboBox
ItemsSource="{Binding MyController.Things}"
SelectedValue="{Binding MyController.SelectedThing, Mode=TwoWay}" />
<ComboBox
DataContext="{Binding MyController.SelectedThing}"
ItemsSource="{Binding TheList}" />
The first ComboBox controls which (A or B) is the data context of the second combo box.
Problem:
When I select A or B from the first ComboBox The list items of the second ComboBox are not updated.
What I have tried:
Making both A and B ObservableObjects
Making IThingWithList implement INotifyPropertyChanged
Adding UpdateSourceTrigger to the ItemsSource Bindings
Scouring Google.
Here is how I typically do the ViewModel (in your case "Controller") Base Class in order to get the functionality you are looking for:
This is the base class that all VMs derive from.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetAndNotify<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(property, value))return;
property = value;
this.OnPropertyChanged(propertyName);
}
}
Here is how I would adjust your ControllerClass:
public class MyControllerClass : ViewModelBase
{
private ObservableCollection<IThingWithList> _things;
public ObservableCollection<IThingWithList> Things
{
get => _things;
set { SetAndNotify(ref _things, value); }
}
private IThingWithList _selectedthing;
public IThingWithList SelectedThing
{
get => _selectedThing;
set{SetAndNotify(ref _selectedThing, value);}
}
}
Now adjust your XAML to have the DataContext of the container set instead of each Control (makes life easier)
<UserControl xmlns:local="clr-namespace:YourMainNamespace">
<UserControl.DataContext>
<local:MyControllerClass/>
</UserControl.DataContext>
<StackPanel>
<ComboBox
ItemsSource="{Binding Things}"
SelectedValue="{Binding SelectedThing, Mode=TwoWay}" />
<!-- ComboBox ItemsSource="See next lines" /-->
</StackPanel>
</Window>
You can change SelectedThing to be an ObservableCollection or you can have a second object that is the list and updates accordingly:
//Add into MyControllerClass
public MyInnerThingList => SelectedThing.TheList;
//Edit the SelectedThing to look like:
private IThingWithList _selectedthing;
public IThingWithList SelectedThing
{
get => _selectedThing;
set
{
SetAndNotify(ref _selectedThing, value);
RaisePropertyChanged(nameof(MyInnerThingList));
}
}
Then change the binding to:
<ComboBox ItemsSource="{Binding MyInnerThingList, Mode=OneWay}" />
You may also want to add a SelectedMyInnerThing property, but not sure if that is needed.

WPF Combobox initial dictionary binding value not showing

I have a wpf combobox bound to a property LogicalP of a class SInstance. The ItemSource for the combobox is a dictionary that contains items of type LogicalP.
If I set LogicalP in SInstance to an initial state, the combobox text field shows empty. If I select the pulldown all my dictionary values are there. When I change the selection LogicalP in SInstance gets updated correctly. If I change Sinstance in C# the appropriate combobox value doesn't reflect the updated LogicalP from the pulldown.
I've set the binding mode to twoway with no luck. Any thoughts?
My Xaml:
<UserControl.Resources>
<ObjectDataProvider x:Key="PList"
ObjectType="{x:Type src:MainWindow}"
MethodName="GetLogPList"/>
</UserControl.Resources>
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding Source={StaticResource PList}}"
DisplayMemberPath ="Value.Name"
SelectedValuePath="Value"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
My C#:
public Dictionary<string, LogicalPType> LogPList { get; private set; }
public Dictionary<string, LogicalPType> GetLogPList()
{
return LogPList;
}
public class LogicalPType
{
public string Name { get; set; }
public string C { get; set; }
public string M { get; set; }
}
public class SInstance : INotifyPropertyChanged
{
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
They are not looking at the same source.
You need to have SInstance supply both the LogPList and LogicalP.
_LogicalP is not connected to LogPList
If you want to different objects to compare to equal then you need to override Equals.
Here's my working solution. By moving the dictionary retrieval GetLogPList to the same class as that supplies the data (as suggested by Blam) I was able to get the binding to work both ways. I changed binding to a list rather than a dictionary to simplify the combobox
Here's the updated Xaml showing the new ItemsSource binding and removal of the SelectedValuePath:
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding GetLogPList}"
DisplayMemberPath ="Name"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
I then changed the dictionary LogPList to static so that it would be accessible to the class SInstance:
public static Dictionary<string, LogicalPType> LogPList { get; private set; }
Finally, I moved GetLogPList to the class SInstance as a property. Note again it's returning a list as opposed to a dictionary to make the Xaml a little simpler:
public class SInstance : INotifyPropertyChanged
{
public List<LogicalPType> GetLogPList
{
get { return MainWindow.LogPList.Values.ToList(); }
set { }
}
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}

Datagrid validation to prevent duplicate entry

Using the code below I can trap an invalid cell entry. In this simple example of a grocery shopping list, the GroceryItem.Name just needs to be filled in.
What I would like to do now is add the ability to validate that the entry doesn't already exist. If it does already exist then I'd like the same cell entry highlighted but with the appropriate message. So if the user enters "Eggs" again, the cell error message should be "Eggs is already on the list".
The item view model shouldn't know about it's container view model, so where can I check for a duplicate entry while still at cell validation for the "Name" property?
Item in the Collection
public class GroceryItem : ObservableObject, IDataErrorInfo
{
#region Properties
/// <summary>The name of the grocery item.</summary>
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChangedEvent("Name");
}
}
private string _name;
#endregion
#region Implementation of IDataErrorInfo
public string this[string columnName] {
get {
if (columnName == "Name") {
if (string.IsNullOrEmpty(Name))
return "The name of the item to buy must be entered";
}
return string.Empty;
}
}
public string Error { get { ... } }
#endregion
}
View Model holding the Collection
public class MainWindowViewModel : ViewModelBase
{
/// <summary>A grocery list.</summary>
public ObservableCollection<GroceryItem> GroceryList
{
get { return _groceryList; }
set
{
_groceryList = value;
RaisePropertyChangedEvent("GroceryList");
}
}
private ObservableCollection<GroceryItem> _groceryList;
/// <summary>The currently-selected grocery item.</summary>
public GroceryItem SelectedItem { get; [UsedImplicitly] set; }
void OnGroceryListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// doing non-validation stuff
}
}
View Binding for DataGrid
<DataGrid
x:Name="MainGrid"
ItemsSource="{Binding GroceryList}"
SelectedItem="{Binding SelectedItem}"
...
>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="TextBlock.ToolTip"
Value="{Binding Error}" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
...
<DataGridTextColumn Header="Item" Width="*" Binding="{Binding Name, ValidatesOnDataErrors=True}" IsReadOnly="false" />
</DataGrid.Columns>
</DataGrid>
I don't know if this violates MVVM but the way I would do it is pass GroceryList to GroceryItem in another constructor and save it in a private ObservableCollection groceryList in GroceryItem. It is just a reverence back to GroceryList so it does not add a much overhead.
public class GroceryItem : ObservableObject, IDataErrorInfo
{
private ObservableCollection<GroceryItem> groceryList;
public void GroceryItem(string Name, ObservableCollection<GroceryItem> GroceryList)
{
name = Name;
groceryList = GroceryList;
// now you can use groceryList in validation
}
}

Mutually Exclusive comboboxes that binds to same data source - MVVM implementation

I'm not sure my Title is right but this is the problem I am facing now.. I have the below XAML code..
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=AvailableFields}"
SelectedItem="{Binding Path=SelectedField}"
></ComboBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
What this basically does is, If my data source contains ten items, this is going to generate 10 row of comboboxes and all comboboxes are bounded to the same itemsource.
Now my requirement is Once an item is selected in the first combo box, that item should not be available in the subsequent combo boxes. How to satisfy this requirement in MVVM and WPF?
This turned out to be harder than I thought when I started coding it. Below sample does what you want. The comboboxes will contain all letters that are still available and not selected in another combobox.
XAML:
<Window x:Class="TestApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=SelectedLetters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path=AvailableLetters}"
SelectedItem="{Binding Path=Letter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
namespace TestApp
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM : INotifyPropertyChanged
{
public VM()
{
SelectedLetters = new List<LetterItem>();
for (int i = 0; i < 10; i++)
{
LetterItem letterItem = new LetterItem();
letterItem.PropertyChanged += OnLetterItemPropertyChanged;
SelectedLetters.Add(letterItem);
}
}
public List<LetterItem> SelectedLetters { get; private set; }
private void OnLetterItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != "Letter")
{
return;
}
foreach (LetterItem letterItem in SelectedLetters)
{
letterItem.RefreshAvailableLetters(SelectedLetters);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public class LetterItem : INotifyPropertyChanged
{
static LetterItem()
{
_allLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Select(c => c.ToString());
}
public LetterItem()
{
AvailableLetters = _allLetters;
}
public void RefreshAvailableLetters(IEnumerable<LetterItem> letterItems)
{
AvailableLetters = _allLetters.Where(c => !letterItems.Any(li => li.Letter == c) || c == Letter);
}
private IEnumerable<string> _availableLetters;
public IEnumerable<string> AvailableLetters
{
get { return _availableLetters; }
private set
{
_availableLetters = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("AvailableLetters"));
}
}
}
private string _letter;
public string Letter
{
get { return _letter; }
set
{
if (_letter == value)
{
return;
}
_letter = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Letter"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private static readonly IEnumerable<string> _allLetters;
}
}
}
This functionality is not provided by WPF, but it can be implemented using some custom coding.
I've created 3 ViewModel classes:
PreferencesVM - This will be our DataContext. It contains the master list of options which can appear in the ComboBoxes, and also contains a SelectedOptions property, which keeps track of which items are selected in the various ComboBoxes. It also has a Preferences property, which we will bind our ItemsControl.ItemsSource to.
PreferenceVM - This represents one ComboBox. It has a SelectedOption property, which ComboBox.SelectedItem is bound to. It also has a reference to PreferencesVM, and a property named Options (ComboBox.ItemsSource is bound to this), which returns the Options on PreferencesVM via a filter which checks if the item may be displayed in the ComboBox.
OptionVM - Represents a row in the ComboBox.
The following points form the key to the solution:
When PreferenceVM.SelectedOption is set (ie a ComboBoxItem is selected), the item is added to the PreferencesVM.AllOptions collection.
PreferenceVM handles Preferences.SelectedItems.CollectionChanged, and triggers a refresh by raising PropertyChanged for the Options property.
PreferenceVM.Options uses a filter to decide which items to return - which only allows items which are not in PreferencesVM.SelectedOptions, unless they are the SelectedOption.
What I've described above might be enough to get you going, but to save you the headache I'll post my code below.
PreferencesVM.cs:
public class PreferencesVM
{
public PreferencesVM()
{
PreferenceVM pref1 = new PreferenceVM(this);
PreferenceVM pref2 = new PreferenceVM(this);
PreferenceVM pref3 = new PreferenceVM(this);
this._preferences.Add(pref1);
this._preferences.Add(pref2);
this._preferences.Add(pref3);
//Only three ComboBoxes, but you can add more here.
OptionVM optRed = new OptionVM("Red");
OptionVM optGreen = new OptionVM("Green");
OptionVM optBlue = new OptionVM("Blue");
_allOptions.Add(optRed);
_allOptions.Add(optGreen);
_allOptions.Add(optBlue);
}
private ObservableCollection<OptionVM> _selectedOptions =new ObservableCollection<OptionVM>();
public ObservableCollection<OptionVM> SelectedOptions
{
get { return _selectedOptions; }
}
private ObservableCollection<OptionVM> _allOptions = new ObservableCollection<OptionVM>();
public ObservableCollection<OptionVM> AllOptions
{
get { return _allOptions; }
}
private ObservableCollection<PreferenceVM> _preferences = new ObservableCollection<PreferenceVM>();
public ObservableCollection<PreferenceVM> Preferences
{
get { return _preferences; }
}
}
PreferenceVM.cs:
public class PreferenceVM:INotifyPropertyChanged
{
private PreferencesVM _preferencesVM;
public PreferenceVM(PreferencesVM preferencesVM)
{
_preferencesVM = preferencesVM;
_preferencesVM.SelectedOptions.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedOptions_CollectionChanged);
}
void SelectedOptions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this,new PropertyChangedEventArgs("Options"));
}
private OptionVM _selectedOption;
public OptionVM SelectedOption
{
get { return _selectedOption; }
set
{
if (value == _selectedOption)
return;
if (_selectedOption != null)
_preferencesVM.SelectedOptions.Remove(_selectedOption);
_selectedOption = value;
if (_selectedOption != null)
_preferencesVM.SelectedOptions.Add(_selectedOption);
}
}
private ObservableCollection<OptionVM> _options = new ObservableCollection<OptionVM>();
public IEnumerable<OptionVM> Options
{
get { return _preferencesVM.AllOptions.Where(x=>Filter(x)); }
}
private bool Filter(OptionVM optVM)
{
if(optVM==_selectedOption)
return true;
if(_preferencesVM.SelectedOptions.Contains(optVM))
return false;
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
}
OptionVM.cs:
public class OptionVM
{
private string _name;
public string Name
{
get { return _name; }
}
public OptionVM(string name)
{
_name = name;
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new PreferencesVM();
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication64.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>
<ItemsControl ItemsSource="{Binding Path=Preferences}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=Options}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedOption}"></ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
**Note that to reduce lines of code, my provided solution only generates 3 ComboBoxes (not 10).

How can I bind an ObservableCollection to TextBoxes in a DataTemplate?

I am trying to successfully TwoWay bind an ObservableCollection to TextBoxes in a DataTemplate. I can get the data to display properly, but I am unable to change the list data through the UI. I have a Model class named 'model' which contains an ObservableCollection named 'List'. The class implements the INotifyPropertyChanged interface. Here is the xaml for the shell. The DataContext for Window1's grid is set to "theGrid.DataContext=model"
<Window x:Class="BindThat.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindThat"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="theGrid">
<GroupBox BorderBrush="LightGreen">
<GroupBox.Header>
<TextBlock Text="Group" />
</GroupBox.Header>
<ItemsControl ItemsSource="{Binding Path=List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</StackPanel>
This is the code for the Model class:
class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<string> _list = new ObservableCollection<string>();
public ObservableCollection<string> List
{
get { return _list; }
set
{
_list = value;
NotifyPropertyChanged("List");
}
}
public Model()
{
List.Add("why");
List.Add("not");
List.Add("these?");
}
}
Could anyone advise if I am going about this the correct way?
You need a property to bind two way, so string is not good for this.
Wrap it in a string object, like this:
public class Model
{
public ObservableCollection<StringObject> List { get; private set; }
public Model()
{
List = new ObservableCollection<StringObject>
{
new StringObject {Value = "why"},
new StringObject {Value = "not"},
new StringObject {Value = "these"},
};
}
}
public class StringObject
{
public string Value { get; set; }
}
and bind to Value property instead of "."
Also, you don't need to notify of a change in observable collection, so until your model has some other propertis of its own, it does not need to have INotifyPropertyChange. If you want your ItemsControl react to changes in the individual StringObjects, then you should add INotifyPropertyChanged to a StringObject.
And yet again, two way binding is default, so you need only
<TextBox Text="{Binding Path=Value}" />
in your binding.
I believe you need to derive your collection items from DependencyObject for TwoWay binding to work. Something like:
public class DependencyString: DependencyObject {
public string Value {
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(DependencyString), new UIPropertyMetadata(""));
public override string ToString() {
return Value;
}
public DependencyString(string s) {
this.Value = s;
}
}
public class Model {
private ObservableCollection<DependencyString> _list = new ObservableCollection<DependencyString>();
public ObservableCollection<DependencyString> List {
get { return _list; }
}
public Model() {
List.Add(new DependencyString("why"));
List.Add(new DependencyString("not"));
List.Add(new DependencyString("these?"));
}
}
...
<StackPanel x:Name="theGrid">
<GroupBox BorderBrush="LightGreen">
<GroupBox.Header>
<TextBlock Text="Group" />
</GroupBox.Header>
<ItemsControl ItemsSource="{Binding Path=List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</StackPanel>
xaml view:
<ItemsControl ItemsSource="{Binding List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
in code behind in the constructor:
DataContext = new ViewModel();
in ViewModel Class:
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<StringObject> _List = new ObservableCollection<StringObject>();
public ObservableCollection<StringObject> List
{
get { return _List; }
set
{
_List = value;
NotifyPropertyChanged("List");
}
}
public ViewModel()
{
List = new ObservableCollection<StringObject>
{
new StringObject {Value = "why"},
new StringObject {Value = "not"},
new StringObject {Value = "these"}
};
}
}
public class StringObject
{
public string Value { get; set; }
}
Be careful with a collection with type string it doesn't work, you have to use an object => StringObject

Resources