I'm just starting with WPF MVVM & I'm trying to populate a datagrid view by binding to an ObservableCollection in my view model.
I have used a simple LINQ query to select all from my table and populate my observable collection but my UI won't display the grid results, presumably because the XAML has to bind to a public property?
private void getTableResults()
{
var query = from c in LDB.myTable
select c;
var results = query;
ObservableCollection<myTable> MyCollection = new ObservableCollection<myTable>(results);
OnPropertyChanged("MyCollection");
}
Here's my XAML binding:
<DataGrid ItemsSource="{Binding MyCollection}"
My DataContext is set and I've checked it binds against another hard-coded property. Where am I going wrong? I've put a breakpoint over the collection so know that it is being populated by the results. How can I expose a public ObservableCollection property which is populated from my LINQ results?
Thanks
Just to add, my data model is MyTable which is an Entity Framework 4.0 edmx.
Assuming the data context of your view is your viewmodel, then yes - your xaml binding should be to a public property of your viewmodel.
So declare your 'MyCollection' property as a property of the viewmodel (not a local property of your getTbaleResults method).
private ObservableCollection<myTable> _myCollection;
/// <summary>
/// Gets or sets the myTable collection.
/// </summary>
public ObservableCollection<myTable> MyCollection
{
get { return _myCollection; }
set
{
if (value == _myCollection) return;
_myCollection= value;
RaisePropertyChanged(() => MyCollection);
}
}
(Note this is using Prism so the lambda on RaisePropertyChanged, but other frameworks have similar implementations).
Related
I'm having an enormous amount of trouble with ComboBoxes in a data grid.
And really would like some help, i think i've gotten confused by the amount of research and things i've tried. This really should be simple so i must be missing something.
SIMPLIFIED PROBLEM
I use a CollectionViewSource in xaml, the C# sets the source of that CollectionViewSource to an ObservableCollection in a class that is the Page's datacontext.
Adding items to the collection does not update the DataGridComboBox column containing that displays the view source.
See below the line for more detail
OVERVIEW
I have a WPF Page with a datagrid on it.
The page has its data context set to a view model.
The viewModel contains two observable collections. One for Equips and One for Locations.
Each Equip has a Location.
These are populated from a code first EF database but i believe that this problem is above that level.
The datagrid is one row per Equip. The Location column needs to be a selectable combobox that allows the user to change Location.
The only way i could get the location combobox to populate at all is by binding it to a separate collection view source.
PROBLEM
It seems that if the Page loaded event occurs prior to the ViewModel populating the ObservableCollection then the locationVwSrc will be empty and the property changed event doesn't get this to change.
IMPLEMENTATION SHORT VERSION
Page has a collection viewSource defined in the xaml.
Loaded="Page_Loaded"
Title="EquipRegPage">
<Page.Resources>
<CollectionViewSource x:Key="locationsVwSrc"/>
</Page.Resources>
The datagrid is defined with xaml.
<DataGrid x:Name="equipsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" Margin="10,10,-118,59"
ItemsSource="{Binding Equips}" EnableRowVirtualization="True" AutoGenerateColumns="False">
The combobox column defined in xaml
<DataGridComboBoxColumn x:Name="locationColumn" Width="Auto" MaxWidth="200" Header="Location"
ItemsSource="{Binding Source={StaticResource locationsVwSrc}, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
SelectedValueBinding="{Binding Location}"
The page context set to the view model
public partial class EquipRegPage : Page
{
EquipRegVm viewModel = new EquipRegVm();
public EquipRegPage()
{
InitializeComponent();
this.DataContext = viewModel;
}
Loaded event setting the context
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Locations View Source
System.Windows.Data.CollectionViewSource locationViewSource =
((System.Windows.Data.CollectionViewSource)(this.FindResource("locationsVwSrc")));
locationViewSource.Source = viewModel.Locations;
// Above does not work if the viewmodel populates these after this call, only works if its populated prior.
//TODO inotifypropertychanged not correct? This occurs before the viewmodels loads, and doesn't display.
// Therefore notify property changes aren't working.
// Using this as cheat instead instead works, i beleive due to this only setting the source when its full
//viewModel.Db.Locations.Load();
//locationViewSource.Source = viewModel.Db.Locations.Local;
//locationViewSource.View.Refresh();
}
The ViewModel class and how it loads
public class EquipRegVm : DbWrap, INotifyPropertyChanged
{
/// <summary>
/// Event triggered by changes to properties. This notifys the WPF UI above which then
/// makes a binding to the UI.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notify Property Changed Event Trigger
/// </summary>
/// <param name="propertyName">Name of the property changed. Must match the binding path of the XAML.</param>
void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<Equip> Equips { get; set; }
public ObservableCollection<Location> Locations { get; set; }
public EquipRegVm() : base()
{
Load();
}
/// <summary>
/// Load the data from the Model.
/// </summary>
public async void Load() //TODO async an issue?
{
// EQUIPMENT
ObservableCollection<Equip> eqList = new ObservableCollection<Equip>();
var eqs = await (from eq in Db.Equips
orderby eq.Tag
select eq).ToListAsync();
foreach(var eq in eqs)
{
eqList.Add(eq);
}
Equips = eqList;
RaisePropertyChanged("Equips");
// LOCATIONS
ObservableCollection<Location> locList = new ObservableCollection<Location>();
var locs = await (from l in Db.Locations
orderby l.Name
select l).ToListAsync();
foreach (var l in locs)
{
locList.Add(l);
}
Locations = locList;
RaisePropertyChanged("Locations");
}
}
It seems you haven't been able to break the problem down into small enough problems. The question seems to be a mix of ComboBoxes in Datagrid, Asynchronously setting CollectionViewSource source, loading data from a database.
I suggest that it would be beneficial to either consider
recreating the problem (or soultion) with the minimum moving parts i.e. a XAML file and a ViewModel with pre canned data.
or decoupling your existing code. It appears that Page knows about your ViewModel explicitly (EquipRegVm viewModel = new EquipRegVm();), and you ViewModel knows explicitly about Databases and how to load itself. Oh snap, now our views are coupled to your database? Isn't that the point of patterns like MVVM so that we are not coupled?
Next I look at some the code and see some more (of what I would call) anti-patterns.
Settable collection properties
code behind for the page (all could live in the XAML)
But I think basically if you just changed your code in 3 places you should be fine.
Change 1
/*foreach(var eq in eqs)
{
eqList.Add(eq);
}
Equips = eqList;
RaisePropertyChanged("Equips");*/
foreach(var eq in eqs)
{
Equips.Add(eq);
}
Change 2
/*foreach (var l in locs)
{
locList.Add(l);
}
Locations = locList;
RaisePropertyChanged("Locations");*/
foreach (var l in locs)
{
Locations.Add(l);
}
Change 3
Either just remove the usage of the CollectionViewSource (what does it offer you?) or use binding to set the source. As you are currently manually setting the Source (i.e. locationViewSource.Source = viewModel.Locations;) you have opted out of getting that value updated when the PropertyChanged event has been raised.
So if you just delete the CollectionViewSource, then you just have to bind to the Locations property. If you decide to keep the CollectionViewSource then I would suggest deleting the page codebhind and just changing the XAML to
<CollectionViewSource x:Key="locationsVwSrc" Source="{Binding Locations}" />
Set a Binding like below :
System.Windows.Data.CollectionViewSource locationViewSource =
((System.Windows.Data.CollectionViewSource)(this.FindResource("locationsVwSrc")));
// locationViewSource.Source = viewModel.Locations;
Binding b = new Binding("Locations");
b.Source = viewModel;
b.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(locationViewSource, CollectionViewSource.SourceProperty, b);
This is all you need.
I'm building an MVVM Light WPF app in Visual Studio 2015 with Entity Framework 6 (EF) providing the data. I have a ComboBox that displays the reasons why someone needs to take a drug test and it looks like this:
<ComboBox ItemsSource="{Binding ReasonsForTest}"
SelectedItem="{Binding Path=ReasonsForTestVm,
UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Description" />
The ReasonsForTest is of type ReasonForTestViewModel class:
public class ReasonForTestViewModel: ViewModelBase
{
private int _ReasonForTestId;
private string _ReasonForTestAbbr;
private string _description;
public int ReasonForTestId
{
get { return _ReasonForTestId; }
set
{
if (value == _ReasonForTestId) return;
_ReasonForTestId = value;
RaisePropertyChanged();
}
}
public string ReasonForTestAbbr
{
get { return _ReasonForTestAbbr; }
set
{
if (value == _ReasonForTestAbbr) return;
_ReasonForTestAbbr = value;
RaisePropertyChanged();
}
}
public string Description
{
get { return _description; }
set
{
if (value == _description) return;
_description = value;
RaisePropertyChanged();
}
}
}
I have a data service class that contains the following code to fetch the data for the valid values of the ComboBox:
public async Task<ObservableCollection<ReasonForTestViewModel>> GetReasonsForTest()
{
using (var context = new MyEntities())
{
var query = new ObservableCollection<ReasonForTestViewModel>
(from rt in context.ReasonForTests
orderby rt.description
select new ReasonForTestViewModel
{
ReasonForTestId = rt.ReasonForTestID,
ReasonForTestAbbr = rt.ReasonForTestAbbr,
Description = rt.description,
});
return await Task.Run(() => query);
}
}
The view model populates the ComboBox using this:
var dataService = new TestDataService();
ReasonsForTest = await dataService.GetReasonsForTest();
The ComboBox has the correct data; however, it's not selecting the correct value when the app starts -- it's showing blank on load. The SelectedItem (ReasonsForTestVm) is also of that class type ReasonForTestViewModel and gets populated from the database with the one item for this person. I've stepped through the code to ensure ReasonsForTestVm has the correct data, and it does.
Here's the property for ReasonsForTestVm:
public ReasonForTestViewModel ReasonForTestVm
{
get
{
return _reasonForTestVm;
}
set
{
if (Equals(value, _reasonForTestVm)) return;
_reasonForTestVm = value;
RaisePropertyChanged();
}
}
What am I doing wrong here? I'm about to lose my mind!
Update: Sorry for the confusing name in the property above. Fixed.
Any WPF items control that extends Selector (such as ComboBox and ListBox) has two properties that are often used in conjunction: ItemsSource and SelectedItem.
When you bind a collection to ItemsSource, a representation of those items are shown in the UI. Each one of the representations is bound to an instance found within the collection bound to ItemsSource. If, for an example, you're using a DataTemplate to create that representation, you'll find within each that the DataContext will be one of those instances from the collection.
When you select one of these representations, the SelectedItemproperty now holds the instance from the collection that was bound to that representation.
This works perfectly through user interaction with the UI. However, there's one important caveat when interacting with these controls programmatically.
It's a very common pattern to bind these properties to similar properties in your view model.
public class MuhViewModel
{
public MuhItems[] MuhItems {get;} = new[]{ new Item(1), new Item(2) };
// I don't want to show INPC impls in my sample code, kthx
[SuperSlickImplementINotifyPropertyChangedAttribute]
public MuhSelectedItem {get;set;}
}
bound to
<ComboBox ItemsSource="{Binding MuhItems}"
SelectedItem="{Binding MuhSelectedItem}" />
If you try to manually update the selected item this way...
muhViewModel.MuhSelectedItem = new Item(2);
The UI will not change. The Selector sees that ItemsSource has changed, yes, but it doesn't find that instance in the ItemsSource collection. It doesn't know that one instance of Item with a value of 2 is equivalent to any other Item with the same value. So it does nothing. (That's a bit simplistic for what really happens. You can bust out JustDecompile and see for yourself. It gets real convoluted down in there.)
What you should be doing in this situation is updating SelectedItem with an instance found within the collection bound to ItemsSource. In our example,
var derp = muhViewModel.MuhItems.FirstOrDefault(x => x.MuhValue == 2);
muhViewModel.MuhSelectedItem = derp;
Side note, when tracking instances within a debug session, it helps to use Visual Studio's Make Object ID feature.
This has been bugging me for a while now, what I would like to be able to do is update an existing record on the database by making a change to the ObservableCollection or the record class SquirrelDataGridActiveView.
This is my DataAccessService:
public interface IDataAccessService
{
ObservableCollection<SquirrelDataGridActiveView> GetEmployees();
void UpdateRecord(SquirrelDataGridActiveView Emp);
int CreateEmployee(SquirrelDataGridActiveView Emp);
}
/// <summary>
/// Class implementing IDataAccessService interface and implementing
/// its methods by making call to the Entities using CompanyEntities object
/// </summary>
public class DataAccessService : IDataAccessService
{
Drive_SHEntities context;
public DataAccessService()
{
context = new Drive_SHEntities();
}
public ObservableCollection<SquirrelDataGridActiveView> GetEmployees()
{
ObservableCollection<SquirrelDataGridActiveView> Employees = new ObservableCollection<SquirrelDataGridActiveView>();
foreach (var item in context.SquirrelDataGridActiveViews)
{
Employees.Add(item);
}
return Employees;
}
public int CreateEmployee(SquirrelDataGridActiveView Emp)
{
context.SquirrelDataGridActiveViews.Add(Emp);
context.SaveChanges();
return Emp.ID;
}
public void UpdateRecord(SquirrelDataGridActiveView temp)
{
}
}
As you can see there is already a GetEmployees() method and a CreateEmployee() method however I'm finiding it very difficult to update the database with the new values.
Any suggestions would be much appreciated.
Looks like the problem maybe be in your EF layer. Do you have any unit tests to check that the creation of a new Employee record works properly? Can you break into the CreateEmployee and inspect the Emp.ID field? See if it's set to something. If it is, the creation of the new record works properly. One observation, if I may: your data access service does not need to return an ObservableCollection. I assume you already have an observable collection in your view-model class. If you want to keep alive the binding between the data grid in the view and the observable collection property in the view-model class, you should not reassign the OC property of the view-model class. The pattern you should follow is this:
return an IEnumeration from your data access service's GetEmployees() method;
Optional, but recommended is to make GetEmployees awaitable;
in your view-model class, clear the content of the OC property and then add, one by one, all the items in the IEnumeration collection returned by your GetEmployees() method. This way your binding will continue to work and any updates in the database will be reflected in the view's data grid.
You can keep the OC return from your GetEmployees, but in your view- model you still need to transfer all the items, one by one, into the property that is, most likely, bound to the data grid in the view. If you just assign the GetEmployees() returned OC list to the the view-model's property, your binding will be gone.
Perhaps your data grid does not refresh properly and that's why you conclude that the database is not updated when in fact it is.
HTH,
Eddie
I've started an MVVM project and now I'm stucking with correct DataBinding.
My project has:
A UserControl whit a ViewModel as DataContext like:
public partial class TestUserControl: UserControl
{
public TestUserControl()
{
this.DataContext = new TestUserControlViewModel();
}
}
ViewModel code is (BaseViewModel class contains PropertyChangedEventHandler):
public class TestUserControlViewModel : BaseViewModel
{
public KrankenkasseControlViewModel()
{}
public IEnumerable<DataItem> GetAllData
{
get
{
IGetTheData src= new DataRepository();
return src.GetData();
}
}
}
IGetTheData is the interface to DataContext:
public interface IGetTheData
{
IEnumerable<DataItem> GetData();
}
}
and finally the DataRepository code:
public class DataRepository : IGetTheData
{
private TestProjectDataContext dax = new TestProjectDataContext();
public IEnumerable<DataItem> GetData()
{
return (from d in this.dax.TestData
select new DataItem
{
ID = d.ID,
SomeOtherData = d.SomeOtherData
});
}
}
My UserControl has a few TextBoxes, but what's the best way to bind correctly?
Thanks for your help, regards.
EDIT: Binding the data against multiple textboxes
After reading your comment, I will elaborate my example for textboxes.
First important thing is that the ViewModel will model the things in the View, so that the View gets all information it needs in the structure it needs. That means, if you have multiple textboses in the View, you will need multiple string Properties in your ViewModel, one for each textbox.
In your XAML you could have something like
<TextBox Text="{Binding ID, Mode=TwoWay}" />
<TextBox Text="{Binding SomeOtherData, Mode=TwoWay}" />
and in your ViewModel
public class TestUserControlViewModel : BaseViewModel {
private string id;
private string someOtherData;
public TestUserControlViewModel() {
DataItem firstItem = new DataRepository().GetData().First();
this.ID = firstItem.ID;
this.SomeOtherData = firstItem.SomeOtherData;
}
public string ID {
get {
return this.id;
}
set {
if (this.id == value) return;
this.id = value;
this.OnPropertyChangedEvent("ID");
}
}
public string SomeOtherData {
get {
return this.someOtherData;
}
set {
if (this.someOtherData == value) return;
this.someOtherData = value;
this.OnPropertyChangedEvent("SomeOtherData");
}
}
}
Here I assume that in your BaseViewModel there is an OnPropertyChangedEvent method to fire the corresponding event. This tells the View that the property has changed and it must update itself.
Note the Mode=TwoWay in the XAML. This means, that it doesn't matter on which side the value changes, the other side will reflect the change immediately. So if the user changes a value in a TwoWay bound TextBox, then the corresponding ViewModel property will automatically change! And also vice versa: if you change the ViewModel property programmatically, the View will refresh.
If you want to show multiple textboxes for more than one data item, then you must introduce more Properties in the ViewModel and bind them accordingly. Maybe a ListBox with a flexible number of TextBoxes inside is a solution then, like #Haspemulator already answered.
Binding the data against a collection control
In the TestUserControl I guess you have a control (like a ListView) to show the list of loaded things. So bind that control against the list in the ViewModel with
<ListView ... ItemsSource="{Binding GetAllData}" ... />
First you must understand that Binding means not "read the data and then forget the ViewModel". Instead you bind the View to the ViewModel (and its Properties) as long as the View lasts. From this point of view, AllData is a much better name than GetAllData (thanks #Malcolm O'Hare).
Now in your code, every time the View reads the AllData property, a new DataRepository is created. Because of the Binding, that is not what you want, instead you want to have one instance of DataRepository for the whole lifetime of the View, which is used to read the initial data and can later be used to update the View, if the underlying database changes (maybe with an event).
To enable such a behavior you should change the type of the AllData property to an ObservableCollection, so that the View can automatically update the list if changes occur.
public class TestUserControlViewModel : BaseViewModel
private ObservableCollection<DataItem> allData;
public TestUserControlViewModel() {
IGetTheData src = new DataRepository();
this.allData = new ObservableCollection<DataItem>(src.GetData());
}
public ObservableCollection<DataItem> AllData {
get {
return this.allData;
}
}
public void AddDataItem(DataItem item) {
this.allData.Add(item);
}
}
Now if you call AddDataItem later, the ListView will update itself automatically.
Your Property Name is bad. You should call it AllData, not GetAllData.
Since you are returning a collection, you probably should be using some sort of list control (ListBox, ListView).
In that case you'd be doing
<ListBox ItemsSource="{Binding GetAllData}" />
Guten Abend. :) As it already mentioned, since you're returning the collection, it's better to use a ListBox. The comment about having ObservableCollection as a cache is also absolutely valid. I would add that if you need to have your data editable, you should use TextBox inside the ItemTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text={Binding SomeOtherData,Mode=TwoWay} />
</DataTemplate>
</ListBox.ItemTemplate>
In this case if user edits the text in the box, data will be updated in your data object, so that it could be saved in the database later.
I'm trying to figure out how to bind a datagrid's itemsource to a List. In particular, I am using LINQ to SQL that pulls the data off a table, and converts it to a List:
var tempTable = (from p in dc.LiveData
select new Custom_GridResult
{ x = p.x,
y = p.y,
z = p.z
}).ToList();
dataGrid.ItemsSource = tempTable.ToList();
public class CustomETO_GridResult
{
public CustomETO_GridResult()
{ }
double x { get; set; }
public double y{ get; set; }
public double z{ get; set; }
}
My plan is to re-pull the data every few minutes (as it is constantly updating) and I'd like my DataGrid to automatically reflect the changes. Is is easier for me to just constantly re-set the ItemsSource, or is there another way?
Thanks a lot
EDIT: I should have added that the database has a constant number of rows (they are predefined). What is constantly updating are the values in the database. For example, pretend that its a database of Airplanes, and the column values are constantly updated with GPS coordinates of where the plane is flying.
For the DataGrid to receive the updates from your source collection it will need to implement INotifyCollectionChanged. Luckily, ObservableCollection already does that. Have your database query populate an ObservableCollection and bind your DataGrid to the ObservableCollection.
Update:
An even better answer is to use the MVVM pattern. You would create a Model class that interfaces with the database and a ViewModel class that prepares the data for presentation but putting it into an ObservableCollection (for example). Then your View would bind to the ViewModel where you would set the DataGrid's ItemsSource to the ObservableCollection. See this post for getting started with MVVM.
Update #2:
Create an instance of ViewModel in your control's constructor and assign it to the control's DataContext. Your DataGrid will bind to the Results property on the ViewModel. When the contents of the ObservableCollection change the contents of the DataGrid will be automatically updated. N
Code:
public class ViewModel
{
public ObservableCollection<CustomETO_GridResult> Results {get;set;}
// Call this method to update the ObservableCollection, obviously could use some optimization
public void UpdateFromDatabase()
{
var query = // make your database query
Results.Clear();
foreach(CustomETO_GridResult result in query)
Results.add(result);
}
... // Initialize ObservableCollection, do other work, etc.
}
XAML:
<DataGrid ItemsSource="{Binding Results}"/>
put tempTable into the observable collection, assign that to datagrid's ItemsSource (once of course) and then when keep pulling new data, keep shoving it in your observable collection, it will do the rest of the magic for you.