Silverlight DataContext Databinding behavior - silverlight

I'll start off with a stripped-down/sanitized version of my code:
Model:
class DataObj : INotifyPropertyChanged {
// these actually call OnPropertyChanged, and have associated private variables
public string Name { get; set; }
public int Age { get; set; }
}
class DataContextObj : INotifyPropertyChanged {
public List<DataObj> DataItems { get; set; }
}
View:
<StackPanel x:Name="MyPanel">
<TextBlock Text="{Binding Path=DataItems[0].Name}" />
<TextBlock Text="{Binding Path=DataItems[0].Age}" />
</StackPanel>
View code-behind:
//in the constructor
MyPanel.DataContext = new DataContextObj();
Now, my question is, if the DataItems list is initialized but empty, what is the expected behavior when something tries to bind to, say, the first element in the list? My understanding is that it just ignores the binding; is that true?

Yes it will ignore the binding. If subsequently an item is added to the empty list the text blocks will not update since the binding expression associated with them will not know that the change happened.
The appropriate solution is to use:
public class DataContextObj
{
public ObservableCollection<DataObj> DataItems {get; private set; }
}
Additions to the collection will notify "Item[]" has changed which will allow the binding expression to re-evaluate.

Related

Updating values in ObservableCollection

Hey I have an ObservableCollection which consists of a class with two attributes (strings = User and Response) bound to a listbox.
I would like to have the users in the listbox first, which I add with this:
for (int i = 0; i < ArrStrUser.Length; i++)
{
Users.Add(new User() { input = ArrStrUser[i].Trim() });
}
I want to add the responses to the respective user later.
If I do this, they will be added to the ObservableCollection but not update in the listbox.
Users[i].response = strOutput.Trim().Replace(Environment.NewLine, " ");
The ObservableCollecton
private ObservableCollection<Input> Users = new ObservableCollection<Input>();
The Class:
public class Input
{
public string user{ get; set; }
public string response { get; set; }
}
XAML:
<ListBox x:Name="LBresponse" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" ItemTemplate="{StaticResource UserTemplate}" />
<DataTemplate x:Key="UserTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path= user}" Width="50"/>
<TextBlock Text="{Binding Path= response}" />
<Button Content="Delete" Click="DeleteUser_Clicked" HorizontalAlignment="Left"/>
</StackPanel>
</DataTemplate>
Simple solution
Your Input class needs to implement the INotifyPropertyChanged interface and invoke the PropertyChanged event upon changing property's value in order to update the ListBox. The ObservableCollection only "cares" about adding or removing items, it doesn't handle item's property changing.
Try editing your input class like this:
public class Input : INotifyPropertyChanged
{
public string user{ get; set; }
private string _response;
public string Response{
get => _response;
set {
_response = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now changing the Response property should update the UI.
Better solution
I'd also advise you to separate the INotifyPropertyChanged implementation into its own class if you want to use it somewhere else, too. Or better yet, use a library that already has it, like the mvvm-helpers nuget package by James Montemagno.
Here's a link to the INotifyPropertyChanged implementation from that library
This is how you use it:
public class Input : ObservableObject
{
public string user{ get; set; }
private string _response;
public string Response{
get => _response;
set => SetProperty(ref _response, value);
}
}
It also supports passing in an OnChanged Action and a validation function.

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
}

WPF DataGrid cell bound to Property of Property in domain object not updating

I've tried to get at this problem from a few angles. Here I've tried to simplify it into a small test case.
I'm having problems getting a DataGrid cell to update which is bound to a property of a property. The property is set by a bound ComboBox cell in another column. The bound object is a follows, with the property I'm referring to:
public class MainObject : INotifyPropertyChanged
{
private int _subObjectId;
public virtual SubObject SubObjectObj { get; set; }
public int SubObjectId {
get { return _subObjectId; }
set { _subObjectId = value; SubObjectObj = <GetObjFromDB> };
}
...
}
public class SubObject : INotifyPropertyChanged
{
public int Id { get; set; }
public string Name { get; set; }
public string Specialty{ get; set; }
...
}
The DataGrid ItemsSource is
public ObservableCollection<MainObject> SourceData;
Now, the column in the DataGrid is a ComboBox of SubObject choices. A TextBox column next to it which is (supposed) to display the SubObject.Specialty of whatever SubObject is selected in the ComboBox.
<DataGridTemplateColumn Header="SubObjects">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubObject.Name, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox x:Name="ComboBoxSubObject" ItemsSource="{Binding Model.SubObjects, RelativeSource={RelativeSource AncestorType={x:Type uch:TestControl}}}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding SubObjectId, UpdateSourceTrigger=PropertyChanged}"
SelectionChanged="ComboBoxDoctor_OnSelectionChanged"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Specialty" Binding="{Binding Path=SubObjectObj.Specialty}"/>
When the grid is initially painted, the Specialty column is correct - it's the property is what SubObject is displayed in the other column. But when I change the ComboBox, the Specialty column does not change. Is there anyway to tell the DataGrid that the Specialty column binding source has changed and to refresh?
Thanks for any advice.
Is there anyway to tell the DataGrid that the Specialty column binding
source has changed and to refresh?
Yes, this is where your INotifyPropertyChanged implementation comes into play. You should have an OnPropertyChanged event as part of that implementation, invoking this event with a property name tells WPF that the property value has changed and to update the UI. You should call OnPropertyChanged for the Speciality property when your SubObject changes. Because they're in different classes, you'll probably need to expose a method or an event to do this:
public class SubObject : INotifyPropertyChanged
{
public int Id { get; set; }
public string Name { get; set; }
public string Specialty{ get; set; }
public void OnSpecialityChanged()
{
OnPropertyChanged("Speciality");
}
}
public class MainObject : INotifyPropertyChanged
{
private int _subObjectId;
public virtual SubObject SubObjectObj { get; set; }
public int SubObjectId
{
get { return _subObjectId; }
set
{
_subObjectId = value;
SubObjectObj = <GetObjFromDB>
SubObjectObj.OnSpecialityChanged();
}
}
}
Side point, I'm unsure of what use your SubObjectId property is serving here. Could you instead maybe use the Id property directly from the SubObjectObj?

Silverlight: binding object collection to datatemplate in itemscontrols binds empty objects to property

Let me explain my situation. I have made a user control that contains an ItemsControl
<ItemsControl Name="itemControlReviewTags">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<my:ReviewControl ReviewEvent="{Binding}" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This ItemsControl is bound to an observablecollection in the code behind
public ObservableCollection<TagEvent> tagItems = new ObservableCollection<TagEvent>();
The collection is set on the ItemsControl like so
itemControlReviewTags.ItemsSource = tagItems;
The TagEvent class is defined like below. The class is added to the collection at certain events.
public class TagEvent : EventArgs
{
public string Text { get; set; }
public string Comment { get; set; }
public string Value { get; set; }
public DateTime Time { get; set; }
public string Type { get; set; }
}
The ReviewControl in the datatemplate had a DependencyProperty like so
public TagEvent ReviewEvent
{
get
{
return (TagEvent)GetValue(ReviewEventProperty);
}
set
{
SetValue(ReviewEventProperty, value);
}
}
public static readonly DependencyProperty ReviewEventProperty = DependencyProperty.Register("ReviewEvent", typeof(TagEvent), typeof(ReviewControl), new PropertyMetadata(new TagEvent() { Comment = "hallo", Text = "De tag", Time = DateTime.Now, Type = "Mark", Value = "Mark" }, ReviewEvent_PropertyChangedCallback));
private static void ReviewEvent_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ReviewControl reviewControl = (ReviewControl)d;
reviewControl.LoadReviewEvent();
}
The strange thing is that when I run my progran the TagEvents get added to the collection and the ItemsControl shows the datatemplate for every item. The testblock I used to verify the value of the properties on the TagEvents shows the proper value of the text property. But in the ReviewEvent Dependency property I get only "empty" objects (all values empty string or default date). Those objects replace the default value as i can see that as the oldvalue in the DP callback.
I could understand the ItemsControl not showing the items, but why it is showing item that look like it's doing "new TagEvent" for every item in the collection is beyond me. Hope someone here has a suggestion for me. I tried implementing INotifyPropertyChanged on the TagEvent, but that did not seem to change anything. I could split out the properties of the TagEvent class but I don't see why I would have to do that, when I could pass the object.
Help?
I can now answer my own question. I had an statement setting the usercontrol's datacontext in code-behind that I forgot about. It messed things up.
Never leave old code laying about...

Child item in TreeView not updating

I copied the basic method of having checkbox in a treeview from the official Silverlight toolkit Checkboxes in a TreeView example.
When a user clicks on a parent TreeViewItem I want all of the child items to be checked, as in the above example. This works fine when the parent is collapsed, clicking the checkbox puts a tick in the parent and when you expand the node all children have a tick in the checkbox.
However it doesn't work if the parent is expanded. None of the children are updated to have a tick in the checkbox, although the underlying data list is updated.
My XAML is as follows:
<sdk:HierarchicalDataTemplate x:Key="NodeTemplate" ItemsSource="{Binding Path=Contracts}">
<StackPanel Orientation="Horizontal" ToolTipService.ToolTip="{Binding Path=Name}">
<CheckBox IsTabStop="False" IsThreeState="{Binding Path=HasContracts}" IsChecked="{Binding Path=Selected, Mode=TwoWay}" Click="CheckBox_Click" />
<TextBlock Text="{Binding Path=Name}" Tag="{Binding Path=ID}"/>
</StackPanel>
</sdk:HierarchicalDataTemplate>
<sdk:TreeView x:Name="tvClientContract" ItemsSource="{Binding Path=ClientContracts, Mode=TwoWay}" ItemTemplate="{StaticResource NodeTemplate}"/>
This is bound to a List<ClientContract> and uses the same code behind as in the linked example.
The ClientContract object is:
public int ID { get; set; }
public string Name { get; set; }
public List<ClientContract> Contracts { get; set; }
public bool? Selected { get; set; }
How can I force the child to repaint itself as the underlying List<ClientContract> object is updated?
If you want to use INotifyPropertyChange(what I did instead of using ObservableCollection) here is how you do it per example on the ID element:
public class myclass : INotifyPropertyChanged
{
private int id_Value;
public int ID
{
get { return id_Value; }
set
{
id_Value = value;
NotifyPropertyChanged("ID");
}
}
public string Name { get; set; }
public List<ClientContract> Contracts { get; set; }
public bool? Selected { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
I hope this helps if it was what you were trying to do.
Try using ObservableCollection<ClientContract> instead of a List<>. Usually you want to databind to this collection type instead when the data is dynamic so it can notify the UI of collection changes.

Resources