ItemsSource Data Binding Not Binding Properly - c#

I am fairly new to xaml and C# data bindings. I am attempting to bind a Picker to an IList using ItemsSource, ItemDisplayBinding, and SelectedItem. The Picker binds to the IList, but it doesn't display the correct information.
I've looked at several resources, and the Monkey App picker was a pretty big help. https://github.com/xamarin/xamarin-forms-samples/tree/master/UserInterface/MonkeyAppPicker
I tried replicating what he had done, but to no avail.
<Picker ItemsSource="{Binding Mills}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding SelectedMill, Mode=TwoWay}"/>
namespace TDSDesktop
{
public class PurchaseAgreementBindings : INotifyPropertyChanged
{
public class Mill
{
public string Name { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Phone { get; set; }
}
public static class MillData
{
public static IList<Mill> Mills { get; private set; }
static MillData()
{
Mills = new List<Mill>();
Mills.Add(new Mill
{
Name = "Name1",
Address1 = "123",
Address2 = "City, State Zip",
Phone = "555-555-5555"
});
Mills.Add(new Mill
{
Name = "Name2",
Address1 = "456",
Address2 = "City, State Zip",
Phone = "888-888-8888"
});
}
}
public IList<Mill> Mills { get { return MillData.Mills; } }
Mill selectedMill;
public Mill SelectedMill
{
get { return selectedMill; }
set
{
if (selectedMill != value)
{
selectedMill = value;
OnPropertyChanged();
}
}
}
}
}
Theoretically, the picker should display two values (Name1 and Name2) because I have the ItemDisplayBinding set to Name. However, when I run my program, the values are "TDSDesktop.PurchaseAgreementBindings+Mill" for both. Clearly I am missing something. If someone could explain how I need to fix this, I would greatly appreciate it.

Related

Correct way of binding xamarin forms picker value

My issue is that I have 2 picker controls. 1 of these picker controls is bound to list a and one of these picker controls is bound to list b. Both of these controls display the data I need them to with the correct display bindings working fine.
My problem is when I bind the selectedItem property, it works for list a but doesn't for list b. I've checked that the code is literally a like for like copy of each other.
I have been using the syncfusion Combobox but switched to the picker as I thought there was an issue here but there isn't. Whatever is happening is totally down to whatever I'm doing.
The usage scenario is that I bring down a list of payment types from my API and populate a picker based on this. This works.
The datasource for my main view contains an ID. When I am modifying a record, I run a method called update to find the selectedItem. I'm not happy with this approach and would be interested to see what other people use.
The update method gets the datasources for the pickers and finds what I would expect to be the selected item. This works fine also but doesn't bind.
[Serializable]
public class PaymentInformation :BaseModel
{
public int? ID { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int? PaymentTypeId { get; set; }
public string PaymentTo { get; set; }
public string Location { get; set; }
public string Notes { get; set; }
public PersonInformation PersonBudget { get; set; }
public decimal AmountPaid { get; set; }
public decimal AmountReceived { get; set; }
public double TotalHours { get; set; }
public void Update(ObservableCollection<ResourceInformation> resources , ObservableCollection<PaymentTypeInformation> paymentTypes)
{
if(PaymentTypeId != null) this.PaymentTypeInformation1 = paymentTypes?.FirstOrDefault((paymentType) => paymentType.ID == PaymentTypeId.Value);
this.Resource = resources?.FirstOrDefault((resource) => resource.ResourceId == PersonBudget?.ID);
}
private PaymentTypeInformation _paymentTypeInformation;
private PaymentTypeInformation PaymentTypeInformation1 { get { return _paymentTypeInformation; } set { _paymentTypeInformation = value; OnPropertyChanged(nameof(PaymentTypeInformation1)); } }
private ResourceInformation _resource;
public ResourceInformation Resource { get { return _resource; } set { _resource = value; OnPropertyChanged(nameof(Resource)); } }
}
The underlying xaml is:
<Label Grid.Row="8" Grid.Column="0" Text="Payment Type:" />
<Picker BackgroundColor="White" Grid.Row="8" Grid.Column="1" ItemsSource="{Binding PaymentTypesDataSource}" ItemDisplayBinding="{Binding Path=DisplayText}" IsEnabled="{Binding IsProcessing, Converter={StaticResource reverseBoolConvertor}}" SelectedItem="{Binding DataSource.PaymentTypeInformation1, Mode=TwoWay}" />
The expected result is that the drop down initializes with the selectedItem which it doesn't (in one usage scenario - the other one works fine).
Couldn't see the wood for the trees.
private PaymentTypeInformation PaymentTypeInformation1
{
get
{
return _paymentTypeInformation;
}
set
{
_paymentTypeInformation = value;
OnPropertyChanged(nameof(PaymentTypeInformation1));
}
}
Can't bind to a private property - changed to public and immediately work. Stuck on this for a day as bonkers as that is to believe.

No filled in data in RadDataGrid

I have an UWP app and I am using a RadDataGrid to show some data. When I do a API call to my API to set data in the grid, the data grid shows empty rows.
My XAML
<tg:RadDataGrid Grid.Row="2" UserGroupMode="Disabled" ColumnDataOperationsMode="Flyout" x:Name="infoGrid" ItemsSource="{x:Bind Path=ViewModel.history}" AutoGenerateColumns="False" FontSize="24" VerticalContentAlignment="Top" Margin="0,0,0,50" VerticalAlignment="Top" MaxHeight="500">
<tg:RadDataGrid.Columns>
<tg:DataGridTextColumn PropertyName="UpdatedBy" Header="Verplaatst door"/>
<tg:DataGridDateColumn PropertyName="UpdateDate" Header="Bijgewerkt op" CellContentFormat=" {0:dd/MM/yyyy}" />
<tg:DataGridTextColumn PropertyName="Location" Header="Verplaatst naar"/>
</tg:RadDataGrid.Columns>
</tg:RadDataGrid>
My C# code to set data
public async void APICALL()
{
var result = await LotService.GetLotInfo(ViewModel.scanField);
ViewModel.lot = result;
ViewModel.history = result.LotHistory;
infoGrid.ItemsSource = null;
infoGrid.ItemsSource = ViewModel.history;
IsBusy = false;
}
}
ViewModel.history is a List of LotHistoryInfo Class
EDIT: Added getters and setters as mm8 suggested
public class LotHistoryInfo
{
public LotInfo lot { get; set; }
public string scanField { get; set; }
public List<LotHistoryInfo> history { get; set; }
public LotHistoryInfo(LotStoreHistory his)
{
Location = new Location(his.LshStoreid.ToString(), his.LshStorex, his.LshStorey, his.LshStorez);
UpdatedBy = his.UpdatedBy;
UpdateDate = his.Updated;
}
public LotHistoryInfo()
{
}
}
My data grid acknowledges there should be 3 items in it, but it doesnt fill them in.
Sooo, what am I missing?
You can only bind to public properties:
public class LotHistoryInfo
{
public Location Location { get; set; }
public string UpdatedBy { get; set; }
public DateTimeOffset UpdateDate { get; set; }
public LotHistoryInfo(LotStoreHistory his)
{
Location = new Location(his.LshStoreid.ToString(), his.LshStorex, his.LshStorey, his.LshStorez);
UpdatedBy = his.UpdatedBy;
UpdateDate = his.Updated;
}
public LotHistoryInfo()
{
}
}
You have implemented Location, UpdatedBy and UpdateDate as fields:
public Location Location;
public string UpdatedBy;
public DateTimeOffset UpdateDate;

Highlighting and getting reference to a row in datagrid in WPF

I'm new to WPF. I have a UI screen on which I am binding and showing a Datagrid in the load_event.
When users clicks on a row, I need to highlight it with blue color and then get a handler to the selected row.
How can I achieve this? Please advise.
The best and easy way is to bind your data (datasource) to your DataGrid (UI).
So, you need bind the SelectedItem property to your C# code to receive all data selected.
Sample:
<Grid>
<DataGrid ItemsSource="{Binding ListOfEmployees}" SelectedItem="{Binding SelectedEmployee, Mode=TwoWay}" />
</Grid>
In the following C# code, the property SelectedEmployee will contains the Employee object selected by user (WPF set automatically the row in blue).
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new PageContext();
}
}
public class PageContext
{
private Employee _selectedEmployee;
public PageContext()
{
this.ListOfEmployees = new ObservableCollection<Employee>();
this.ListOfEmployees.Add(new Employee() { Name = "Voituron", Phone = "123.456.789" });
this.ListOfEmployees.Add(new Employee() { Name = "Dubois", Phone = "147.258.369" });
}
public ObservableCollection<Employee> ListOfEmployees { get; set; }
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
_selectedEmployee = value;
Debugger.Break();
}
}
}
public class Employee
{
public string Name { get; set; }
public string Phone { get; set; }
}
Find more on my blog (note: this article is in french)

WPF DataGrid binding doesn't update

Here's my DataGrid:
<DataGrid x:Name="MoonMining"
ItemsSource="{Binding MarketData.MoonMinerals, ElementName=window}">
<DataGrid.DataContext>
<local:MoonMineral/>
</DataGrid.DataContext>
<DataGrid.Columns>
.. Yes i have columns and they are irrelevant to my question .
</DataGrid.Columns>
</DataGrid>
MarketData is a class which contains most of my programs logic. MoonMinerals is defined in that class:
public class MarketData
{
private ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
set { _moonMinerals = value; }
}
}
And here's my MoonMineral class:
[NotifyPropertyChanged]
public class MoonMineral
{
public MoonMineral()
: this("Def", "Def")
{
}
public MoonMineral(string name, string rarity)
{
Name = name;
Rarity = rarity;
}
public string Name { get; set; }
public double Price { get; set; }
public double Volume { get; set; }
public string Rarity { get; set; }
public double TransportVolume { get; set; }
public double TransportCosts { get; set; }
public double GrossProfit { get; set; }
public double NetProfit { get; set; }
}
As you can see, I'm using PostSharp to clear up my code, but when I manually implement INotifyPropertyChanged I have the same problem.
Now the problem is that my DataGrid doesn't update by itself, I have to manually call this in a method which modifies MoonMinerals:
var bindingExpression = MoonMining.GetBindingExpression(ItemsControl.ItemsSourceProperty);
if (bindingExpression != null)
bindingExpression.UpdateTarget();
I know this isn't big of a deal, but I wanted to finally manage to bind data to ui entirely using xaml. All my previous attempts involved setting DataGrids ItemsSource property every time I updated the data.
To sum up comments you're implementing INotifyPropertyChanged interface for MoonMineral class and use ObservableCollection which will handle changes to the collection but there seems to be nothing in place to handle changes to MoonMinerals property
private ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
set { _moonMinerals = value; }
}
You can either implement INotifyPropertyChanged interface in the class that exposes MoonMinerals property or change it to read-only and use only one instance of _moonMinerals and simply clear it and add/remove items
private readonly ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
}
Also, as a side note, you don't need
<DataGrid.DataContext>
<local:MoonMineral/>
</DataGrid.DataContext>
as this will set DataContext of the DataGrid to new instance of MoonMineral. It works in your case as you change binding context of ItemsSource using ElementName so DataContext is not used in your case.

Theory on loosely coupled viewmodel

Can anyone please explain the theory on creating a loosely coupled viewmodel.
I have attached some example code below to try and explain what I mean.
I have 2 example classes just for this example
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Web.UI.Models
{
public class EmployerAddress
{
public string address { get; set; }
public string city { get; set; }
public string region { get; set; }
public string country { get; set; }
public string postZipCode { get; set; }
}
public class EmployerDetails
{
public string position { get; set; }
public string gender { get; set; }
public string dob { get; set; }
}
public class DisplayEmployerAddress : IDisplayEmployerAddress
{
public IEnumerable<EmployerAddress> employerAddr()
{
List<EmployerAddress> Data = new List<EmployerAddress>();
Data.Add(new EmployerAddress
{
address = "address1",
city = "city1",
region = "region1",
country = "country1",
postZipCode = "post zip1"
});
return Data;
}
}
public class DisplayEmployerDetails : IDisplayEmployerDetails
{
public IEnumerable<EmployerDetails> employerDetails()
{
List<EmployerDetails> Data = new List<EmployerDetails>();
Data.Add(new EmployerDetails
{
position = "trainee",
gender = "male",
dob = "22-08-1964"
});
Data.Add(new EmployerDetails
{
position = "trainee2",
gender = "male2",
dob = "22-08-1970"
});
return Data;
}
}
}
The code above has the interfaces:
IEnumerable<EmployerAddress> employerAddr();
IEnumerable<EmployerDetails> employerDetails();
I then use Ninject to bind the above.
kernel.Bind<IDisplayEmployerAddress>().To<DisplayEmployerAddress>().InSingletonScope();
kernel.Bind<IDisplayEmployerDetails>().To<DisplayEmployerDetails>().InSingletonScope();
At this point everything is OK, I could just change DisplayEmployerAddress etc and so long as all the methods etc match up the code will still work.
I then create a viewmodel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Web.UI.Models
{
public class EmployerDetailsViewModel
{
public string age { get; set; }
public IEnumerable<EmployerAddress> EmployerAddress { get; set; }
public IEnumerable<EmployerDetails> EmployerDetails { get; set; }
}
}
But now this would cause a problem as EmployerAddress is now tightly coupled, so if I change the code, it will now have to be updated in 2 places.
In my controller I have
public class HomeController : Controller
{
private readonly IDisplayEmployerAddress _address;
private readonly IDisplayEmployerDetails _details;
public HomeController(IDisplayEmployerAddress address,
IDisplayEmployerDetails details)
{
_address = address;
_details = details;
}
public ActionResult Index()
{
ViewBag.Title = "Title";
var Address = _address.employerAddr();
var Details = _details.employerDetails().AsEnumerable();
var Age = _details.employerDetails().FirstOrDefault().dob;
var employerModel = new EmployerDetailsViewModel
{
EmployerAddress = Address,
EmployerDetails = Details,
age = age.calAge(Age)
};
return View(employerModel);
}
I keep the controller lightweight as all the books I read say keep as little code as possible in the controller, so to calculate age I use a static class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Web.UI.Models
{
public static class age
{
public static string calAge(string dob)
{
//Would cal age here
return "48";
}
}
}
So my question is 3 parts.
Is this example the correct way.
As my viewmodel is now tightly coupled, how do I make it loosley
coupled.
If I did not want to use a foreach loop how can I get each item out
of say EmployerDetails
This employer holds the position of #Model.EmployerDetails.position the gender is #Model.EmployerDetails.gender
<ul>
#foreach (var d in Model.EmployerAddress)
{
<li>#d.address</li>
<li>#d.city</li>
<li>#d.country</li>
<li>#d.region</li>
<li>#d.postZipCode</li>
}
</ul>
<ul>
#foreach (var dd in Model.EmployerDetails)
{
<li>#dd.position</li>
<li>#dd.gender</li>
<li>#dd.dob</li>
}
</ul>
So far This employer holds the position of #Model.EmployerDetails.position the gender is #Model.EmployerDetails.gender
Worked out question 3, changed code to #Model.EmployerDetails.FirstOrDefault().position
Hope the above example makes sense on what I'm trying to learn
Thanks
George
public class Employer
{
public int Id { get; set; }
}
public class EmployerAddress
{
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string Country { get; set; }
public string PostZipCode { get; set; }
public int EmployerId { get; set; }
}
public class EmployerDetails
{
public string Position { get; set; }
public string Gender { get; set; }
public string Dob { get; set; }
public int EmployerId { get; set; }
}
public class MyRepository : IMyRepository
{
public IEnumerable<Employer> GetEmployers()
{
return new List<Employer>
{
new Employer {Id = 1},
new Employer {Id = 2}
};
}
public IEnumerable<EmployerAddress> GetEmployeeAddresses()
{
return new List<EmployerAddress>
{
new EmployerAddress
{
EmployerId = 1,
Address = "address1",
City = "city1",
Region = "region1",
Country = "country1",
PostZipCode = "post zip1"
},
new EmployerAddress
{
EmployerId = 2,
Address = "address2",
City = "city2",
Region = "region2",
Country = "country2",
PostZipCode = "post zip2"
}
};
}
public IEnumerable<EmployerDetails> GetEmployeeDetails()
{
return new List<EmployerDetails>
{
new EmployerDetails
{
EmployerId = 1,
Position = "trainee",
Gender = "male",
Dob = "22-08-1964"
},
new EmployerDetails
{
EmployerId = 2,
Position = "trainee2",
Gender = "male2",
Dob = "22-08-1970"
}
};
}
}
public class EmployerChangedEvent
{
public EmployerChangedEvent(Employer selectedEmployer)
{
Employer = selectedEmployer;
}
public Employer Employer { get; set; }
}
public class EmployerViewModel
{
private readonly IEventAggregator _events;
private Employer _selectedEmployer;
// Configure Ninject properly to get those types
public EmployerViewModel(IEventAggregator events, IMyRepository myRepository)
{
_events = events;
Employers = myRepository.GetEmployers().ToList();
EmployerAddressViewModel = new EmployerAddressViewModel(_events, myRepository);
EmployerDetailsViewModel = new EmployerDetailsViewModel(_events, myRepository);
}
public List<Employer> Employers { get; set; }
public EmployerAddressViewModel EmployerAddressViewModel { get; set; }
public EmployerDetailsViewModel EmployerDetailsViewModel { get; set; }
public Employer SelectedEmployer
{
get { return _selectedEmployer; }
set
{
_selectedEmployer = value;
// this notifies the dependent view models in a loosley coupled way
_events.Publish(new EmployerChangedEvent(_selectedEmployer));
}
}
}
public class EmployerAddressViewModel :
IHandle<EmployerChangedEvent> // specifies which events shall be caught
{
private readonly IMyRepository _myRepository;
private Employer _selectedEmployer;
public EmployerAddressViewModel(IEventAggregator events, IMyRepository myRepository)
{
_myRepository = myRepository;
// this subscribes this view model to the passed event aggregator
// from your main view model (EmployerViewModel)
events.Subscribe(this);
}
public EmployerAddress EmployerAddress { get; set; }
public void Handle(EmployerChangedEvent message)
{
_selectedEmployer = message.Employer;
EmployerAddress = _myRepository.GetEmployeeAddresses()
.FirstOrDefault(e => e.EmployerId == _selectedEmployer.Id);
}
}
public class EmployerDetailsViewModel :
IHandle<EmployerChangedEvent> // specifies which events shall be caught
{
private readonly IMyRepository _myRepository;
private Employer _selectedEmployer;
public EmployerDetailsViewModel(IEventAggregator events, IMyRepository myRepository)
{
_myRepository = myRepository;
// this subscribes this view model to the passed event aggregator
// from your main view model (EmployerViewModel)
events.Subscribe(this);
}
public EmployerDetails EmployerDetails { get; set; }
public void Handle(EmployerChangedEvent message)
{
_selectedEmployer = message.Employer;
EmployerDetails = _myRepository.GetEmployeeDetails()
.FirstOrDefault(e => e.EmployerId == _selectedEmployer.Id);
}
}
internal class Program
{
private static void Main(string[] args)
{
// do this with Ninject
var employerViewModel = new EmployerViewModel(new EventAggregator(), new MyRepository());
// this selection should actually be user input
employerViewModel.SelectedEmployer = employerViewModel.Employers.First();
// select another one
employerViewModel.SelectedEmployer = employerViewModel.Employers.Last();
}
}
As I am not familiar with ASP.NET, my answer doesn't imply any of UI notifications.
I suggest Caliburn.Micro's event aggregator class here, because it solves your coupling problem nicely. This library is worth a look anyway for learning the MVVM pattern.
The IEventAggregator allows you to subscribe with instance of a class to an instance of the aggregator. If multiple view models share an instance of the event aggregator you can easily send events from one to another in a loosley coupled way.
I refactored your original code, to make it more fitting for the actual MVVM pattern (your first question, let's say this implementaion is more proper). I've added an Employer class, which is basically the main object. It only has an id. The EmployerDetails and EmployerAddress also have a new property EmployerId which is a reference to the Employer they belong to.
I have put all the stuff to query data in the MyRepository class!
For each of those three classes exist three seperate view models and they're only coupled through the event aggregator they share (answers your 2nd question). The EmployerViewModel manages the main data objects of type Employer and publishes an event as soon as the selected Employer changes. The new value is passed into the EmployerChangedEvent which then is caught by the view models which handle this certain kind of event (IHandle<EmployerChangedEvent). In their Handle() implementation the passed employer is put into a private field of the receiving view model.
This is just a console application which simulates user input, though try with putting break points on both of the handle methods, as the SelectedEmployer changes.
I think some kind of stuff I do in my Main() method should be done in your controllers. I have to mention that this code is just for showing the benefits of the MVVM pattern, it might be over abstracted in some cases. Also things like querying the repository efficiently are not covered at all!
I think my answer also solves your 3rd question, as I see no foreach is anymore needed.
Remember to reference Caliburn.Micro if you'd like to run this code. Just get it through NuGet or download it here.

Categories