ObservableCollection does not update - wpf

I have ObservableCollection<Customer> on my window.
ObservableCollection<Customer> customers = new ObservableCollection<Customer>();
public ObservableCollection<Customer> Customers { get { return customers; } set { customers = value; OnPropertyChanged("Customers"); } }
This ObservableCollection has bound to ListView on the window. Once Use select on Customer from listView and click on edit a new window will appear with selected customer's data.
Second window's constructor
public EditCustomerWindow(Customer c)
{
InitializeComponent();
customerobj = c; //Original object
tempCustomerobj = new Customer(c); //Getting a copy of the customer object
CustomerDataGrid.DataContext = tempCustomerobj;
}
Once user clicks on Save Button customer object will get updated and window will closes.
But my issue is ObserverCollection does not get update on fist window even though I set new edited customer object before editing window get closed. Cannot find what is the wrong I am doing. Please advice me.
customerobj = tempCustomerobj;

You appear to be creating a new Customer object that is not in your ObservableCollection
tempCustomerobj = new Customer(c);
and then editing that new object.
CustomerDataGrid.DataContext = tempCustomerobj;
Doing that will not in any way affect the original Customer object that is still in your ObservableCollection.
To solve, don't create a new Customer, but rather edit an existing one.
Update
Based on your comments
The line
customerobj = c; //Original object
causes customerobj to be an alias to c, your object that is actually in the ObservableCollection.
The line
customerobj = tempCustomerobj;
causes customerobj to now be an alias to tempCustomerobj, which is your brand-new Customer object that is (I presume) a clone of c.
Change your constructor to
public EditCustomerWindow(Customer c)
{
InitializeComponent();
CustomerDataGrid.DataContext = c;
}
Update 2
The object you're editing should support IEditableObject. See
https://stackoverflow.com/a/1091240/141172
Alternatively, you can serialize the object before you start editing and deserialize the saved state if the edit is canceled.

Related

unable to add row to a winforms datagridview that is bound to a SortableBindngList

I have a datagridview that was bound to a generic List<> of my objects. Everything works fine. I then changed the List to a SortableBindingList so that I can sort the columns. This works fine to except now I get an exception when I try to add a row. The exception is:
"Operation is not valid due to the current state of the object."
This occurs in the WinForms runtime in DatagridView.DataGridviewDataConnection.ProcessListChanged method.
Anyone have any ideas what the problem might be?
So you have separated your data from how it is displayed. Good for you! Too often I see that people try to fiddle with cells and rows instead of using the datasource.
However, if your DataSource is a List<...>, the changes that the operator makes to the DatagridView are not reflected in the DataSource.
If you want that items that are added / removed / changed by the operator are also changed in your DataSource, you should use an object that implements IBindingList, like (surprise!) BindingList
You forgot to tell us what you show in your DataGridView, let's assume you show Products
class Product
{
public int Id {get; set;}
public string Name {get; set;}
public decimal Price {get; set;}
public int StockCount {get; set;}
public string LocationCode {get; set;}
...
}
Using visual studio designer you've added the columns that you want to show. You'll have to tell which column shows which property. For instance in the constructor:
public MainForm()
{
InitializeComponents();
// define which columns show which properties
columnId.DataPropertyName = nameof(Product.Id);
columnName.DataPropertyName = nameof(Product.Name);
columnPrice.DataPropertyName = nameof(Product.Price);
...
To make access to the displayed products easy, add a Property:
private BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.dataGridView1.DataSource,
set => this.dataGridView1.DataSource = value;
}
Initialization:
private void ShowProducts()
{
IEnumerable<Product> queryProducts = this.FetchProducts();
this.DisplayedProducts = new BindingList<Product>(queryProducts.ToList());
}
Now whenever the operator adds / removes a row, or edits the cells in a row, the BindingList is automatically updated, even if the columns are rearranged, or the data sorted.
If the operator indicates that he finished editing the data, for instance by pressing a button, you immediately have all updated information:
private void OnButtonOk_Clicked(object sender, ...)
{
ICollection<Product> displayedProducts = this.DisplayedProducts;
// find out what is added / removed / changed
this.ProcessEditedProducts(displayedProducts);
}
If you need to access selected items, consider to add the following properties:
private Product CurrentProduct => (Product)this.dataGridView1.CurrentRow?.DataBoundItem;
private IEnumerable<Product> SelectedProducts => this.dataGridView1.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<Product>();
If the operator adds a row, he starts with a row that is constructed using the default constructor. If you don't want this, but for instance initialize the Id, or the LocationCode, consider to subscribe to event BindingList AddingNew
this.DisplayedProducts.AddingNew += OnAddingNewProduct;
void OnAddingNewProduct(object sender, AddingNewEventArgs e)
{
e.NewObject = new Product
{
Id = this.GenerateProductId(),
Name = "Please enter product name>",
LocationCode = "Unknown Location",
...
};
}

How to detect the sender(button) of dynamical created bindings

I create some RibbonButtons dynamically and add them to a group according to an xml file. The follwoing function is carried out as often as entries found in the xml file.
private void ExtAppsWalk(ExternalAppsXml p, AppsWalkEventArgs args)
{
RibbonButton rBtn = new RibbonButton();
rBtn.Name = args.Name;
Binding cmdBinding = new Binding("ExtAppCommand");
rBtn.SetBinding(RibbonButton.CommandProperty, cmdBinding);
Binding tagBinding = new Binding("UrlTag");
tagBinding.Mode = BindingMode.OneWayToSource;
rBtn.SetBinding(RibbonButton.TagProperty, tagBinding);
rBtn.Label = args.Haed;
rBtn.Tag = args.Url;
rBtn.Margin = new Thickness(15, 0, 0, 0);
MyHost.ribGrpExtern.Items.Add(rBtn);
}
I tried to use the Tag property to store the Url's to be started when the respective button is clicked. Unfortunately the binding to the Tag property gives me the last inserted Url only.
What would be the best way to figure out which button is hit or to update the Tag property.
The datacontext is by default the context of the Viewmodel. The RibbonGroup to which the Buttons are added is created in the xaml file at designtime. I use that construct:
MyHost.ribGrpExtern.Items.Add(rBtn);
to add the buttons. It maight not really be conform with the mvvm pattern. May be someone else has a better idea to carry that out.
I foud a solution for my problem here and use the RelayCommand class. So I can pass objects (my Url) to the CommandHandler.
RibbonButton rBtn = new RibbonButton();
rBtn.Name = args.Name;
Binding cmdBinding = new Binding("ExtAppCommand");
rBtn.SetBinding(RibbonButton.CommandProperty, cmdBinding);
rBtn.CommandParameter = (object)args.Url;
private void ExtAppFuncExecute(object parameter)
{
if (parameter.ToString().....//myUrl

Reusing Binding Collections for WPF

I am working on a WPF app using the MVVM patterm, which I am learning. It uses EF4. I am trying to use a similar tabbed document interface style; several combo boxes on these tabs have the same items sources (from a sql db). Since this data almost never changes, it seemed like a good idea to make a repository object to get them when the app starts, and just reuse them for each viewmodel. For whatever reason though, even though I use new in the constructors, the lists are connected.
If I set a bound combo box on one tab, it gets set on another (or set when a new tab is created). I don't want this to happen, but I don't know why does.
The repository object is initialized before anything else, and just holds public lists. The views simply use items source binding onto the ObservableCollection. I am using the ViewModelBase class from the article. Here is the Viewmodel and model code.
ViewModel
TicketModel _ticket;
public TicketViewModel(TableRepository repository)
{
_ticket = new TicketModel(repository);
}
public ObservableCollection<Customer> CustomerList
{
get { return _ticket.CustomerList; }
set
{
if (value == _ticket.CustomerList)
return;
_ticket.CustomerList = value;
//base.OnPropertyChanged("CustomerList");
}
}
Model
public ObservableCollection<Customer> CustomerList { get; set; }
public TicketModel(TableRepository repository)
{
CustomerList = new ObservableCollection<Customer>(repository.Customers);
}
EDIT: I am sure this is the wrong way to do this, I am still working on it. Here is the new model code:
public TicketModel(TableRepository repository)
{
CustomerList = new ObservableCollection<Customer>((from x in repository.Customers
select
new Customer
{
CM_CUSTOMER_ID = x.CM_CUSTOMER_ID,
CM_FULL_NAME = x.CM_FULL_NAME,
CM_COMPANY_ID = x.CM_COMPANY_ID
}).ToList());
}
This causes a new problem. Whenever you change tabs, the selection on the combo box is cleared.
MORE EDITS: This question I ran into when uses Rachels answer indicates that a static repository is bad practice because it leaves the DB connection open for the life of the program. I confirmed a connection remains open, but it looks like one remains open for non-static classes too. Here is the repository code:
using (BT8_Entity db = new BT8_Entity())
{
_companies = (from x in db.Companies where x.CO_INACTIVE == 0 select x).ToList();
_customers = (from x in db.Customers where x.CM_INACTIVE == 0 orderby x.CM_FULL_NAME select x).ToList();
_locations = (from x in db.Locations where x.LC_INACTIVE == 0 select x).ToList();
_departments = (from x in db.Departments where x.DP_INACTIVE == 0 select x).ToList();
_users = (from x in db.Users where x.US_INACTIVE == 0 select x).ToList();
}
_companies.Add(new Company { CO_COMPANY_ID = 0, CO_COMPANY_NAME = "" });
_companies.OrderBy(x => x.CO_COMPANY_NAME);
_departments.Add(new Department { DP_DEPARTMENT_ID = 0, DP_DEPARTMENT_NAME = "" });
_locations.Add(new Location { LC_LOCATION_ID = 0, LC_LOCATION_NAME = "" });
However, now I am back to the ugly code above which does not seem a good solution to copying the collection, as the Customer object needs to be manually recreated property by property in any code that needs its. It seems like this should be a very common thing to do, re-using lists, I feel like it should have a solution.
Custom objects, such as Customer get passed around by reference, not value. So even though you're creating a new ObservableCollection, it is still filled with the Customer objects that exist in your Repository. To make a truly new collection you'll need to create a new copy of each Customer object for your collection.
If you are creating multiple copies of the CustomerList because you want to filter the collection depending on your needs, you can use a CollectionViewSource instead of an ObservableCollection. This allows you to return a filtered view of a collection instead of the full collection itself.
EDIT
If not, have you considered using a static list for your ComboBox items, and just storing the SelectedItem in your model?
For example,
<ComboBox ItemsSource="{Binding Source={x:Static local:Lists.CustomerList}}"
SelectedItem="{Binding Customer}" />
This would fill the ComboBox with the ObservableCollection<Customer> CustomerList property that is found on the Static class Lists, and would bind the SelectedItem to the Model.Customer property
If the SelectedItem does not directly reference an item in the ComboBox's ItemsSource, you need to overwrite the Equals() of the item class to make the two values equal the same if their values are the same. Otherwise, it will compare the hash code of the two objects and decide that the two objects are not equal, even if the data they contain are the same. As an alternative, you can also bind SelectedValue and SelectedValuePath properties on the ComboBox instead of SelectedItem.

Error trying to bind Content to Window for 2nd time

have the following in my CodeBehind (class name MainHostWindow)
private object _hostContent = null;
public object HostContent
{
get { return _hostContent; }
set { _hostContent = value;}
}
this binds into a ContentControl of my View.
in a different class I do the following:
MainHostWindow host = new MainHostWindow();
{
host.HostContent = MyView; // this is an instance of a UserControl
host.Owner = this._mainWindow;
host.DataContext = viewModel;
}
host.ShowDialog();
first time it shows the MainHostWindow with the correct Content, 2nd time I get the following exception:
Specified element is already the logical child of another element. Disconnect it first.
It looks as if you are trying to add the same UserControl (not a new instance of it) to another instance of your MainHostWindow. The error is correct because the same element cannot be the child of two different containers (what would UserControl.Parent return?). You will need to create a new instance of your UserControl.
host.HostContent = new MyView();
are you able to set MyView declaratively in the XAML for MainHostWindow as this would always create a new instance when the Control is created.

Entity Framework 4 Binding a related field to a DataGridView column

I have a DataGridView that is bound - via a binding source - to a list of entities:
VehicleRepository:
private IObjectSet<Vehicles> _objectSet;
public VehicleRepository(VPEntities context)
{
_context = context;
_objectSet = context.Vehicles;
}
List<Vehicle> IVehicleRepository.GetVehicles(Model model)
{
return _objectSet
.Where(e => e.ModelId == model.ModelId)
.ToList();
}
In my presenter
private List<Vehicle> _vehicles;
...
_vehicles = _vehicleRepository.GetVehicles(_model);
_screen.BindTo(_vehicles);
in my view
public void BindTo(List<Vehicle> vehicles)
{
_vehicles = vehicles;
if (_vehicles != null)
{
VehicleBindingSource.DataSource = _vehicles;
}
}
This works fine - my grid displays the data as it should. However, in the grid I am wanting to replace the ModelId column with a description field from the Model table. I've tried changing the binding for the column from ModelId to Model.ModelDescription but the column just appears blank.
I'm pretty sure that the data is being loaded, as I can see it when I debug, and when the same list is passed to a details screen I can successfully bind the related data to text fields and see the data.
Am I doing something obviously wrong?
It's a bit manual, but it 'works on my machine'.
Add a column to your DataGridView for the description field and then after you set your DataSource iterate through like so.
Dim row As Integer = 0
foreach (entity In (List<Entity>)MyBindingSource.DataSource)
{
string description = entity.Description;
MyDataGridView.Rows[row].Cells["MyDescriptionCell"].Value = description;
row ++;
}
You get a readonly view of your lookup. I make the new column readonly, but you could write something to handle the user changing the field if you wanted updates to run back to the server. Might be messy though.
The answer involves adding unbound read only columns and setting their value in the DataGridView's DataBindingComplete event
as described here
You can just add a column to your DataGridView, and in the DataPropertyName you must set the [entity].[Field name you need] in your case you could do: VehiclesType.Description
then you must add another binding source for the VehiclesTypes to the form, fill it using your context, and your good to go ;)

Resources