views:

451

answers:

2

I'm creating a WPF application using the MVVM design pattern, and I'm trying to create a Combobox that allows the user to edit the items in the drop-down list at runtime, similar to the way MS Access 2007 lets you do it. So I've created a UserControl that builds on top of a Combobox... when the drop-down is shown, there is a button below the list that opens another window to edit the items in the list. Pretty straight forward, but the popup window knows nothing about the type of items in the list, other than they are of some type of an ObservableCollection.

You can view a screenshot of what I'm trying to explain HERE.

For instance, on the View, I bind the custom combobox to ObservableCollection<Sizes>. I click the button to edit the list and the popup window displays all of the items in a TextBox for the user to edit. The problem is trying to add/update/delete items in the ObservableCollection from the popup window. This window does not know anything about the ObservableCollection, other than the name of the display field (this.DisplayMemberPath).

I know that I could always bind the combobox to an ObservableCollection<string>, or IEnumerable<string>, but I am using LINQ to SQL to populate the combobox items, and I need to be aware of change tracking on all of my objects so I can update the database of changes made to the list. Therefore, (I think) I must use ObservableCollection<Sizes> in order to monitor change tracking. I've also toyed with the idea of using the CollectionChanged Event to update the database, but I'm wondering if there is a cleaner method.

I have a feeling that I'll be needing to use Reflection to update the list, but I'm not very well versed in working with Reflection.

Here's my source code for displaying the popup window:

public event EventHandler<EventArgs> EditListClick;
private void EditButton_Click(object sender, RoutedEventArgs e)
{
    if (this.EditListDialog == null)
    {
        // Create the default dialog window for editing the list
        EditListDialogWindow dialog = new EditListDialogWindow();
        string items = string.Empty;

        if (this.Items != null)
        {
            // Loop through each item and flatten the list
            foreach (object item in this.Items)
            {
                PropertyInfo pi = item.GetType().GetProperty(this.DisplayMemberPath);
                string value = pi.GetValue(item, null) as string;

                items = string.Concat(items, ((items == string.Empty) ? items : "\n") + value);
            }

            // Now pass the information to the dialog window
            dialog.TextList = items;
        }

        // Set the owner and display the dialog
        dialog.Owner = Window.GetWindow(this);
        dialog.ShowDialog();

        // If the user has pressed the OK button...
        if (dialog.DialogResult.HasValue && dialog.DialogResult.Value)
        {
            // Make sure there has been a change
            if (items != dialog.TextList)
            {
                // Unflatten the string into an Array
                string[] itemArray = dialog.TextList.Split(new string[]{"\n", "\r"}, StringSplitOptions.RemoveEmptyEntries);

                // Add the items to the list
                foreach (string item in itemArray)
                {
                    // This is where I run into problems...
                    // Should I be using reflection here??
                    ((ObservableCollection<object>)this.ItemsSource).Add(item.Trim());
                }
            }
        }
    }

    if (EditListClick != null)
        EditListClick(this, EventArgs.Empty);
}
A: 

Have you tried using an IValueConverter?

When you bind the ObservbleCollection to the custom ComboBox, set a custom IValueConverter. T His defines 2 methods, Convert and ConvertBack. The idea is, that you can convert types.

In this case, you can have your ObservableCollection<Sizes> and the binding converter will take that and convert it to the required type.

If you set the converter on the collection binding, you can convert to and from ObservableCollection<Sizes> and ObservableCollection<string>.

The other option is set the IValueConverter internally to the custom ComboBox and do the conversion from Sizes to string. Another option, in conjunction is to specific a itemtemplate the contains the binding and conversion.

HtH.

Alastair Pitts
Hmm... I hadn't thought of using a ValueConverter in that way before... I'll give it a try and get back with you. Thanks!
Brent
The only problem I have with this is that it would require me to write a bunch of ValueConverters for every type of combobox I use this on, as each one would contain a different type. This will be a large app, so I could have 30-50 different types of lists.I really think that reflection is the way to go, but I don't know how to implement it...
Brent
Hmmm, that does certainly change it. Will each of these objects in the lists be custom written?
Alastair Pitts
Yes, each of the lists will be custom written... they are all LINQ to SQL classes that have been generated from SQL Server tables. Technically your answer will work, so I'll mark it as answered, but I want to wait a bit to see if there are any other possible solutions.
Brent
Yeah, it's a tricky one. One thing you could *possibly* do, and i'll admit my LINQ-to-SQL is not super strong, make sure that all of the classes have an overridden .ToString(). You could then define an interface that has 1 method, FromString(), that load an object from string data. Inside of the value converter, just use the ToString() in the Convert and the .FromString() in the convert back.
Alastair Pitts
I think that will work. I might have to make another layer on top of the LINQ classes, but I think that's a good idea. Thanks for all of your help!
Brent
no probs! hope it all works out for you.
Alastair Pitts
+1  A: 

It sounds like you need to know about the list, but not the specific types. That is where the non-generic interfaces come in; try accessing the list as IList (which has all the indexer / Add / Remove etc), INotifyCollectionChanged (for notifications), etc.

In the more general case there is also IBindingList / IBindingListView / ITypedList etc, but I don't think you'll need those in this case.

Marc Gravell
I'm not sure I understand how to implement this. Could you provide a code example?
Brent