I have an ObservableCollection<string> bound to a ItemsControl as the ItemsSource, the binding works fine from the VM to the View but if I change the content of the binding in the TextBox it will not update the ObservableCollection that it is bound to.
I can't seem to work out why, does anyone know why this is?
Here is my code:
<ItemsControl ItemsSource="{Binding Metrics, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Grid.Column="1" Grid.Row="1" Margin="0, 20, 0, 0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel Orientation="Horizontal">
<TextBox Name="CalibrationNameTB" Grid.Column="1" Text="{Binding ., UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Style="{StaticResource baseStyle}" Margin="0, 1" Padding="5, 1" Width="270" FontSize="12"/>
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl" >
<StackPanel Orientation="Horizontal" >
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
You can't update a string because it's immutable.
What you should do is to replace the ObservableCollection<string> with an ObservableCollection<YourType> where YourType is a class with a public string property that you can get or set:
class YourType : INotifyPropertyChanged
{
private string _theString;
public string TheString
{
get { return _theString; }
set { _theString = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then you bind to this property in your XAML markup:
<WrapPanel Orientation="Horizontal">
<TextBox Name="CalibrationNameTB" Grid.Column="1" Text="{Binding TheString, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource baseStyle}" Margin="0, 1" Padding="5, 1" Width="270" FontSize="12"/>
</WrapPanel>
Related
I have a quite complicated structure of ListViews. Inside this structure are TextBoxes with values from my ViewModel. When I change a value in some textbox, the property in ViewModel doesn't update. The "AllTexts" property in ViewModel still contains only "Hello" strings.
Basically, I want to show user structure of strings and then let the user change this structure. After he finishes his modification I want to save his changes. "Hello" strings are here just for testing.
My ViewModel:
class MainWindowViewModel
{
public ObservableCollection<ObservableCollection<ObservableCollection<string>>> AllTexts { get; set; }
public int SelectedGroupIndex { get; set; }
public int SelectedColumnIndex { get; set; }
public ICommand AddGroup { get; private set; }
public ICommand AddColumn { get; private set; }
public MainWindowViewModel()
{
this.AllTexts = new ObservableCollection<ObservableCollection<ObservableCollection<string>>>();
this.SelectedGroupIndex = -1;
this.SelectedColumnIndex = -1;
this.AddGroup = new Command(this.AddGroupCommandHandler);
this.AddColumn = new Command(this.AddColumnCommandHandler);
}
private void AddGroupCommandHandler()
{
var tempColumn = new ObservableCollection<string>() { "Hello", "Hello", "Hello", "Hello", "Hello" };
var tempGroup = new ObservableCollection<ObservableCollection<string>>();
tempGroup.Add(tempColumn);
this.AllTexts.Add(new ObservableCollection<ObservableCollection<string>>(tempGroup));
}
private void AddColumnCommandHandler()
{
if (this.SelectedGroupIndex >= 0 && this.SelectedGroupIndex < this.AllTexts.Count)
{
var tempColumn = new ObservableCollection<string>() { "Hello", "Hello", "Hello", "Hello", "Hello" };
this.AllTexts[this.SelectedGroupIndex].Add(tempColumn);
}
}
}
My View:
<Window.Resources>
<ResourceDictionary>
<local:MainWindowViewModel x:Key="vm" />
</ResourceDictionary>
</Window.Resources>
<Grid Margin="10,10,10,10" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="300" />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemsSource="{Binding AllTexts, Source={StaticResource vm}, Mode=TwoWay}"
Background="Red"
SelectedIndex="{Binding SelectedGroupIndex, Source={StaticResource vm}}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<ListView
Background="Yellow"
ItemsSource="{Binding Path=., Mode=TwoWay}"
SelectedIndex="{Binding SelectedColumnIndex, Source={StaticResource vm}}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<ListView
Background="Green"
ItemsSource="{Binding Path=., Mode=TwoWay}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=., Mode=TwoWay, NotifyOnSourceUpdated=True}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Width="100" Height="40"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,20,0,0">
<Button Content="Add Group" Width="120" Height="30"
Command="{Binding AddGroup, Source={StaticResource vm}}" />
<Button Content="Add Column" Margin="20,0,0,0" Width="120" Height="30"
Command="{Binding AddColumn, Source={StaticResource vm}}" />
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,20,0,0">
<TextBlock Width="120" Height="30" FontSize="20"
Text="{Binding SelectedGroupIndex, Source={StaticResource vm}}" />
<TextBlock Width="120" Height="30" Margin="20,0,0,0" FontSize="20"
Text="{Binding SelectedColumnIndex, Source={StaticResource vm}}" />
</StackPanel>
</Grid>
Could someone, please help me?
Thank you.
Your ViewModel has to notify the View about the changes, or else the View retains original values of the ViewModel
In this case, string cannot notify the changes made to itself. Only its enclosing observable collection can notify about changes made to itself like add or remove and does not monitor further into its elements.
So you need an observable string:
public class MyString : DependencyObject
{
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(MyString), new PropertyMetadata(""));
}
To use in the collection:
public ObservableCollection<ObservableCollection<ObservableCollection<MyString>>> AllTexts { get; set; }
I also added the following line to the MyString class in order to test the code and it worked.
public static MyString Hello { get { return new MyString { Value = "Hello" }; } }
Obviously, this is how it will be used:
var tempColumn = new ObservableCollection<MyString>() { MyString.Hello, MyString.Hello, MyString.Hello, MyString.Hello, MyString.Hello };
In xaml there are also some unnecessary things which you can get rid of:
use ItemsSource="{Binding}" for both ListViews, and use Text="{Binding Value}" for the TextBox. (there is no need for explicit TwoWay in any of those)
ProjectInformation instance = lstbxindex.SelectedItem as ProjectInformation;
string name = instance.ProjectRow.Name;
IEditableCollectionView items = lstbxindex.Items;
if(items.CanRemove)
{
items.Remove(lstbxindex.SelectedItem);
}
Using these lines of code remove listboxitems. after edit the values I need to add the values in listbox.
XAML
<ListBox ItemsSource="{Binding}" HorizontalContentAlignment="Left" x:Name="lstbxindex" SelectionMode="Extended" Foreground="White" FontSize="20px" Height="241" BorderBrush="#555555" Margin="10,34,16,0" VerticalAlignment="Top" Width="322" Background="#555555" >
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel Orientation="Horizontal" Margin="5" >
<TextBlock Height="40px" Width="80px" Text="{Binding Roundedhour1}" FontSize="24" Background="#555555" Foreground="Black"></TextBlock>
<Label x:Name="items" Content="{Binding ProjectRow.Name}" Margin="35,0,0,0" MouseDoubleClick="items_MouseDoubleClick" Foreground="White"></Label>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Set the ItemsSource property of the ListBox to an ObservableCollection<ProjectInformation> and add and remove items from this Collection using the Add and Remove methods.
XAML:
<ListBox HorizontalContentAlignment="Left" x:Name="lstbxindex" SelectionMode="Extended" Foreground="White" FontSize="20px" Height="241" BorderBrush="#555555" Margin="10,34,16,0" VerticalAlignment="Top" Width="322" Background="#555555" >
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel Orientation="Horizontal" Margin="5" >
<TextBlock Height="40px" Width="80px" Text="{Binding Roundedhour1}" FontSize="24" Background="#555555" Foreground="Black"></TextBlock>
<Label x:Name="items" Content="{Binding ProjectRow.Name}" Margin="35,0,0,0" MouseDoubleClick="items_MouseDoubleClick" Foreground="White"></Label>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code:
public class ProjectInformation
{
public int Roundedhour1 { get; set; }
}
public partial class MainWindow : Window
{
private ObservableCollection<ProjectInformation> _sourceCollection = new ObservableCollection<ProjectInformation>();
public MainWindow()
{
InitializeComponent();
lstbxindex.ItemsSource = _sourceCollection;
//add
ProjectInformation item = new ProjectInformation() { Roundedhour1 = 1 };
_sourceCollection.Add(item);
}
private void items_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
//remove
_sourceCollection.Remove(lstbxindex.SelectedItem as ProjectInformation);
}
}
I want to create a list of TextBox in binding with ObservableCollection<String> in a ViewModel but i have a problem. The binding performs only by source to dialog but it not works by dialog to source
in my ViewModel i have
private ObservableCollection<String> _ListSashDim;
public ObservableCollection<String> ListSashDim
{
get { return _ListSashDim; }
set {
if (_ListSashDim != value)
{
_ListSashDim = value;
this.RaisePropertyChanged("ListSashDim");
}
}
}
the list is initialized as below
private ObservableCollection<string> createListSashPosition(int numAnte)
{
ObservableCollection<string> ls = new ObservableCollection<string>();
for (int i = 0; i < numAnte; i++)
{
ls.Add("X");
}
return ls;
}
in my Xaml i have a DataTemplate
<!--#region DataTemplate ListSashDim-->
<DataTemplate x:Key="DataTemplateSashDim">
<ItemsControl>
<StackPanel Orientation="Vertical" Width="116" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock FontSize="10" HorizontalAlignment="Center" Text="Sash Dim"></TextBlock>
<TextBox x:Name="tbSashDim" Width="90" Text="{Binding Path=.,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ></TextBox>
</StackPanel>
</ItemsControl>
</DataTemplate>
<!--#endregion-->
and an ItemsControl in a StackPanel
<StackPanel
Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center" FlowDirection="LeftToRight"
Margin="0,10,0,0">
<ItemsControl ItemsSource="{Binding ListSashDim, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0" ItemTemplate="{DynamicResource DataTemplateSashDim}" Padding="8" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
Thanks so much
Manuel
I have done the following in XAML
<ItemsControl x:Name="CursorLegendIC" Grid.Column="0" Grid.Row="1" ItemsSource="{Binding}" Margin="0,0" Padding="0,0,0,-300">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Ellipse Width="8" Height="8" HorizontalAlignment="Left" Margin="0,0,0,-16" Fill="{Binding SeriesColor, Converter={StaticResource ColorToBrushConverter}}" />
<TextBlock Margin="10,0,0,0" HorizontalAlignment="Left" TextWrapping="Wrap" FontSize="11" FontWeight="Bold" Foreground="{Binding SeriesColor, Converter={StaticResource ColorToBrushConverter}}" Text="{Binding SeriesName}"/>
<TextBlock FontSize="11" HorizontalAlignment="Left" TextWrapping="Wrap" Margin="10,-3,0,4" Foreground="{Binding SeriesColor, Converter={StaticResource ColorToBrushConverter}}" Text="{Binding YValue, StringFormat=\{0:0.000\}}" />
<TextBlock FontSize="11" HorizontalAlignment="Left" TextWrapping="Wrap" Margin="10,-8,0,4" Foreground="{Binding SeriesColor, Converter={StaticResource ColorToBrushConverter}}" Text="{Binding RenderableSeries.YAxisId}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And I have set the data context accordingly:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
CursorLegendIC.DataContext = this.RolloverSeriesWithoutFirstData;
}
And set the Observable collection property as public
public ObservableCollection<SeriesInfo> RolloverSeriesWithoutFirstData
{
get
{
ObservableCollection<SeriesInfo> Temp = rolloverModifier.SeriesData.SeriesInfo;
return Temp;
}
}
But binding is still not working!
It seems to only take the binding at the first instance.
When data collection is later added, the binding changes does not seem to take effect.
Any help? Thanks
Your issue is that the instance (the entire collection) of the property SeriesInfo changes, without the owner of RolloverSeriesWithoutFirstData (lets call it MyWindow) is notified of the change. Either make your own event, or implemenet INotifyPropertyChanged. I've made an example with INPC:
class SeriesData : INotifyPropertyChanged
{
private ObservableCollection<SeriesInfo> _seriesInfo;
public ObservableCollection<SeriesInfo> SeriesInfo
{
set{ SetProperty(ref _seriesInfo, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
private bool SetProperty<T>(ref T storage, T value, [CallermemberName] string propertyName = null)
{
if(Equals(storage,value)) return false;
storage = value;
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
return true;
}
}
In MyWindow you does this:
class MyWindow : Window, INotifyPropertyChanged
{
public ObservableCollection<SeriesInfo> RolloverSeriesWithoutFirstData
{
get{ return rolloverModifier.SeriesData.SeriesInfo; }
}
public event PropertyChangedEventHandler PropertyChanged;
public MyWindow()
{
// Get rolloverModifier
rolloverModifier.SeriesData.PropertyChanged += SeriesDataPropertyChanged;
}
private void SeriesDataPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch(e.PropertyName)
{
case "SeriesInfo":
RaisePropertyChanged("RolloverSeriesWithoutFirstData");
break;
}
}
private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
Now SeriesData notifies it's listeners (our case MyWindow) that one of it's properties has changed value. MyWindow will then relay that notification to it's listeners (the bindings) that it's property: RolloverSeriesWithoutFirstData has changed.
Assuming that you are using MVVM pattern, you should remove the code behind and just bind to your ObservableCollection :
<ItemsControl x:Name="CursorLegendIC" Grid.Column="0" Grid.Row="1" ItemsSource="{Binding RolloverSeriesWithoutFirstData}" Margin="0,0" Padding="0,0,0,-300">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Ellipse Width="8" Height="8" HorizontalAlignment="Left" Margin="0,0,0,-16" Fill="{Binding SeriesColor, Converter={StaticResource ColorToBrushConverter}}" />
<TextBlock Margin="10,0,0,0" HorizontalAlignment="Left" TextWrapping="Wrap" FontSize="11" FontWeight="Bold" Foreground="{Binding SeriesColor, Converter={StaticResource ColorToBrushConverter}}" Text="{Binding SeriesName}"/>
<TextBlock FontSize="11" HorizontalAlignment="Left" TextWrapping="Wrap" Margin="10,-3,0,4" Foreground="{Binding SeriesColor, Converter={StaticResource ColorToBrushConverter}}" Text="{Binding YValue, StringFormat=\{0:0.000\}}" />
<TextBlock FontSize="11" HorizontalAlignment="Left" TextWrapping="Wrap" Margin="10,-8,0,4" Foreground="{Binding SeriesColor, Converter={StaticResource ColorToBrushConverter}}" Text="{Binding RenderableSeries.YAxisId}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
+Questions: what is rolloverModifier ? is rolloverModifier.SeriesData.SeriesInfo modified?
You just need to implement the INotifyPropertyChanged Interface in the class that you defined your RolloverSeriesWithoutFirstData property in. As that property has no setter, you will have to manually raise the NotifyPropertyChanged event whenever you update the collection:
(pseudo code):
rolloverModifier.SeriesData.SeriesInfo = DataAccess.GetNewCollection();
NotifyPropertyChanged("RolloverSeriesWithoutFirstData");
Change
CursorLegendIC.DataContext = this.RolloverSeriesWithoutFirstData
for
CursorLegendIC.ItemsSource= this.RolloverSeriesWithoutFirstData
Or as You can see in the above answer, remove code behind and use clear mvvm
<ListBox Height="434" HorizontalAlignment="Left" Margin="6,238,0,0" Name="listBox1" VerticalAlignment="Top" Width="432" DataContext="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" Margin="4" Foreground="{StaticResource PhoneAccentBrush}"></TextBlock>
<StackPanel Orientation="Horizontal" Margin="4">
<TextBlock Text="Set" Margin="16" Foreground="{StaticResource PhoneAccentBrush}" />
<TextBlock Text="Weight" Margin="16" Foreground="{StaticResource PhoneAccentBrush}" />
<TextBlock Text="Reps" Margin="10,16,0,16" Foreground="{StaticResource PhoneAccentBrush}" />
</StackPanel>
<ListBox Name="setsAndReps" Height="auto" Width="auto" ItemsSource="{Binding Sets}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SetNumber}"/>
<TextBox Text="{Binding Weight}"/>
<TextBox Text="{Binding Reps}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The outer listbox's item source is being set to an observable collection of a user defined class called excercise
public class excercise : IComparable, IEquatable<excercise>, INotifyPropertyChanged
{
string name;
int max;
int NUM_SETS;
ObservableCollection<set> sets;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return this.name; }
set { this.name = value; }
}
public excercise(string name)
{
this.name = name;
this.NUM_SETS = 0;
this.sets = new ObservableCollection<set>();
}
public ObservableCollection<set> Sets
{
get{return this.sets; }
}
public ObservableCollection<set> getSets()
{
return this.sets;
}
}
The properties in the inner list box are from the set class but none of them are being displayed and I am not sure what the problem is.
your first listbox doesn't have any itemsource set and the datacontext is set to an empty binding (both on line 1 of your code)