WPF: Binding to property of a class in a ObservableCollection - wpf

i have a class called Robot:
public class Robot
{
public string name { get; private set; }
public Robot(string robotName)
{
name = robotName;
}
}
I made in my ModelView an ObservableCollection of this class:
public ObservableCollection<Robot> Robots { get; private set; } = new ObservableCollection<Robot>();
And I need now to bind this ObservableCollection to my ListView, but I need the property name binded to the ListView, not the class converted to a string.
<ListView ItemsSource="{Binding Robots}" />
How do I do this?

Set the DisplayMemberPath property to the name of the item property:
<ListView ItemsSource="{Binding Robots}" DisplayMemberPath="name" />
The Robot property should be named Name - using Pascal Casing to adhere to widely accepted naming conventions:
public class Robot
{
public string Name { get; }
public Robot(string name)
{
Name = name;
}
}
The XAML would then be
<ListView ItemsSource="{Binding Robots}" DisplayMemberPath="Name" />

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.

Binding to property of collection elements

I use MVVM pattern in my WPF application.
I have ObservableCollection Records in my ViewModel.
public enum RecordState
{
NotChanged,
Changed,
Added,
Deleted,
AlreadyExist
}
public class Record
{
public string FirstId { get; set; }
public RecordState State { get; set; }
public string CurrentId
{
get { return GetIdFromInstance(Instance); }
}
public MyStronglyTypedClass Instance { get; set; }
}
public class MyViewModel
{
public ObservableCollection<Record> Records;
// other code
}
In View i have DataGrid.
<DataGrid ItemsSource="{Binding }" //>
What i have to write(if that possible) in ItemsSource="{Binding /* here */}", so that Datagrid Items changed to
Records[0].Instance
Records[1].Instance
Records[2].Instance
...
Records[Records.Count-1].Instance
{Binding} means that your ItemsSource is the DataContext of that DataGrid(probably inherited from ancestor elements).
What you should do is set the DataContext of your top-level element(Window, UserControl etc..) to your ViewModel class.
And then as Gary suggested:
<DataGrid ItemsSource="{Binding Records}">
The link you gave about dynamic elements does the same in that matter, it adds more complicated element bindings and DataTemplates.

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?

Nested Collections not updating the UI when properties change

I had a listbox of user controls and each usercontrol displays properties of the bounded data and bind a collection to custom control when the custom control changes the data the UI not updated my code is as follows :
ObservableCollection<Subscription> subscriptions = new ObservableCollection<Subscription> SubscriptionRepository.GetSubscriptions());
SubListBox.ItemsSource = subscriptions;
xaml :
<DataTemplate x:Key="MyDataTemplate">
<UserControls:SubscriptionUC />
</DataTemplate>
<ListBox x:Name="SubListBox" ItemTemplate="{StaticResource MyDataTemplate}">
</ListBox>
user control :
<TextBlock Text="{Binding Path=DaysAttended}" />
<cc:CustomControl SubscriptionSource="{Binding Path=SubscriptionDays,Mode=TwoWay}" />
Subscription class :
public class Subscription : INotifyPropertyChanged
{
public int SubscriptionTypeId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public ObservableCollection<SubscriptionDay> SubscriptionDays { get; set; }
public int DaysAttended { get { return SubscriptionDays.Count(d => d.Attended == true); } }
public void DayChanged()
{
RaisePropertyChanged("SubscriptionDays");
RaisePropertyChanged("DaysAttended");
}
}
DayChanged() is called from SubscriptionDay class when SubscriptionDay property changed and it is called but DaysAttended not updated.
You also need to have setter for DaysAttended property,For Two Way binding it is must that your property should have getter and setter .I hope this will help.

Resources