I'm still in the learning Phase of WPF, EF and MVVM and now I got the following problem. I can delete and insert new items in my DataGridView but I don't know how to update my items.
All I do is select an emptyrow which already has a primary key and then I put the data into it. It's working (updating database) but the GridView is not refreshing. I Need to restart the program first to see my updated data.
My Execute Command to Update my Database. I'm in the ViewModel class
public void ExecuteUpdate(object obj)
{
try
{
SelectedIndex.Child_Update(new Farbe { FarbauswahlNr = SelectedIndex.FarbauswahlNr, Kurztext = SelectedIndex.Kurztext, Ressource = SelectedIndex.Ressource, Vari1 = SelectedIndex.Vari1, Vari2 = SelectedIndex.Vari2 });
//ListeAktualisieren --> Refreshing the List
ListeAktualisieren();
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
Here is my Refresh Method which SHOULD Refresh the GridView. I'm in the ViewModel class
public void ListeAktualisieren()
{
farbliste.ListeAktualisieren(db);
farbliste.Model = farbliste.Model.Concat(farbliste.Addlist).ToList();
Model = farbliste.Model;
farbliste.Addlist.Clear();
}
The method is calling my Business List which also got a Refresh Method. Reading from my database here. I'm in the Business List class
public void ListeAktualisieren(TestDBEntities db)
{
Model.Clear();
foreach (var item in db.Farben)
{
//Insert and delete working
add = new Farbe { FarbauswahlNr = item.FarbauswahlNr, Kurztext = item.Kurztext, Ressource = item.Ressource, Vari1 = Convert.ToBoolean(item.Var1), Vari2 = item.Vari2 };
Addlist.Add(add);
}
}
Model is the Source of my GridView which is not Refreshing changed data when Updated but is showing new data rows when inserting or deleting.
You need Observablecollections and Classes with implemented INotifyPropertyChanged. Add the new element to the Observablecollection by insert and raise the event propertychanged by a change.
The rest should be done by WPF.
Edit: The Sourcecollection for the DataGrid needs to be the Observablecollection.
Edit2: To be nice I put the result of the comments here ;-)
Each row of the DataGrid is an element of the collection. Each cell of one row listens to a PropertyChangedEvent of its element (the String is Casesensitive so be carefull). If the getter of the property isn't called after the propertychangedevent the binding didn't receive the event.
This piece of Code can help asure that you don't call with nonexistent strings:
private void VerifyPropertyName(string PropertyName)
{
if (string.IsNullOrEmpty(PropertyName))
return;
if (TypeDescriptor.GetProperties(this)(PropertyName) == null) {
string msg = "Ungültiger PropertyName: " + PropertyName;
if (this.ThrowOnInvalidPropertyName) {
throw new isgException(msg);
} else {
Debug.Fail(msg);
}
}
}
Try adding this to your binding section
ItemsSource="{Binding Path=Model, UpdateSourceTrigger= PropertyChanged"}
Related
I have problem with ordering data for ListView. I have EventDisplay class which is an ObservableCollection for ListView(called Events)
private ObservableCollection<EventDisplay> currentEvents = new ObservableCollection<EventDisplay>();
private void Events_Loaded(object sender, RoutedEventArgs e)
{
sv = (ScrollViewer)VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(this.Events, 0), 0);
Events.ItemsSource = currentEvents;
}
I then add new data by function :
private void LoadDataToList(List<EventDisplay> newItems)
{
foreach (EventDisplay ed in newItems)
{
//Set some additional data
currentEvents.Add(ed);
}
//When this line below is commented ListView data is updated
//but is not sorted, when i uncomment the listview data is not being updated
//currentEvents = new ObservableCollection<EventDisplay>(currentEvents.OrderByDescending(x => x.ed.date).ToList());
}
So what is the proper way of ordering data for ListView in Windows 8.1 apps ?
You can sort & filter the view of your ObservableCollection (explanation here)
public class ViewableCollection<T> : ObservableCollection<T>
{
private ListCollectionView _View;
public ListCollectionView View
{
get
{
if (_View == null)
{
_View = new ListCollectionView(this);
}
return _View;
}
}
}
Data structure for the example:
interface ICustomer
{
string CuctomerName{get;set;}
int Age{get;set;}
}
Example use of the code:
ViewableCollection<ICustomer> vCustomers = new ViewableCollection<ICustomer>();
// Sorting settings:
ViewableCollection<ICustomer> vCustomers.View.SortDescriptions.Add(new SortDescription("CustomerName", ListSortDirection.Ascending));
vCustomers.View.Filter = MyCustomFilterMethod;
// add data to collection
MyCustomers.ToList().ForEach(customer => vCustomers.Add(customer));
Examlpe filter method:
private bool MyCustomFilterMethod(object item)
{
ICustomer customer = item as ICustomer;
return customer.Age > 25;
}
when you need to refresh the filter, the only thing you need to do is call:
this.vCustomers.View.Refresh();
Then you bind your GUI to vCustomers.View
You don't need to reset binding sources etc.
Use this for your add items code:
foreach (EventDisplay ed in newItems.OrderByDescending(x => x.ed.date).ToList()
{
//Set some additional data
currentEvents.Add(ed);
}
The reason your doesn't work is that you are reassigned the currentEvents reference rather than updating the ObservableCollection.
You should do the following :
currentEvents = new ObservableCollection<EventDisplay>(currentEvents.OrderByDescending(x => x.ed.date).ToList());
Events.ItemsSource = currentEvents;
This forces the ListView to rebind to your new sorted observable collection.
Another option is to sort the Observable collection in place. However, it may introduce flickering as the ListView will constantly update as the sort progresses.
If you don't want the ScrollView to reset its position, you can save the scrollview position and then restore it after sorting the list.
I've had success with Implementing a custom ObservableCollection that supports sorting but prevents UI flickering by suspending change notification during sort and then issuing a reset notification. The ScrollView should stay at its current position even when confronted with the reset event.
I have a ViewModel which contains an ObservableCollection<CustomKeyGroup<CustomItem>> property bound to a control in a View and the problem is that I want to sort this collection by a property in CustomKeyGroup<T>, without setting the ObservableCollection<...> object property (i.e. sort the collection inline):
public class MainViewModel : ViewModelBase {
... // data service etc code
private ObservableCollection<CustomKeyGroup<CustomItem>> _items = new ObservableCollection<CustomKeyGroup<CustomItem>>();
public ObservableCollection<CustomKeyGroup<CustomItem>> Items
{
get
{
return _items;
}
set
{
_items = value;
RaisePropertyChanged("Items");
}
}
public void Sort(string _orderBy = null, bool _descending = true) {
if (string.IsNullOrEmpty(_orderBy) || this.Items.Count == 0) {
return;
}
var test = this.Items.ToList();
// bubble sort
try {
for (int i = test.Count - 1; i >= 0; i--) {
for (int j = 1; j <= i; j++) {
CustomKeyGroup<CustomItem> o1 = test[j - 1];
CustomKeyGroup<CustomItem> o2 = test[j];
bool move = false;
var order = typeof(CustomKeyGroup<CustomItem>).GetProperty(orderBy);
var t = order.GetValue(o1);
var t2 = order.GetValue(o2);
// sort comparisons depending on property
if (_descending) { // ascending
if (t.GetType() == typeof(int)) { // descending and int property
if ((int)t < (int)t2) {
move = true;
}
} else { // descending and string property
if (t.ToString().CompareTo(t2.ToString()) > 0) {
move = true;
}
}
} else { // ascending
if (t.GetType() == typeof(int)) { // ascending and int property
if ((int)t > (int)t2) {
move = true;
}
} else { // ascending and string property
if (t.ToString().CompareTo(t2.ToString()) < 0) {
move = true;
}
}
}
// swap elements
if (move) {
//this.Items.Move(j - 1, j); // "inline"
test[j] = o1;
test[j - 1] = o2;
}
}
}
// set property to raise property changed event
this.Items = new ObservableCollection<CustomKeyGroup<CustomItem>>(test);
} catch (Exception) {
Debug.WriteLine("Sorting error");
}
//RaisePropertyChanged("Items"); // "inline sort" raise property changed to update Data binding
Debug.WriteLine("Sorted complete");
}
... // get data from service, etc.
From the code above, the attempted inline sorts are commented out (as they do not update the control that databinds to it), and the manual setting of Items are left in (works, but if you scroll down the control and sort, it will take you back to the top - undesirable!).
Anyone have any idea how I can update the view/control using an inline sort option? I've also tried manually raising the RaisePropertyChanged event (specified in ObservableObject using the MVVMLight Toolkit) to no avail.
Note: Setting a breakpoint at the end of the try-catch reveals that the ObservableCollection<...> is indeed sorted, but the changes just do not reflect in the View! Even weirder is that the control (LongListSelector) has a JumpList bound to another property of CustomKeyGroup<T> and it successfully updates instantly!! If I tap on any of these items in the JumpList, the View correctly updates itself, revealing the sorted items... I then thought of setting the DataContext of the View after sorting, but that also does not solve the issue.
Thanks.
Adding my own answer here.
So following the comments from the original post, #piofusco points out that a View does not update when an ObservableCollection has only been sorted. Even manually changing the collection (hence, raising NotifyPropertyChanged or NotifyCollectionChanged) does not update it.
Searching around a little more, I decided I could make use of CollectionViewSource, which would do my sorting for me - without changing the collection itself (hence allowing the control to retain its current scroll position). To get it working, basically, add a new property to the ViewModel of type CollectionViewSource, add a SortDescription, set its Source and bind directly to that property (instead of the original ObservableCollection:
In ViewModel:
private CollectionViewSource _sortedCollection = new CollectionViewSource();
public CollectionViewSource SortedCollection {
get {
_sortedCollection.Source = this.Items; // Set source to our original ObservableCollection
return _sortedCollection;
}
set {
if (value != _sortedCollection) {
_sortedCollection = value;
RaiseNotifyPropertyChanged("SortedCollection"); // MVVMLight ObservableObject
}
}
}
View XAML (note the binding to Property.View):
<ListBox ItemsSource="{Binding SortedCollection.View}" ... />
And in your View code-behind, if you have a Sort button:
ViewModel _vm = this.DataContext as ViewModel;
viewModel.SortedCollection.SortDescriptions.Clear(); // Clear all
viewModel.SortedCollection.SortDescriptions.Add(new SortDescription("PropertyName", ListSortDirection.Descending)); // Sort descending by "PropertyName"
And boom! Your sorted collection should update instantly in the View! Even better is that it retains our ObservableCollection functionality in that any updates to objects in the ObservableCollection will raise the NotifyPropertyChanged or NotifyCollectionChanged handlers, thereby updating the View (allowing for both sorting and updating of objects while retaining current scroll positions)!
Note: For those out there using a LongListSelector control, I wasn't able to get it to work, and with a little more internet-digging with I came across this post, which, discusses why LLS cannot bind to a CollectionViewSource.View without some modifications. So I ended up using a ListBox control instead. You can read about some of the differences here. For my task though, the ListBox will suffice.
I have a grid which i load the data as following
Method i without Sorting
public partial class frmSalesOrderDetails : BaseForm.TfrmList
{
// DECLARE OBJECTS
private BusinessObject.TransactionLayer.SalesOrderMaster SalesOrderMaster = new BusinessObject.TransactionLayer.SalesOrderMaster();
private ObservableCollection<BusinessObject.TransactionLayer.SalesOrderDetail> ocSalesOrderDetails { get; set; }
// DATA BINDING TO GRID
private void DataBind_grdSalesOrderDetail(int InvoiceId)
{
// FILL OBJECT SalesOrderMaster , it is based on POCO so it also contains SalesOrderMaster.SalesOrderDetails
SalesOrderMaster = (BusinessObject.TransactionLayer.SalesOrderMaster)(_Context.SalesOrderMaster.Where(t => t.InvoiceId == InvoiceId).FirstOrDefault());
//Fill the Grid's datasource
grdSalesOrderDetail.DataSource = SalesOrderMaster.SalesOrderDetails.ToBindingList().ToBindingList();
}
Mehod 2 with sorting
public partial class frmSalesOrderDetails : BaseForm.TfrmList
{
// DECLARE OBJECTS
private BusinessObject.TransactionLayer.SalesOrderMaster SalesOrderMaster = new BusinessObject.TransactionLayer.SalesOrderMaster();
private ObservableCollection<BusinessObject.TransactionLayer.SalesOrderDetail> ocSalesOrderDetails { get; set; }
// DATA BINDING TO GRID
private void DataBind_grdSalesOrderDetail(int InvoiceId)
{
// FILL OBJECT SalesOrderMaster , it is based on POCO so it also contains SalesOrderMaster.SalesOrderDetails
SalesOrderMaster = (BusinessObject.TransactionLayer.SalesOrderMaster)(_Context.SalesOrderMaster.Where(t => t.InvoiceId == InvoiceId).FirstOrDefault());
// SORT THE DATA on a temp ocSalesOrderDetails2
ObservableCollection<BusinessObject.TransactionLayer.SalesOrderDetail> ocSalesOrderDetails2 = new ObservableCollection<BusinessObject.TransactionLayer.SalesOrderDetail>(SalesOrderMaster.SalesOrderDetails.OrderBy(t => t.STFicheLineNo));
// Fill ocSalesOrderDetails with ocSalesOrderDetails2's data
ocSalesOrderDetails = new ObservableCollection<BusinessObject.TransactionLayer.SalesOrderDetail>(ocSalesOrderDetails2);
//Fill the Grid's datasource
grdSalesOrderDetail.DataSource = ocSalesOrderDetails.ToBindingList();
}
So the problem is as following : When I use Method 1 and use the following code to remove (delete ) a SalesOrderDetail :
base._Context.SalesOrderDetail.Remove(((BusinessObject.TransactionLayer.SalesOrderDetail)((DevExpress.XtraGrid.Views.Grid.GridView)grdSalesOrderDetail.MainView).GetFocusedRow()));
All is in sync, the record is removed from Context, and grid datasource When I use Method 2 and use delete the same way : There is no more sync, the record is not deleted from the grid datasource
Could anybody help please.
If you follow this link, ObservableCollection Constructor you'll see that the constructor actually creates a copy of the collection passed to it. So for your method1, when you delete a row, SalesOrderMaster from _context got changed which was instantly reflected in your object, SalesOrderMaster used as the DataSource.
For method2, the same is still happening, except, now in the memory, you have 2 copies of the same data. The _context value is still getting updated but that has got nothing to do with the object ocSalesOrderDetails which has it's own set of data used as DataSource. Actually, your grid is in sync with this variable instead of the actual result.
Easy way out is to call this method
DataBind_grdSalesOrderDetail(int InvoiceId)
after each delete operation to keep your grid both, in sync as well as sorted.
I am writing my first application for WP8 platform in C#. I implemented three datatypes namely locationModel which has locationGroups. Each locationGroup has a ObservableCollection of type locationData.
locationData has two double types for latitude and longitude and a title string.
I used a textblock inside a stackpanel to show the locationData element's title, where the lat long are hidden to user.
There is a context menu on each of this textblock element which enable the user to delete the respective locationData.
When I open the app and delete any item, it succesfully does and updates the view too. But when I do it for another item it just doesnt work. I cannot delete more than one items for each time I open the app.
I used breakpoints to see where the problem is. the selected locationData is succesfully passed to the App.ViewModel.LocationModel.Items.Remove(). But it just that they are not deleted from the observable collection. I even tried to see the index of the locationData in observable collection and use RemoveAt method. Even it doesnt work.
I did a lot of research to find the problem, but no one seems to face the same problem as me. I referred to msdn article on how to implement inotifypropertychanged to update the collection. But its too complex for me to understand that.
I dont really understand why the observable collection delete the item for the second time even though if I pass the index of that item. And my usage of breakpoints showed me that the data is not even null.
So kindly tell me what is causing this problem and how do I overcome it so that I can implement my own workaround and not face this issue again. I can show you the code if you want me to.
Thanks.
CODE:
Adding an item
private void SaveLocationData(LocationData locationData)
{
IsolatedStorageSettings appSettings = IsolatedStorageSettings.ApplicationSettings;
try
{
App.ViewModel.Custom.Items.Add(locationData);
var data = JsonConvert.SerializeObject(App.ViewModel.Custom);
appSettings[LocationModel.CustomKey] = data;
appSettings.Save();
//Notify that data is changed
App.ViewModel.LoadData();
NavigationService.Navigate(new Uri("/MainPage.xaml", UriKind.RelativeOrAbsolute));
}
catch(IsolatedStorageException ex)
{
MessageBox.Show(ex.Message);
}
}
Deleting item:
private void DeleteLocationData(LocationData locationData)
{
IsolatedStorageSettings appSettings = IsolatedStorageSettings.ApplicationSettings;
try
{
App.ViewModel.Custom.Items.Remove(locationData);
var data = JsonConvert.SerializeObject(App.ViewModel.Custom);
appSettings[LocationModel.CustomKey] = data;
appSettings.Save();
//Notify that data is changed
App.ViewModel.LoadData();
NavigationService.Navigate(new Uri("/MainPage.xaml", UriKind.RelativeOrAbsolute));
}
catch (IsolatedStorageException ex)
{
MessageBox.Show(ex.Message);
}
}
One more thing that I want to say is, whenever I add a locationData to the collection, it updates automatically. Because adding is done on another page and when the mainpage.xaml loads(in which the observable collection data is), it updates automatically because of the code in OnNavigatedTo method.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
And LoadData method is :
public void LoadData()
{
Custom = LoadCustomLocations();
IsDataLoaded = true;
}
private LocationGroup LoadCustomLocations()
{
string dataFromAppSettings;
LocationGroup data;
if (IsolatedStorageSettings.ApplicationSettings.TryGetValue(CustomKey, out dataFromAppSettings))
{
data = JsonConvert.DeserializeObject<LocationGroup>(dataFromAppSettings);
}
else
{
data = new LocationGroup();
}
return data;
}
So, can anyone help ?
In the case you described on your comment I think you set the DataContext to your Items. When you create a new Items-List the DataContext will be lost. So you have to reset the DataContext to the new loaded Items-List
I'm using WPF to create an application to enable an organisation to enter different pieces of data into the application.I have a tab control to allow them to do this.
Then in a separate view, I have a series of different data grids showing the user what data they have inserted into the database. Containing buttons to either, add, update or delete the data they want.
Which leads me to my question. Currently, I am able to delete, and add data with ease and with no problem. But then comes my issue with trying to get the selected item to update, which it doesn't, resulting in a null reference exception.
If i set my property attributes programmatically though, it updates it fine. like so;public int _OrganisationTypeDetailID = 17; public int _OrganisationTypeID = 1;But I do not want this, as I want the ability for the user to select for themselves and update the data they need to.
Here's some of the code that may help in resolving my issue;
View Model;
public void UpdateOrganisationTypeDetail(OrganisationTypeDetail orgTypeDetail)
{
using (DBEntities context = new DBEntities())
{
var orgTD = context.OrganisationTypeDetails.Where(otd => otd.OrganisationTypeDetailID == SelectedType.OrganisationTypeDetailID).FirstOrDefault();
if (orgTD != null)
{
orgTD.Title = Title;
orgTD.FirstName = FirstName;
orgTD.Surname = Surname;
orgTD.Position = Position;
orgTD.DateOfBirth = DateOfBirth;
orgTD.Address = Address;
orgTD.Country = Country;
orgTD.Postcode = Postcode;
orgTD.PhoneNumber = PhoneNumber;
orgTD.MobileNumber = MobileNumber;
orgTD.FaxNumber = FaxNumber;
orgTD.Email = Email;
orgTD.NINumber = NINumber;
//context.OrganisationTypeDetails.Attach(orgTD);
context.OrganisationTypeDetails.ApplyCurrentValues(orgTD);
context.SaveChanges();
MessageBox.Show("Updated Organisation Type Details");
}
else
{
MessageBox.Show("Unable to update selected 'Type'.");
}
}
private OrganisationTypeDetail _SelectedType;
public OrganisationTypeDetail SelectedType
{
get
{
return _SelectedType;
}
set
{
if (_SelectedType == value)
return;
_SelectedType = value;
OnPropertyChanged("SelectedType");
}
}
public List<OrganisationTypeDetail> GetOrganisationTypeDetail //Loads data
{
get
{
using (DBEntities context = new DBEntities())
{
var query = from e in context.OrganisationTypeDetails
select e;
return query.ToList<OrganisationTypeDetail>();
}
}
}
private ICommand showUpdateCommand;
public ICommand ShowUpdateCommand //Update command
{
get
{
if (showUpdateCommand == null)
{
showUpdateCommand = new RelayCommand(this.UpdateFormExecute, this.UpdateFormCanExecute); //i => this.UpdateOrganisationTypeDetail()
}
return showUpdateCommand;
}
}
Code behind;
private void btnUpdateOrgTypeDetail_Click(object sender, RoutedEventArgs e)
{
OrganisationTypeDetail selected = dgOrgTypeDetail.SelectedItem as OrganisationTypeDetail;
OrganisationTypeDetailViewModel org = new OrganisationTypeDetailViewModel();
if (selected == null)
MessageBox.Show("You must select a 'Type' before updating.");
else
{
OrganisationTypeDetailUpdateView update = new OrganisationTypeDetailUpdateView();
update.ShowDialog();
org.UpdateOrganisationTypeDetail(selected);
Page_Loaded(null, null);
}
}
xaml;
<DataGrid Name="dgOrgTypeDetail" Height="145" Width="555"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding GetOrganisationTypeDetail}"
SelectedItem="{Binding SelectedType, Mode=TwoWay}">
Hope this issue can be resolved.
I would say that your best bet for this is to use commanding in the MVVM pattern to achieve this..
It looks like you're using a combination of MVVM and code behind and actually creating a new instance of the view model when your click event fires. Try binding the view model to your view once in the code behind of the view as the datacontext and then try updating the selected type..
Also when you're trying to do the update on SelectedType - look at your View using Snoop - see if the SelectedType property is still bound to the view.
ICommand UpdateOrgTypeDetail { get;}
Then in the view model constructor declare new instance
UpdateOrgTypeDetail = new DelegateCommand<object>(ExecuteUpdateOrgTypeDetail, CanExecuteUpdateOrgTypeDetail);
These two delegates will then allow you to click your button (which needs to bind to UpdateOrgTypeDetail)
<Button Command="{Binding UpdateOrgTypeDetail}" />
You should find that the update on the property is done correctly from here.