DataBinding and optional ComboBox objects

Posts   
 
    
Waveslam
User
Posts: 18
Joined: 20-Nov-2006
# Posted on: 20-Nov-2006 22:06:54   

Hi I am currently evaluating LLBLGenPro and using it to create a simple Windows Forms UI to display information from a single table in a DataGridView. Most of the columns are FKs to other "lookup" tables.

To allow the user to filter the data displayed, I also have a number of ComboBox objects, one for each FK column displayed in the grid. The ComboBox objects are simply populated using the generated Entity Collection for the relevant lookup table, which is then databound to the ComboBox to show all the available values. The idea is the user then selects values in the comboboxes and the grid is refreshed to only display rows with that value in the corresponding column. (Similar to thread http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=6363)

Originally I was using some hand-coded data access code to populate the comboxes. One of the advantages of this was that I was able to explicitly add a "<All>" entry as item 0 in each Combobox, allowing the user to choose "<All>" to specify they dont want to filter on that column i.e. rows with any value for that column will be displayed.

However, I am now using LLBLGenPro-generated code and databinding to populate the ComboBoxes. Since the comboxes are databound, the SelectedItem() property always returns a value (even if its just the first one in the list), effectively meaning that a value is always "selected" for each combobox, resulting in a filter being applied for every single column.

What I need is some means of including the type of "<All>" entry I previously had (or even just a blank entry) as the first in the list. However: - I cant manually add entries to the ComboBox any more: once you set the .DataSource property of the ComboBox, any attempt to manually tinker with items causes an exception. - I cant just add a dummy "<All>" entry to the Entity Collections I am using for databinding: Even if I could avoid this resulting in a new row being created in the DB, I couldn't always get the text to display "<All>' e.g. for numeric columns etc - Similar to a solution offered in thread 6363 above, I am thinking I could somehow alter the generated SELECT statement that populates each ComboBox Entity Collection to include some sort of UNION statement so that they all have a "<All>" entry as item 0. However, that seems like a nasty hack and even then I have no idea how I would go about doing that.

So, is there any way to accomplish what I want, without having to resort to adding a corresponding 'Filter On This Field' checkbox for every combobox?

I am using SelfServicing, LLBLGenPro version 2.0.0.0 DEMO, .NET 2.0, C#, SQL Server 2000.

Thanks heaps Cheers Brett

jmeckley
User
Posts: 403
Joined: 05-Jul-2006
# Posted on: 20-Nov-2006 23:56:04   

I suggest seperating your DB values from interface. The <All> option is just a flag for the UI/BL to not apply a filter (or apply a filter for the specific value specified). I develop for web apps not desktop apps, but I would think something like this would work.

On Show Form


this.dropdown.datasource = myentitycollection;
this.dropdown.items.insert(0, new listitem("<All>", "", true));

In your event to get results (search button most likely)


if (this.dropdown.selectedindex > 0)
{
      // create filter based on the dropdown
      // mypredicatebucket.predictexpression.add(myentity.myfield = this.dropdown.selectedvalue);
}

Waveslam
User
Posts: 18
Joined: 20-Nov-2006
# Posted on: 21-Nov-2006 00:42:06   

Hi Jason, Cheers fgr the reply and the suggestion. Sadly, I already tried to do exactly what you suggest but it doesn't work :-( Problem is that once you set the .Datasource property on the ComboBox, you cannot alter the items collection yourself. If you look up the MSDN doco for "ComboBox.DataSource Property" it includes the remark "When the DataSource property is set, the items collection cannot be modified."

After some more work here, the only way I can think of to make it work is: 1. Get the Entity Collection using normal LLBLGenPro-generated code 2. Create my own collection of custom objects, each of which CONTAINS an Entity object from the Entity Collection 3. Add an initial '<All>' entry to my custom collection 4. Bind my CUSTOM collection to the control (ie. rather than binding the Entity Collection)

Seems a bit messy but dont think I have much choice.

Cheers Brett

bclubb
User
Posts: 934
Joined: 12-Feb-2004
# Posted on: 21-Nov-2006 02:34:12   

Can you not just add a new All entity to the LLBLGen collection and then bind to that?

Waveslam
User
Posts: 18
Joined: 20-Nov-2006
# Posted on: 21-Nov-2006 02:46:45   

Yep, tried that too. Trouble is that the Entity Collections are strongly typed, containing objects that map directly to each DB table e.g. the ProductEntityCollection contains objects of type ProductEntity, which maps to the Product table in the DB. So, I cant just add a "<All>" string object to the Entity Collection as its not the right type.

Instead I can only add a new ProductEntity Object, essentially creating a new dummy table row. First problem is that I would need to prevent this new dummy row from being treated as a new row that needs to be saved to the DB.

Bigger issue is how do I get the string "<All>" to appear in the ComboBox: the .DisplayMember property of the ComboBox is mapped to a particular column/property of the ProductEntity object e.g. Product.Description. If the displayed column/property is a string column, then I am OK: I can just set this column to "<All>" in my dummy row. However, if the displayed column happens to be a number or a date for a partiuclar Entity Object, then I am screwed :-(

bclubb
User
Posts: 934
Joined: 12-Feb-2004
# Posted on: 21-Nov-2006 03:29:55   

This may work.


nameCollection.GetMulti(null);
NameEntity[] names = new NameEntity[nameCollection.Count];
nameCollection.CopyTo(names, 0);
comboBox1.Items.AddRange(names);
comboBox1.DisplayMember = "Name";
comboBox1.ValueMember = "KeyId";
comboBox1.Text = "All";

Waveslam
User
Posts: 18
Joined: 20-Nov-2006
# Posted on: 21-Nov-2006 04:06:49   

Yep, that would work I guess, but a couple of snags: 1. Once the user chooses some entry in the list, there is no way to go back to the "All" entry, since that entry never really existed. 2. EVen if the user doesn't make any change to the COmboBox and its still showing "All", calling .SelectedItem will still return the first object from the Entity Collection, even though its not currently displayed. So, to make it work I would need to check the .text property when looking for "All".

Anyway, I have now got it working pretty much as I described above: 1. Use the LLBLGenPro-generated DAL code to get the Entity Collection eg. ProductEntityCollection 2. Create my own collection of custom objects, each of which can hold a single Entity and gives me control over the displayed text e.g.

         private class FilterComboBoxItem
        {
            public ProductEntity productEntity; // Holds the row object return from the DAL
            public String text; // Set when loading the collection
            public override String ToString()
            {
                return text;
            }
        };
  1. Add the initial "<All>" entry to the custom collection:
                    item = new FilterComboBoxItem();
                    item.productEntity = null;
                    item.text = "<All>";
                    itemList.Add(item);
  1. Load the custom collection with a FilterComboBoxItem object for each object in the Entity Collection:
                foreach (ProductEntity productEntity in ProductEntityCollection)
                {
                    item = new FilterComboBoxItem();
                    item.productEntity = productEntity;
                    item.text = productEntity.Description;
                    itemList.Add(item);
                }
  1. Bind the combobox to the custom collection instead of the Entity Collection
  2. You can then handle what the user selected with:
                    FilterComboBoxItem selected = (FilterComboBoxItem)myComboBox.SelectedItem();
                    if (selected.productEntity == null)
                    {
                             // They chose the initial "<All>" entry
                    }

Cheers B

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39882
Joined: 17-Aug-2003
# Posted on: 21-Nov-2006 09:12:31   

This is indeed a problem with winforms databinding: in asp.net the combobox has a flag to allow items to be added by code as well as databound data, but in winforms, it's either the data in the datasource or the data you add to the combobox.

In the designer I had the same problem in the sequences combobox in the entity editor, fields tab: I bind the collection of sequences to it but the empty option also has to be present. This means that a dummy object had to be inserted. Luckily the collection is an arraylist so I could add a dummy object for this purpose, but in your case that's not possible.

What I would do is use the checkbox approach. It might sound a lot of work, but it will make the gui more cleaner. When I wrote the sequence combobox code I didn't think of the checkbox but now I read about it I think it's a more cleaner approach. Your code gets cleaner (just check the checkbox to see if you have to set something) and you don't have to check for dummy objects with no meaning in the real data plus you can use the entitycollections you already had. simple_smile

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 21-Nov-2006 10:50:50   

Here is another possibility (not tested this though) without copying the source data....

http://www.codeproject.com/useritems/AddItemToBindedComboBox.asp

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39882
Joined: 17-Aug-2003
# Posted on: 21-Nov-2006 11:41:19   

simmotech wrote:

Here is another possibility (not tested this though) without copying the source data.... http://www.codeproject.com/useritems/AddItemToBindedComboBox.asp

That's the same approach of creating a dummy instance and set the display member's property value to the string '<All>', if I understand his AddItem routine correctly (which simply is a general routine working which works with all lists. )

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 21-Nov-2006 12:26:25   

Otis wrote:

simmotech wrote:

Here is another possibility (not tested this though) without copying the source data.... http://www.codeproject.com/useritems/AddItemToBindedComboBox.asp

That's the same approach of creating a dummy instance and set the display member's property value to the string '<All>', if I understand his AddItem routine correctly (which simply is a general routine working which works with all lists. )

I was hoping it would work on any list rather than having to create a new custom list and custom class and copy items into it.

What is really needed is a general-purpose list wrapper that takes any list in its constructor along with a display string. Its Count and indexer reflect the underlying list plus the virtual element 0 with the indexer just subtracting 1 from the index before returning the wrapped lists's element at that index. It could also be a live list ala EntityView listening to any events supported by the wrapped list and forwarding them on. Then you just bind to the list wrapper rather than the list and the combobox is none the wiser.

Not actually tried any of this you understand but I can't see why it wouldn't work.

Cheers Simon

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39882
Joined: 17-Aug-2003
# Posted on: 22-Nov-2006 10:43:32   

That might work but it's a lot of work as well. You could almost better subclass the combobox and add an ownerpainted selection at the top.

Frans Bouma | Lead developer LLBLGen Pro