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

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

Related

Caliburn micro listbox multiple selection MVVM

I have a ListBox containing Name. Now I need to select multiple items from ListBox.
ViewModel.CS
private Person selectedListOfPeople_;
public Person SelectedListOfPeople
{
get
{ return selectedListOfPeople_;}
set
{ this.SetProperty(ref selectedListOfPeople_, value, nameof(SelectedListOfPeople));}
}
private ObservableCollection<Person> listOfPeople_;
public ObservableCollection<Person> ListOfPeople
{
get { return listOfPeople_; }
set
{
this.SetProperty(ref listOfPeople_, value, nameof(ListOfPeople));
}
}
public ShellViewModel()
{
ListOfPeople = new ObservableCollection<Person>
{
new Person("ABC"),new Person("DEF"),new Person("GHI"),new Person("JKL")
};
}
public class Person : Screen
{
private string personName_;
public string PersonName
{
get { return personName_; }
set { this.SetProperty(ref personName_, value, nameof(PersonName)); }
}
public Person(string personName)
{
PersonName = personName;
}
private bool isSelected_;
public bool IsSelected
{
get { return isSelected_; }
set { this.SetProperty(ref isSelected_, value, nameof(IsSelected)); }
}
}
View.XAML
<Grid Width="500" Height="500" Background="LightBlue">
<ListBox x:Name="ListOfPeople" SelectionMode="Multiple" Height="300" Width="300" Margin="120,100,80,100">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
in that SelectedListOfPeople is not called when the second item is selected in ListBox set to Multiple selections. How can I make sure that this event is raised every time the user makes a selection in ListBox?
One way of doing this would be to break from the convention available in that framework and bind the property manually.
But first you would need to update the property for multiselect in the view model
private ObservableCollection<Person> selectedListOfPeople;
public ObservableCollection<Person> SelectedListOfPeople {
get { return selectedListOfPeople; }
set { this.SetProperty(ref selectedListOfPeople, value, nameof(SelectedListOfPeople)); }
}
private ObservableCollection<Person> listOfPeople;
public ObservableCollection<Person> ListOfPeople {
get { return listOfPeople; }
set { this.SetProperty(ref listOfPeople, value, nameof(ListOfPeople)); }
}
public ShellViewModel() {
ListOfPeople = new ObservableCollection<Person> {
new Person("ABC"),
new Person("DEF"),
new Person("GHI"),
new Person("JKL")
};
SelectedListOfPeople = new ObservableCollection<Person>();
}
And then bind to the desired property in the view's XAML
<ListBox x:Name="ListOfPeople" SelectionMode="Multiple"
Height="300" Width="300" Margin="120,100,80,100"
SelectedItems = "{Bining SelectedListOfPeople}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
The convention will bind the items source or the ListBox and the manual binding of the SelectedItems will provided the desired behavior.

WPF Binding to DependencyProperty of UserControl in DataTemplate is not working

i have a custom userControl with DPs and have the problem that the binding to these properties only works if i use the controls outside of datatemplates.
Outside of Datatemplates works the usercontrol great.
XAML to Test the UserControl in a DataTemplate
<GroupBox Header="DataTemplate" Padding="5">
<ItemsControl ItemsSource="{Binding Dummies}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:Dummy">
<StackPanel Orientation="Horizontal" Margin="2">
<common:QuarterPicker SelectedFirstDay="{Binding Gueltigkeit}" Margin="5,0" />
<!--control the value of the item-->
<TextBlock Text="Gueltigkeit: "/>
<TextBlock Text="{Binding Gueltigkeit}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
MainWindow codebehind and ViewModel
public partial class QuarterPickerTest : UserControl
{
public QuarterPickerTest()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
public ViewModel()
{
this.Dummy = new Dummy { Gueltigkeit = DateTime.Today };
this.Dummies = new List<Dummy>
{
new Dummy {Gueltigkeit = DateTime.Today},
new Dummy {Gueltigkeit = DateTime.Today.AddMonths(6)},
new Dummy {Gueltigkeit = DateTime.Today.AddDays(-6)},
};
}
public Dummy Dummy { get; set; }
public List<Dummy> Dummies { get; set; }
}
Here is the code behind of my UserControl
#region SelectedFirstDay
public DateTime SelectedFirstDay
{
get { return (DateTime)GetValue(SelectedFirstDayProperty); }
set { SetValue(SelectedFirstDayProperty, value); }
}
public static readonly DependencyProperty SelectedFirstDayProperty
= DependencyProperty.Register("SelectedFirstDay", typeof (DateTime), typeof (QuarterPicker),
new FrameworkPropertyMetadata(DateTime.Today, SelectedFirstDayPropertyChangedCallback) { BindsTwoWayByDefault = true });
private static void SelectedFirstDayPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var quarterPicker = dependencyObject as QuarterPicker;
var date = (DateTime)args.NewValue;
var quarter = GetQuarter(date);
quarterPicker.UpdateProperties(date.Year, quarter);
if (date.Year == quarterPicker.LeftInfiniteYear && quarter == quarterPicker.LeftInfiniteQuarter)
quarterPicker.ShowPopupButtonLeftInfinite();
}
#endregion
Thanks for any suggestions!

ItemsControl that contains bound ComboBox in ItemTemplate

I've just stuck in a problem to bind collection in ItemsControl with ItemTeplate that contains bounded ComboBox.
In my scenario I need to "generate" form that includes textbox and combobox for each item in collection and let user to update items. I could use DataGrid for that but I'd like to see all rows in edit mode, so I use ItemsControl with custom ItemTemplate.
It's ok to edit textboxes but when you try to change any ComboBox, all other ComboBoxes in other rows will change too.
Is it a bug or feature?
Thanks, Ondrej
Window.xaml
<Window x:Class="ComboInItemsControlSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="480" Width="640">
<Window.Resources>
<CollectionViewSource x:Key="cvsComboSource"
Source="{Binding Path=AvailableItemTypes}" />
<DataTemplate x:Key="ItemTemplate">
<Border BorderBrush="Black" BorderThickness="0.5" Margin="2">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=ItemValue}" />
<ComboBox Grid.Column="2"
SelectedValue="{Binding Path=ItemType}"
ItemsSource="{Binding Source={StaticResource cvsComboSource}}"
DisplayMemberPath="Name"
SelectedValuePath="Value" />
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Path=SampleItems}"
ItemTemplate="{StaticResource ItemTemplate}"
Margin="10" />
</Grid>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel
{
public ViewModel()
{
SampleItems = new List<SampleItem> {
new SampleItem { ItemValue = "Value 1" },
new SampleItem { ItemValue = "Value 2" },
new SampleItem { ItemValue = "Value 3" }
};
AvailableItemTypes = new List<SampleItemType> {
new SampleItemType { Name = "Type 1", Value = 1 },
new SampleItemType { Name = "Type 2", Value = 2 },
new SampleItemType { Name = "Type 3", Value = 3 },
new SampleItemType { Name = "Type 4", Value = 4 }
};
}
public IList<SampleItem> SampleItems { get; private set; }
public IList<SampleItemType> AvailableItemTypes { get; private set; }
}
public class SampleItem : ObservableObject
{
private string _itemValue;
private int _itemType;
public string ItemValue
{
get { return _itemValue; }
set { _itemValue = value; RaisePropertyChanged("ItemValue"); }
}
public int ItemType
{
get { return _itemType; }
set { _itemType = value; RaisePropertyChanged("ItemType"); }
}
}
public class SampleItemType : ObservableObject
{
private string _name;
private int _value;
public string Name
{
get { return _name; }
set { _name = value; RaisePropertyChanged("Name"); }
}
public int Value
{
get { return _value; }
set { _value = value; RaisePropertyChanged("Value"); }
}
}
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Picture
here you can see the result on picture
I believe it's because you're binding to a CollectionViewSource, which tracks the current item. Try binding directly to your list instead, which won't track the current item
<ComboBox Grid.Column="2"
SelectedValue="{Binding Path=ItemType}"
DisplayMemberPath="Name"
SelectedValuePath="Value"
ItemsSource="{Binding RelativeSource={
RelativeSource AncestorType={x:Type ItemsControl}},
Path=DataContext.AvailableItemTypes}" />
While you have a combobox in each row, it doesnt see these comboboxes as being seperate. i.e. They are all using the same collection, and the same selectedValue, so when a value changes in one box, it changes in all of them.
The best way to fix this is to add the SampleItemType collection as a property on your SampleItem model and to then bind the combo box to that property.

WPF - Can't display ObservableCollection in a window

I have an ObservableCollection that I can't seem to get to display in a window. Here is the code:
The View Model:
public class QueryParamsVM : DependencyObject
{
public string Query { get; set; }
public QueryParamsVM()
{
Parameters = new ObservableCollection<StringPair>();
}
public ObservableCollection<StringPair> Parameters
{
get { return (ObservableCollection<StringPair>)GetValue(ParametersProperty); }
set { SetValue(ParametersProperty, value); }
}
// Using a DependencyProperty as the backing store for Parameters. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ParametersProperty =
DependencyProperty.Register("Parameters", typeof(ObservableCollection<StringPair>), typeof(QueryParamsVM), new UIPropertyMetadata(null));
}
public class StringPair: INotifyPropertyChanged
{
public string Key { get; set; }
public string Value
{
get { return _value; }
set { _value = value; OnPropertyChanged("IsVisible"); }
}
private string _value;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The Window xaml:
<Window x:Class="WIAssistant.QueryParams"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Query Parameters" Height="390" Width="504">
<Grid>
<ListBox DataContext="{Binding Parameters}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Key}" ></TextBlock>
<TextBlock Text="{Binding Value}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
The calling code:
// Get Project Parameters
QueryParams queryParams = new QueryParams();
queryParams.ViewModel.Parameters.Add(new StringPair {Key = "#project", Value = ""});
queryParams.ShowDialog();
I have tried putting debug statements in the bindings. The Parameter gets bound to, but the Value binding never gets called.
Any Ideas on how to get my list to show up?
Try
<ListBox ItemsSource="{Binding Parameters}">
or
<ListBox DataContext="{Binding Parameters}" ItemsSource="{Binding}">
Something odd here:
public string Value
{
get { return _value; }
set { _value = value; OnPropertyChanged("IsVisible"); }
}
You're raising a property change event on a different property. Should probably be:
public string Value
{
get { return _value; }
set { _value = value; OnPropertyChanged("Value"); }
}

WPF Binding to local variable

Can you bind to a local variable like this?
SystemDataBase.cs
namespace WebWalker
{
public partial class SystemDataBase : Window
{
private string text = "testing";
...
SystemDataBase.xaml
<TextBox
Name="stbSQLConnectionString"
Text="{SystemDataBase.text}">
</TextBox>
??
Text is set to the local variable "text"
The pattern is:
public string Text {get;set;}
and the binding is
{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}
If you want the binding to update automatically you should make it a DependencyProperty.
I think 3.5 added ElementName to bindings, so the following is a little easier:
<Window x:Name="Derp" ...
<TextBlock Text="{Binding Text, ElementName=Derp}"/>
To bind to a local "variable" the variable should be:
A property, not a field.
Public.
Either a notifying property (suitable for model classes) or a dependency property (sutable for view classes)
Notifying property example:
public MyClass : INotifyPropertyChanged
{
private void PropertyType myField;
public PropertyType MyProperty
{
get
{
return this.myField;
}
set
{
if (value != this.myField)
{
this.myField = value;
NotifyPropertyChanged("MyProperty");
}
}
}
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Dependency property example:
public MyClass : DependencyObject
{
public PropertyType MyProperty
{
get
{
return (PropertyType)GetValue("MyProperty");
}
set
{
SetValue("MyProperty", value);
}
}
// Look up DependencyProperty in MSDN for details
public static DependencyProperty MyPropertyProperty = DependencyProperty.Register( ... );
}
If you're doing a lot of this, you could consider binding the DataContext of the whole window to your class. This will be inherited by default, but can still be overridden as usual
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}">
Then for an individual components you can use
Text="{Binding Text}"
To bind a local variable which is present in your Window class it has to be :
1. Public property
2. A notifying property. For this your window class should implement INotifyPropertyChanged interface for this property.
Then in the constructor
public Assgn5()
{
InitializeComponent();
this.DataContext = this; // or **stbSQLConnectionString**.DataContext = this;
}
<TextBox
Name="stbSQLConnectionString"
Text="{Binding text}">
</TextBox>
Need to add the following line in the int constructor:
this.DataContext = this;
And use this in the XAML:
<TextBox Text = "{Binding SomeProperty}" />
Exmaple:
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public string PersonName { get; set; }
public MainWindow()
{
InitializeComponent();
PersonName = "default";
this.DataContext = this;
}
void button1_Click(object sender, RoutedEventArgs args)
{
MessageBox.Show($"Hello {PersonName}");
}
}
MainWindow.xaml
<StackPanel>
<TextBox Name="textbox1"
Text="{Binding PersonName, Mode=TwoWay}"
/>
<Button Name="button1" Click="button1_Click" Content="Click Me" />
</StackPanel>

Resources