Okkaayy, after much trial and error, I have something that seems to work.
I'm
creating a contact database. My main entity is a Person. The related
entities are Phone, Address, Email, etc. There is a form that allows
users to view and edit Person data. The related tables are represented
by read-only DataGridViews. For example, there is a DataGridView bound
to Person.PhoneCollection.
While users cannot edit records directly in
a DataGridView, they can click Add or Edit buttons. Clicking one of these buttons opens a little
dialog box, where the user can add a record or modify an existing record.
This dialog has the standard "Okay" and "Cancel" buttons. If the user
clicks "Cancel", the current changes are discarded. If the user clicks
"Okay", then the changes they've made to the current Phone record are
reflected back in the DataGridView. However, the changes are not saved
until the user clicks Save on the main Person form.
I've tried a million different combinations, and here's what works so far.
When the main form opens (which is bound to a Person record), the DataGridView that lists the person's phone numbers is bound to the related PhoneCollection:
Code:
phoneCollection = person.PhoneCollectionByPersonId;
this.grdPhone.DataSource = phoneCollection;
If the user clicks the Add button located above the DataGridView, the following code is executed:
Code:
private void btnAddPhone_Click(object sender, EventArgs e)
{
Phone newPhone = new Phone();
EditPhone edit = new EditPhone(newPhone);
DialogResult result = edit.ShowDialog(this);
if (result == DialogResult.OK) {
newPhone.PersonId = person.Id;
phoneCollection.AttachEntity(newPhone);
}
}
So, a new Phone object is created and then sent to the dialog box (a form called EditPhone). When the user clicks "Okay" on the dialog, the foreign key is set properly, and the record is added to the PhoneCollection using the AttachEntity method. This works perfectly. Thanks to data binding, the new record automatically pops into the DataGridView. The new Phone record has not been saved, though.
Rather than having a single Edit button, I put an Edit button in each row of the DataGridView. When that button is clicked, the corresponding row in the DataGridView should be opened in the dialog box.
I tried extracting a Phone record from the PhoneCollection and simply sending that to EditPhone, my dialog box. It worked perfectly for newly added records that hadn't yet been saved. But for old records that had been saved, it didn't work as I hoped. Here is where my code gets a little kludgy. I found I could create a duplicate Phone record, send THAT to EditPhone, and then copy the results back to the Phone record in the PhoneCollection.
Code:
private void grdPhone_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (IsNonHeaderButtonCell(this.grdPhone, e)) {
Phone phoneToEdit = phoneCollection[e.RowIndex];
Phone copyToEdit = new Phone();
EditPhone edit;
if (phoneToEdit.es.IsAdded) {
edit = new EditPhone(phoneToEdit);
} else {
CopyEntity(phoneToEdit, copyToEdit);
edit = new EditPhone(copyToEdit);
}
DialogResult result = edit.ShowDialog(this);
if ((phoneToEdit.es.IsAdded == false) && (result == DialogResult.OK)) {
CopyEntity(copyToEdit, phoneToEdit);
phoneToEdit.PersonId = person.Id;
}
}
}
private bool IsNonHeaderButtonCell(DataGridView grd, DataGridViewCellEventArgs cellEvent)
{
if (grd.Columns[cellEvent.ColumnIndex] is DataGridViewButtonColumn
&& cellEvent.RowIndex != -1) { return true; } else { return (false); }
}
private void CopyEntity(EntitySpaces.Core.esEntity original, EntitySpaces.Core.esEntity copy)
{
foreach (EntitySpaces.Interfaces.esColumnMetadata c in original.es.Meta.Columns) {
copy.SetProperty(c.PropertyName, original.GetColumn(c.PropertyName));
}
}
The code for EditPhone is quite simple. It's a form with a few fields, which are bound to a BindingSource. When I created the form I set the DataSource to a Phone record, but then removed it and set it programmatically. Here is the EditPhone class:
Code:
public partial class EditPhone : Form
{
public EditPhone(Phone phoneToEdit)
{
InitializeComponent();
this.bindingSource.DataSource = phoneToEdit;
}
private void btnCancel_Click(object sender, EventArgs e)
{
this.bindingSource.CancelEdit();
}
private void btnOk_Click(object sender, EventArgs e)
{
this.bindingSource.EndEdit();
}
}
I actually think the code for btnOk_Click isn't even necessary. The code for btnCancel_Click is only necessary for records that were recently added but haven't yet saved.
When the user clicks the Save button back on the main Person form, the code couldn't be simpler:
That automatically saves the related PhoneCollection. If the user never clicks the Save button, then the changes to the PhoneCollection are never committed to the database, which is the desired behavior.
So there you have it. For people who want to use EntitySpaces, read-only DataGridViews, and modal dialog boxes to add/edit records, this solution seems to work.
However, my solution feels kludgy. I'm not even sure why some parts of it work. If somebody has a more elegant solution, please post it on this thread.
Thanks!
Patrick