Binding GridView to List<Object> - c#

I've been scouring through the web (including stack overflow) for the solution to this problem. The current problem I'm having is that I'm binding that my data incorrectly to my View. When a user first navigates to the home page, the GridView shows nothing. When i go to another page and then come back to the home page, the list is shown except it is populated with strings that say "MyProject.Model.MyTask"
I know the issue isn't with my model or viewmodel as I'm able to retrieve the list properly with the data inside. One issue could be with my await call in my viewmodel. Any thoughts as to what I'm doing wrong in binding my List to the GridView?
ViewModel
public HomeViewModel(IDataService dataService, INavigationService navigationService) _dataService = dataService;
_navigationService = navigationService;
Initialize();
}
private async Task Initialize() {
MyTasks = await ApiHandlker.GetMyTasks();
}
private List<MyTask> _myTasks;
public List<MyTask> MyTasks {
get { return _myTasks; }
set { _myTasks = value; }
}
Model
public class MyTask {
public string TaskID { get; set; }
public string TaskName { get; set; }
public string TaskAssigneeID { get; set; }
//...
}
View
<GridView x:Name="gridViewMyTasks" ItemsSource="{Binding MyTasks}">
<TextBlock Text="{Binding TaskName}"></TextBlock>
</GridView>

Don't bind it to a list, bind instead to an ObservableCollection<MyTask>. You're populating the list AFTER the GridView has bound to it, so the GridView doesn't know it changed. If you're not populating it and instead simply setting it, you'll need to implement INotifyPropertyChanged.
Secondly, what you're essentially doing is setting the source to your list, then trying to add a TextBlock also as a source (and being ignored). What you're looking for instead is to set the ItemTemplate. Something like:
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TaskName}"/>
</DataTemplate>
</GridView.ItemTemplate>
See the documentation for more information.

You need to implement INotifyPropertyChanged and raise PropertyChanged in the setter of MyTasks.
You may find my recent MSDN article on asynchronous data binding helpful.

You're missing parts of the XAML:
<GridView x:Name="gridViewMyTasks" ItemsSource="{Binding MyTasks}">
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TaskName}"/>
<DataTemplate>
<GridView.ItemTemplate>
</GridView>

Related

Combobox binding issue

My ComboBox does not get populated with data.
Class Employee set to public, has variables such as:
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
Code on UserControl:
public IEnumerable<csEmployee> employeeList;
public ObservableCollection<csEmployee> _employeeSorted { get; set; }
public ucAddClient()
{
InitializeComponent();
//Establish connection
var GetMyData = new DataAccess();
//Get data by procedure
employeeList = GetMyDataPV.ExecuteStoredProc<csEmployee>("procedure", new {KeyDate = Key_to_extract});
employeeList = employeeList.Where(record => record.EmployeeLevelID > 300);
_employeeSorted = new ObservableCollection<csEmployee>(employeeList.Where(record => record != null));
}
And WPF:
<ComboBox x:Name="cbAddManager"
Foreground="#FF4D648B"
FontSize="12"
IsEditable="True"
ItemsSource="{Binding _employeeSorted}"
DisplayMemberPath="FirstName"
PreviewKeyDown="cbAddManager_PreviewKeyDown"
Width="200">
<!--<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width ="50" Text="{Binding LastName}"/>
<TextBlock Text=", "/>
<TextBlock Width ="50" Text="{Binding FirstName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>-->
</ComboBox>
Do you have any idea, why ComboBoxis not populated? When I do this in code (I add it in user control class) it gets data needed.
Im not sure if Im binding it correctly?
That is because you assign a new instance of a collection to your _employeeSorted property after InitializeComponent. At that time, the binding is already set up and does not get notified that you have updated the property from null, because you do not implement INotifyPropertyChanged.
There are multiple ways to solve the issue:
Initialize the collection before InitializeComponent and work on this same collection if you intend to change it, using Clear and Add instead of creating a new instance on changes.
Implement the INotifyPropertyChanged interface and use it to notify changes to your property so that the bindings are updated the the changes are applied in the user interface, e.g.:
public partial class MyUserControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<csEmployee> _employeeSortedField;
public ObservableCollection<csEmployee> _employeeSorted
{
get => _employeeSortedField;
set
{
if (_employeeSortedField == value)
return;
_employeeSortedField = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Expose a depenedency property for the collection instead and bind it to a collection in your view model that is passed as data context of the UserControl, thus moving the data access out it and separating the view from the business logic and data (recommended, see below MVVM).
Another issue might be that you do not set your data context to the UserControl itself in XAML (which is not recommened by the way, although it might solve your issue). In this case, the binding is unable to resolve the property at runtime (a binding error will be shown in the output window).
<UserControl x:Class="YourProject.YourControl"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
As a note, it seems that you mix your business logic with your UserControl (view). Leverage the MVVM design pattern to create view models and seprate both concerns instead. Furthermore, if you set the data context of your UserControl to itself, you break data context inheritance.

MVVM Binding Observable Collection to view?

In my application, I need to bind a checkbox list to an observable collection. I have seen many examples but I could not find a proper implementation for this and thats why I am posting this question.
The View:
<Grid Name="GrdMain" Background="White">
<ListView Name="lstConditions" VerticalAlignment="Top" Height="150"
ItemsSource="{Binding ConditionsModels}" Margin="0,25,0,0" BorderBrush="Transparent" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Path=condition}" Margin="8" Style="{StaticResource CheckBoxDefault}"
IsChecked="{Binding hasCondition,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</grid>
The model:
public class ConditionsModel
{
public int profileId { get; set; }
public string condition { get; set; }
public bool hasCondition { get; set; }
}
The View Model:
public class ConditionsViewModel : INotifyPropertyChanged
{
private ConditionsModel _conditionsModel;
private ObservableCollection<ConditionsModel> _conditionsModels;
public ConditionsModel ConditionsModel
{
get
{
return _conditionsModel;
}
set
{
_conditionsModel = value;
RaisePropertyChanged("ConditionsModel");
}
}
public ObservableCollection<ConditionsModel> ConditionsModels
{
get
{
return _conditionsModels;
}
set
{
_conditionsModels = value;
RaisePropertyChanged("ConditionsModels");
}
}
public ConditionsViewModel(int profileId)
{
ConditionsModel = new ConditionsModel();
ConditionsModels = new ObservableCollection<ConditionsModel>();
ConditionsModels.CollectionChanged += ConditionsModels_CollectionChanged;
GetConditions(profileId);
}
void ConditionsModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("ConditionsModels");
}
private void GetConditions(int profileId)
{
HealthAssessmentRepository _rep = new HealthAssessmentRepository();
_conditionsModels = _rep.GetConditions(profileId);
}
}
Is this a correct implementation? I need to update the model when the user checks or unchecks the checkbox. But its not raising the propery changed event when the check box is checked or unchecked.Should I implement the INotifyPropertyChanged interface on the model as well?
I have seen many examples, but all of them has different approaches to this and I am confused. Please show the correct implementation of this?
Thanks
I think you have missed the DataType property within DataTemplate. Just refer this
<DataTemplate DataType="{x:Type sampleApp:ConditionsModel}">
Here sampleApp in the namespace reference created within tag. And ConditionsModel is your model class.
You need to implement INotifyPropertyChanged for class ConditionsModel and raise PropertyChangedEvent for the property you want to observe/synchronize, because it is ViewModel as well.
For class ConditionsViewModel, it's the ViewModel of whole ListView, for ConditionsModel, it's the ViewModel of every line. ViewModel can be overlaid. If ConditionsModel is the domain model, my suggestion is that add a new ItemViewModel, because they belong to different layers. It's always better to distinguish the different layers properly.

Having trouble binding ViewModel to ComboBox

I have a viewmodel setup as the following
public class cDriveListVM
{
public string Drive { get; set; }
public cDriveListVM(string name)
{
Drive = name;
}
}
I declare the observablecollection in the window and set its datacontext to this observable collection.
public ObservableCollection<cDriveListVM> DriveList { get; set; }
private void dl()
{
DriveList = new ObservableCollection<cDriveListVM>();
DriveList.Add(new cDriveListVM("drive 1"));
DriveList.Add(new cDriveListVM("drive 2"));
this.DataContext = DriveList;
}
Xml for combobox:
<ComboBox x:Name="Drive_ComboBox" ItemsSource="{Binding Path=Drive}" HorizontalAlignment="Center" IsReadOnly="True" Grid.Column="0" Grid.Row="0" Width="300" Margin="10" SelectionChanged="Drive_Changed" Height="22" VerticalAlignment="Top"/>
I am just learning how to use Viewmodel so I am unsure what I am doing wrong, any help would be appreciated. I updated the xml file it results in the following combbox.
There are a few problems with this code.
One, the binding is set up wrong. Since the property with the viewmodel collection is DriveList, the binding should be ItemsSource="{Binding Path=DriveList}".
Two, you are attempting to display a field from your viewmodel, which is not doable. WPF's binding engine only works with properties, so the viewmodel should have a property:
public string Drive { get; set; }
And finally, the DisplayMemberPath should match the property name from the viewmodel: DisplayMemberPath="Drive".
Update: I just noticed that the DataContext is the observable collection itself -- I probably missed it on the first read. In that case, you want to bind directly to the data context:
ItemsSource="{Binding}"
And set DisplayMemberPath to the property you want to display:
DisplayMemberPath="Drive"

WPF Data Binding with multiple controls

In WPF, I'm trying to bind multiple controls, but the second control isn't changing when the first control is changed.
I have two classes: a Task class, and a Log class, which is stored as a collection in the Task class. The list boxes below are bound to the Tasks, and the inner Logs for the selected Task.
The problem is that the list boxes are populated fine at first load, but if I select a different task, I'd expect the Logs to be update to the collection for the new Task, but it doesn't change from those from the originally selected task on first load. What am I missing?
In the designer:
<ListBox x:Name="listBoxTasks" ItemsSource="{Binding}" DisplayMemberPath="Key"
Grid.Row="0" Grid.Column="0" Grid.RowSpan="2">
</ListBox>
<ListBox x:Name="listBoxLogs"
ItemsSource="{Binding Logs}" DisplayMemberPath="EntryDate"
Grid.Row="1" Grid.Column="1">
</ListBox>
In the code behind:
public MainWindow()
{
InitializeComponent();
IMongoCollection<Task> tasks = DataManager.GetData();
this.DataContext = tasks.AsQueryable();
}
The Task class:
public class Task : BusinessBase<Task>
{
public ObjectId _Id { get; set; }
public string Key { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
public string Details { get; set; }
public IEnumerable<Log> Logs { get; set; }
public IEnumerable<Link> Links { get; set; }
public IEnumerable<String> RelatedKeys { get; set; }
public IEnumerable<TaskItem> Items { get; set; }
}
Your Task class need to implement INotifyPropertyChanged interface so that as soon as there is any change in the underlying data it can tell WPF UI that something has changed now update/refresh your controls agains
Your task class need to implement INotifyPropertyChanged
http://msdn.microsoft.com/en-us/library/ms743695.aspx
You have to bind your first ListBox SelectedItem to object of Task model and add event handler for SelectionChanged. inside the this event you have to populate your logs by selected Task model also you have to implement INotifyPropertyChanged in your class.
It looks to me like the second binding should not work at all, as the DataContext is an enumerable of Tasks and the enumerable itself has no property called Logs. You could try working with IsSynchronizedWithCurrentItem and a binding to the current item:
<ListBox x:Name="listBoxTasks" ItemsSource="{Binding}" DisplayMemberPath="Key"
Grid.Row="0" Grid.Column="0" Grid.RowSpan="2"
IsSynchronizedWithCurrentItem="True"> <!-- Set this -->
</ListBox>
<ListBox x:Name="listBoxLogs" DisplayMemberPath="EntryDate"
Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding /Logs}"> <!-- Note the slash which indicates a binding to the current item -->
</ListBox>
You could also bind to the SelectedItem of the other ListBox but this introduces a redundant dependency between the controls. Also note that if you change any property in your data-objects you need to implement the interface mentioned by the other answerers, INotifyPropertyChanged.
I have it all working now. I implemented INotifyPropertyChanged, although that didn't solve the problem.
I am now using the MVVM pattern. This helped...the NoRM library I was using didn't have a SelectionChanged event. I created a View Model and was able to convert those Models to ObservableCollections. Now I'm just setting the Logs control DataContext on selection changed for the Task class.

Problem with DataBinding and style [WP7]

I have been trying to bind listbox with an observableConnection in Xaml on WP7 with no luck. All I want to do is to make listbox to show an instance of my class that inherits from ObservableConnection and apply some style on listbox. I can do this from code like
public Storage.Categories tmp;
...
tmp = new Storage.Categories();
listBox1.ItemsSource = tmp;
but how to apply style on that?
Here is code:
<ListBox Height="497"
HorizontalAlignment="Left"
Margin="0,104,0,0"
Name="listBox1"
VerticalAlignment="Top"
Width="450">
namespace Genesa.Storage
{
public class Categories : ObservableCollection<Category>
{
public void LoadCategories()
{
// deserialize obiect
}
public void SaveCategories()
{
// serialize obiect
}
public Categories() : base()
{
LoadCategories();
}
}
public class Category
{
public Category() { }
public String name { get; set; }
public String description { get; set; }
public Category(String _name, String _description)
{
name = _name;
description = _description;
}
public override string ToString()
{
return String.Format("{0} - {1}", name, description);
}
}
}
You're going to want to use a DataTemplate. A data template let's you structure the items in your ListBox. For example:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding name}" />
<TextBlock Text="{Binding description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Also, you might want to reconsider inheriting from ObservableCollection. If what you're doing is as simple as it looks above, you probably want to stick to creating a class which contains an ObservableCollection and which implements the INotifiyPropertyChanged interface. This is assuming you're using the MVVM design pattern. If you're not, feel free to disregard this suggestion. If you are implementing MVVM, you also want to make the Category class implement the INotifyPropertyChanged interface.
As Jared suggests, the most appropriate approach to your solution is to provide an ItemTemplate for the ListBox that defines the structure of each item in the ListBox, which enables you to bind directly to properties on your class, instead of having to override the ToString method. However, there is a small mistake in Jared's DataTemplate because it can only contain a single item, so you need to wrap the elements in some kind of container, as shown below:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding name}" />
<TextBlock Text="{Binding description}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You only need to implement the INotifyPropertyChanged on your Category class if the properties can change during the lifetime of that object. If the values are constant throughout it's lifetime, then there's no need.
usually the ObservableCollection is member of the ViewModel to which the View binds to. You don't have to inherit from ObservableCollection and the logic from Categories class can be placed inside ViewModel.
Then you need to set DataContext of Page or other object in hierarchy to be the ViewModel and then you can bind for example ListBox.ItemsSource to ViewModel.ObservableCollection.
After that DataTemplate will work in scope of Category (single item in ObservableCollection).
Regarding the logic of loading etc, there is usually one more layer responsible for these operations, which is injected to ViewModel, but if you don't want it, it's just fine.

Categories