DataGridView, New row disappears unless edited

DataGridView, New row disappears unless edited

Old forum URL: forums.lhotka.net/forums/t/4084.aspx


JimBalo posted on Monday, December 24, 2007

Hello,

I have a DataGridView bound to a SortedBindingList<Invoice>.  When I click on the "New" row on the grid, a new row gets added with all the proper defaults as needed.  So far, so god.  But the moment I leave the new row, it disappears.  However, if I edit the row in any way, it stays put.  It is as if the grid believes the new row is empty and should so be removed unless I manually edit one of the cells.

Any suggestions for how to make new rows stay put without manually having to edit them?

Thanks & Merry Christmas,

Jim

 

 

 

 

 

ajj3085 replied on Wednesday, December 26, 2007

Are you overriding AddNewCore properly in your BusinessListBase subclass?  Also, do your Invoice objects return unique values from GetIdValue?  Each invoice must have a value unique within the collection, or the grid will get confused.

JimBalo replied on Wednesday, December 26, 2007

Hi Ajj and thanks for the reply.

Yes, I am overrideing AddNewCore (and setting AllowNew = true in constructor), as well as using a unique ID in GetIdValue. 

The way I have it setup, the moment the user clicks on the blank "new row" row in the grid, a new record get added to the collection with all fiels automatically filled in with default values and it shows up in the grid just fine.  If I just change any one character in any of the cells of that new row, everything works fine (=the row stays put after I move away from it).  But if I do not, it disappears the moment I click on a different row or similar. 

I believe the grid (or maybe the binding source) does not know that the new row has its fields filled out with defaults automatically, so when you leave it without editing it, it treats it as if it were an empty row that should not be saved.

What I am missing?

Thanks,

Jim

ajj3085 replied on Wednesday, December 26, 2007

Can we see your implementation of AddNewCore?  Also, what grid control are you using?  Maybe its trying to be smart and removing rows which the user hasn't changed to be "helpful."

JimBalo replied on Thursday, December 27, 2007

Hi Ajj & Marjon,

Here are my AddNewCore and some other possibly relevant snippets:

protected override object AddNewCore()
{
   PaymentOrder paymentOrder = PaymentOrder.NewPaymentOrder(_invoice);
   Add(paymentOrder);
   return paymentOrder;
}

This is the constructor on PaymentOrder that gets called (via Static factory method):

private PaymentOrder(Invoice invoice, SmartDate payDate, double payAmount)
{
   _invoice = invoice;
   _invoiceNo = invoice.InvoiceNo;
   _vendorNo = invoice.VendorNo;
   _seqNo = invoice.PaymentOrders.NextPaymentNo;
   PayDateString = payDate.Text;
   PayAmount = payAmount;

   ValidationRules.CheckRules();
   MarkAsChild();
}

As you can see, I am using both instance variables (when there are no settors) and property settors (when available).

Here are the two gettors / settors:

public string PayDateString
{
   get
   {
       return _payDate.Text;
   }
   set
   {
       if (value == null) value = string.Empty;
       if (!_payDate.Equals(value))
       {
           _payDate.Text = value;
           PropertyHasChanged("PayDateString");
       }
   }
}

public double PayAmount
{
   get
   {
       return _payAmount;
   }
   set
   {
       if (!_payAmount.Equals(value))
       {
           _payAmount = value;
           PropertyHasChanged("PayAmount");
       }
   }
}

Thanks,

Jim

ajj3085 replied on Thursday, December 27, 2007

It doesn't look like you're raising the AddingNew event.  I'm not sure if that may make a difference or not. 

Also, databinding ONLY works with properties, it won't work with fields.  Even if the property would only have a getter, you should consider changing your public fields to private and exposing via a property.  Maybe that is confusing things as well.

JimBalo replied on Thursday, December 27, 2007

ajj3085:
It doesn't look like you're raising the AddingNew event.  I'm not sure if that may make a difference or not. 

Also, databinding ONLY works with properties, it won't work with fields.  Even if the property would only have a getter, you should consider changing your public fields to private and exposing via a property.  Maybe that is confusing things as well.

Ok, I tried raising the event, but it did not make a difference:

protected override object AddNewCore()
{
   PaymentOrder paymentOrder = PaymentOrder.NewPaymentOrder(_invoice);
   OnAddingNew(new AddingNewEventArgs(paymentOrder));
   Add(paymentOrder);
   return paymentOrder;
}
No, I do not have any public fields in PaymentOrder (nor anywhere else) - the snippet I posted was from the constructor which uses the private fields for the fields that do not have settor properties.

The grid is a standard  DataGridView.  I could try a third-party grid, but I do not think that should be needed - I am sure I am just missing some detail to make it work with the regular DataGridView.

I appreciate your help so far.  Any other suggestions?

Thanks,

Jim

 

 

tmg4340 replied on Thursday, December 27, 2007

I'm wondering if the PropertyChanged comment might be something more to investigate...

I see that your constructor is providing some property values, but what actually gets passed in?  In other words, are the property values that are being set actually different from your default values?

One thing to try would be to put a "MarkDirty()" call at the end of your constructor.  Technically, it shouldn't be necessary, but again, if your property procedures are not getting "new" values, that will never happen.  MarkDirty raises the "unknown" PropertyChanged event, which might do the trick.

HTH

- Scott

JimBalo replied on Thursday, December 27, 2007

tmg4340:

I'm wondering if the PropertyChanged comment might be something more to investigate...

I see that your constructor is providing some property values, but what actually gets passed in?  In other words, are the property values that are being set actually different from your default values?

One thing to try would be to put a "MarkDirty()" call at the end of your constructor.  Technically, it shouldn't be necessary, but again, if your property procedures are not getting "new" values, that will never happen.  MarkDirty raises the "unknown" PropertyChanged event, which might do the trick.

HTH

- Scott

No, the properties are set to different values and I have checked to make sure IsDirty = true, so that should not be the problem.  I have also tried MarkDirty(false) in the constructor to be sure (but took it out since it did not help).

Also, I noticed that I do not have to actually change a field value to make the new row stick.  Example:

1) Click on the "new row" row.  A new row gets filled out with all proper defaults automatically.

2) In one of the fields, hit F2 to go to Edit mode.

3) Hit backspace to delete one character

4) Type the character just deleted (so that the cell has the same value it had before)

5) Click on a different row.  The added row stays put.

Jim

 

JimBalo replied on Thursday, December 27, 2007

I created a simple test app that is only using the regular BindingList and I get the same problem.  I will post a question on a Windows Form group since this is not really a CSLA problem, but if anyone here has any more suggestions, they are certainly welcome.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace TestApp
{
    public partial class Form1 : Form
    {
        PersonList _persons = new PersonList();
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            dataGridView1.DataSource = _persons;
        }
    }

    public class PersonList : BindingList<Person>
    {
        public PersonList ()
            : base ()
        {
            AllowNew = true;
        }

        protected override object AddNewCore()
        {
            Person person = new Person("<Type name here>", 0);
            Add(person);
            return person;
        }
    }

    public class Person
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
       
        private int _age;
        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }

        public Person(string name, int age)
        {
            _name = name;
            _age = age;
        }
    }
}

tmg4340 replied on Thursday, December 27, 2007

Hmm... Essentially, this seems to boil down to a Begin/Cancel/ApplyEdit issue.  Rocky has said more than once that BeginEdit() is called on the current item in a grid pretty much immediately - in fact, his latest e-book says "the current object is always in edit mode."  Based on what you are seeing, the new object may be in edit mode, but it doesn't recognize the programmatic "edits" you're making.

I'm wondering if this is a side-effect of being a new object.  Look at it this way - when you create the new object in "AddNewCore", you're raising PropertyChanged() events in the object constructor.  But the object very likely hasn't been bound to the grid yet - after all, you haven't even added it to the collection yet.  So, as such, those PropertyChanged() events are "eaten", and the fully-populated object looks clean to the grid.  Given that, if you don't change anything via the grid, it's not considered an edit, and CancelEdit() is called instead of ApplyEdit() when you move off the row.

This is honestly something I've never run into, because I've never had the occasion to create new rows that can be fully accepted "as-is".  But if I'm right, you might try something like this (if nothing else, doing so should prove me right or wrong either way):

Take your constructor code and move it to an "Initialize" method.  After you add the object to the collection, but before you return it in "AddNewCore", call the "Initialize" method.  My theory is that adding the object to the collection will hook the PropertyChanged() events, and thus your "Initialize" call that raises them should be caught by the grid.

HTH

- Scott

JimBalo replied on Friday, December 28, 2007

tmg4340:

This is honestly something I've never run into, because I've never had the occasion to create new rows that can be fully accepted "as-is".  But if I'm right, you might try something like this (if nothing else, doing so should prove me right or wrong either way):

Take your constructor code and move it to an "Initialize" method.  After you add the object to the collection, but before you return it in "AddNewCore", call the "Initialize" method.  My theory is that adding the object to the collection will hook the PropertyChanged() events, and thus your "Initialize" call that raises them should be caught by the grid.

Thanks Scott!!  That was it! And of course it makes total sense once you pointed that out.  Actually, there was one more thing that needed to be done for it to work, but the main thing was obviously to get the item into the collection before firing off the events to the data binding.  The "one more thing" was to call "OnUnknownPropertyChanged()" from that new Initialize method (even though the settors already called PropertyHasChanged and IsDirty = true.  Not sure why this was required, but it works now. 

Here are some snippets from the now working code:

InvoicePaymentOrderList class:

protected override object AddNewCore()
{
   PaymentOrder paymentOrder = PaymentOrder.NewPaymentOrder();
   Add(paymentOrder);
   paymentOrder.Initialize(_invoice);
   return paymentOrder;
}

PaymentOrder class:

public void Initialize(Invoice invoice)
{
   _invoice = invoice;
   _invoiceNo = invoice.InvoiceNo;
   _vendorNo = invoice.VendorNo;
   _seqNo = invoice.PaymentOrders.NextPaymentNo;
   PayDateString = invoice.DueDate.Text;
   PayAmount = invoice.NetAmountDue;

   ValidationRules.CheckRules();
   OnUnknownPropertyChanged(); // This has to be here for it to work.  Go figure...
}

Jim

 

ajj3085 replied on Friday, December 28, 2007

Is there a reason your factory method isn't doing DataPortal.Create<PaymentOrder>()?  I didn't think anything of it at the time, but that's how I usually create child objects, because they are being created independately of the root and its feasible that I might load defaults from the database.  If you do this, the data portal will call MarkNew for you, which will end up calling OnUnknownPropertyChanged.

If you don't have any database work to do, you can always mark your DP_C method with the RunLocal attribute.  You may want to consider moving to this method of creating children.

Typically I only follow the pattern you're doing if I'm loading children from the database; in that case there's no need to call DataPortal.Fetch, since the root object already has done that.  Sorry for not picking up on that earlier..

HTH
andy

tmg4340 replied on Friday, December 28, 2007

That's true - I didn't think about that.  Take the "Initialize" code, put it in DP_Create, and call that from your factory method... duh!

You do this long enough, and you tend to forget why you do things a certain way... Embarrassed [:$]

JimBalo replied on Friday, December 28, 2007

ajj3085:
Is there a reason your factory method isn't doing DataPortal.Create<PaymentOrder>()?  I didn't think anything of it at the time, but that's how I usually create child objects, because they are being created independately of the root and its feasible that I might load defaults from the database.  If you do this, the data portal will call MarkNew for you, which will end up calling OnUnknownPropertyChanged.

If you don't have any database work to do, you can always mark your DP_C method with the RunLocal attribute.  You may want to consider moving to this method of creating children.

Typically I only follow the pattern you're doing if I'm loading children from the database; in that case there's no need to call DataPortal.Fetch, since the root object already has done that.  Sorry for not picking up on that earlier..

Well, the main reason would be ignorance on my part - I am still struggling to learn the CSLA way of doing things.  But doing a quick search in the forum about Create vs. Fetch, I came across this from Rocky:

http://forums.lhotka.net/forums/thread/14117.aspx

"Typically the data portal is only used for root objects.

While you can technically use the data portal for child objects, there's no value to doing so, and so I recommend against it. Instead, I recommend using the coding style from the book, where the parent calls a Friend/internal factory method on the child, that factory method calls the constructor and the constructor calls a private Create() or Fetch() method to do the actual data load."

Currently, this is what I am doing (or was, before I changed to using an Initialize method):

internal static PaymentOrder NewPaymentOrder(Invoice invoice)
{
   return new PaymentOrder(invoice, invoice.DueDate, invoice.NetAmountDue);
}

private PaymentOrder(Invoice invoice, SmartDate payDate, double payAmount)
{
   _invoice = invoice;
   _invoiceNo = invoice.InvoiceNo;
   _vendorNo = invoice.VendorNo;
   _seqNo = invoice.PaymentOrders.NextPaymentNo;
   PayDateString = payDate.Text;
   PayAmount = payAmount;

   ValidationRules.CheckRules();
   MarkAsChild();
}

Should I use DataPortal_Create<> from the private constructor instead of what I am doing now?

Jim

 

tmg4340 replied on Friday, December 28, 2007

You certainly don't have to use DP_C with your child objects.  One of the reasons Rocky recommends against it is because in n-tier designs it saves a round-trip - but, like Andy said, you can mark the DP_C with the RunLocal attribute and get around that.  Also, if you're pulling your child data at the same time as the parent data, using the DP_ calls for your children passes that data around to a lot of places, and ends up involving a couple of reflection calls you don't need.  As has been said before, the reflection used in the DP isn't really going to hurt your performance, but why involve it if you don't have to?

Using DP_C would essentially be doing the same thing you're doing now - creating the object and initializing it in separate steps.  The only kicker might be the timing of things - the object needs to be added to the collection before you do all your property initialization to get what you want from the grid.  Even with the "MarkNew" call (which you probably should be doing in your "Initialize" method instead of "OnUnknownPropertyChanged"), unless that happens after the add to the collection, you're still up a creek.

The one advantage to using a DP_C on children is the situation Andy talks about, where his child objects are created separately from their root.  In that case, you really have to use DP_C, since the child objects are more separated from their roots.

In the end, I'd probably leave it the way it is, with the "Initialize" call.  I've used DP_C in several cases for child objects, but again, I've never created child objects that are 100% acceptable right from the get-go.

- Scott

JimBalo replied on Saturday, December 29, 2007

Thanks, Scott, et al. 

Jim

ajj3085 replied on Monday, December 31, 2007

Well, one thing to keep in mind is that the thread to which Rocky was responding was talking about a scenario where you were already in the dataportal.  So its the root calling an internal factory method, and there is probably don't make sense to use the dataportal.  Even if you weren't already in the dataportal, it may not make sense.  It depends on what you want to do.

Personally I prefer to use the DP pretty much whenever there's a public factory method that can be called.   The reason is that these are to be called from the UI, and there may be case where I actually need to load defaults from a database.  If / when that time comes, I can just remove the RunLocal attribute and modify my DP_C code to load from the database.

Internal factory methods (for me, anyway) are used exclusively by root objects and assume we're already running on the application server.  So here I never use the dataportal, but I DO have a pattern I like to follow:

internal static MyChild GetChild( ChildData dataObject ) {
    MyChild result;
   
    result = new MyChild();
    result.LoadSelf( dataObject );

    return result;
}

private void LoadSelf( ChildData dataObject ) {
    // Load fields from dataObject

    ValidationRules.CheckRules();
    MarkOld();
}

LoadSelf then is similar to a DataPortal_Fetch in what it does, but obviously it's not used by the DataPortal itself.  I do similar things if a root is creating child objects during a DataPortal_Create too, but that's rare.  In this way everything is pretty similar, whether or not you're going through a DataPortal directly or not.

There's no reason you need to do this though, your method is perfectly fine.  I just do it because it makes child and root objects feel more consistent (and forces me to remember what the dataportal does for me, like call MarkOld, and what it doesn't).

Marjon1 replied on Wednesday, December 26, 2007

This is just a stab at the dark, but perhaps it is looking for a PropertyHasChanged event to be raised to know that something has changed.

Are you setting the values directly or via properties?



Copyright (c) Marimer LLC