views:

470

answers:

3

I work for an Architecture firm and I am creating a plug-in for a 3D modeling program to assist design. I have a Building class, and a Floor class. The building contains a reference to a FloorList collection of floors. I'm trying to figure out what to base the FloorList collection off of so that I can minimize the amount of work I need to do to create an interface to edit the collection.

The Floor collection represents a series of building floors that are stacked on top of one another. Each Floor has a Floor.Height property that is read write, and an Floor.Elevation property that is read only and set by totaling the floor heights below the current Floor. So, whenever a Floor is added, removed, moved, or changed in the collection the Floor.Elevation properties need to be updated.

In addition, I want to create a UI to edit this collection. I was thinking of using a DataGrid control where each Floor is listed with its Height and other properties as a row of the control. The user should be able to add, remove, and re order floors using the control. I'd like setting this up to be as easy and flexible as possible. Meaning I'd like to simply be able to bind the collection of Floors to the DataGrid and have the columns of the DataGrid populate based on the properties of the Floor class. If possible I'd like to be able to utilize the built in Add/Remove UI interface of the DataGrid control with out having to mess with setting up a bunch of event relationships between my collection and the DataGrid.

To complicate things further in the future I'll need to be able to allow the user to dynamically add custom properties to the Floors that I'll want them to be able to see and edit in the DataGrid as well. I think I'd end up doing that by having Floor class implement IExtenderProvider. So ultimately the DataGrid would look something like this:

Initial Properties      Future Custom User Properties

Height    Elevation     ProgramType    UnitType  UnitCount  
15'       70'           Residential    Luxury    5
15'       55'           Residential    Luxury    5
15'       40'           Residential    Budget    10
20'       20'           Retail         N/A       2
20'       0'            Retail         N/A       3

My question now is what should I base my FloorList collection off of to allow for this functionality? The options I'm considering are as follows.

1) Inherit from List(Floor)

  • Methods such as Add/Remove are not vitrual and thus I can't not override them to update the elevations

2) Implement IList(Floor)

  • OnChange event is not built in so if the lists changes the DataGrid will not update (I think?)

  • I think this is probably the best option but what would I need to do the ensure that changes to the FloorList collection or DataGrid are synced with one another?

3) Inherit from BindingList(Floor)

  • Method like Add/Remove are not virtual so I can't modify them to update the floor elevations.

4) Implement IBindingList

  • IBindinglist is not generic and I only want my collection to contain Floor objects
A: 

Implement IList(Floor), and have your new collection also implement INotifyPropertyChanged interface.

Charlie
A: 

You should use BindingList for your collection or implement IBindingList as this will notify back the DataGridView about any changes in the list.

Then implement INotifyPropertyChanged interface for Floor class, this will allow for a TwoWay binding mode between your individual Floor items and the DataGridView.

Eric, you could also do something like this

   public class MyFloorCollection : BindingList<Floor>
            {
                public MyFloorCollection()
                    : base()
                {
                    this.ListChanged += new ListChangedEventHandler(MyFloorCollection_ListChanged);

                }

                void MyFloorCollection_ListChanged(object sender, ListChangedEventArgs e)
                {

                 if (e.ListChangedType == ListChangedType.ItemAdded)
                 {

                    Floor newFloor = this[e.NewIndex] as Floor;

                    if (newFloor != null)
                    {
                        newFloor.HeightChanged += new Floor.HeightChangedEventHandler(newFloor_HeightChanged);
                    }
                  }

                }

                void newFloor_HeightChanged(int newValue, int oldValue)
                {
                    //recaluclate
                }


            }

Of course you can create your own HeightChangedEvent and subscribe to that, that way you dont have to go by property names in an if statement.

So your Floor class will look like this

 public class Floor : INotifyPropertyChanged
        {
            private int _height;

            public int Height
            {
                get { return _height; }
                set 
                {
                    if (HeightChanged != null)
                        HeightChanged(value, _height);

                    _height = value;
                    OnPropertyChanged("Height");

                }
            }




            public int Elevation { get; set; }

            private void OnPropertyChanged(string property)
            {
                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs(property));
            }

            #region INotifyPropertyChanged Members

            public event PropertyChangedEventHandler PropertyChanged;

            #endregion

            public delegate void HeightChangedEventHandler(int newValue, int oldValue);
            public event HeightChangedEventHandler HeightChanged;
        }

this way you only have to subscribe to your HeightChanged variable, instead of to PropertyChanged. PropertyChanged will be consumed by the DataGridView to keep a TwoWay binding. I do believe this way is cleaner.

You can also change the delegate and pass the item as the sender.

public delegate void HeightChangedEventHandler(Floor sender, int newValue, int oldValue);

EDIT: To unsubscribe from HeightChanged event you need to override RemoveItem

  protected override void RemoveItem(int index)
        {
            if (index > -1)
                this[index].HeightChanged -= newFloor_HeightChanged;

            base.RemoveItem(index);
        }
Stan R.
But IBindingList is not generic and I'd prefer to not have to do a bunch of casting from Object types to my Floor type.
Eric Anastas
Eric, I updated my answer.
Stan R.
AHh ok that makes sense. So you are having the class respond to it's own ListChanged Event. However, if you were to remove a floor from the list wouldn't the the Event relationship remain and prevent the Floor from being disposed assuming the the event relationship was the only reference to the Floor.Also the ListChanged event of the collection should be evoked when items in the list change. Do I need to manually link the Floor.PropertyChanged event to the FloorList.ListChanged event or does the BindingList<T> do that automaticly?
Eric Anastas
Eric: please see my answer for removing Floor from the list. Its very dirty,but you get the idea. As far as PropertyChanged, I am pretty positive that as long as you implement INotifyPropertyChanged interface, DataGridView will automatically subscribe to it and unsubscribe when needed. You can see this, when you are adding a new Floor, step in and look at PropertyChanged..you'll see its not null.
Stan R.
When the item is being removed, FOR DEBUGGING PURPOSES make a reference to it -- Floor item = this[index]; in RemoveItem and then look at the item after base.RemoveItem(index) and you'll see PropertyChanged is null as well as HeightChanged. Do this just to see that the events are being unsubscribed, and remove the line after you're satisfied, because you don't need that reference ;)
Stan R.
hey, let me know if this helped you out. I am quite curious.
Stan R.
A: 

You could try exposing an ObservableCollection<Floor> property on some data containing class. Also, the Floor object would need to implement INotifyPropertyChanged to support two-way binding from the UI.

kek444