6

I'm facing a problem that I don't know how to solve and am hoping the community can help.

I'm writing an app that manages "Lead" objects. (These are sales leads.) One part of my program will import leads from a text file. Now, the text file contains lots of potential leads, some of which I will want to import and some of which I won't.

For ease of programming (and use), I'm parsing the text file into a List<Lead> object, and using a DataGridView to display the leads by setting the DataSource property of the DataGridView.

What I want to do is add a column to the grid, called "Import," with a checkbox that the user can check to indicate whether or not each lead should be imported.

My first thought is to derive a class from Lead:

public Class LeadWithImportCheckbox : Lead
{
   bool bImport = false;

public bool Import { get { return bImport;} set { bImport = value;} } }

However, the parsing engine returns a list of Lead objects. I can't downcast a Lead to a LeadWithImportCheckbox. This fails:

LeadWithImportCheckbox newLead = (LeadWithImportCheckbox)LeadFromParsingEngine;
This is an invalid cast.

The other option I see is to create a constructor for LeadWithImportCheckbox:

public LeadWithImportCheckbox(Lead newlead)
{
  base.Property1 = newlead.Property1;
  base.Property2 = newlead.Property2;
  ....
  base.Property_n = newlead.Property_n;
}
This is problematic for two reasons. One, the Lead object has several dozen properties and writing this constructor is a PITA.

But worse, if I ever change the underlying structure of Lead, I need to remember to go back and change this constructor for LeadWithImportCheckbox. This is a danger to my code maintenance.

Is there a better way of accomplishing my goal?

2
  • Just a random thought: is it possible that you might want to track if a lead was "imported" or not (hand-entered)? In other words, might it be feasable to make imported a permanent property of Lead?
    – Adrien
    Commented Jul 15, 2009 at 19:02
  • I don't think that would be something I'd want to track. I do see the problems that this would solve but it's really not part of what I really want to accomplish. Commented Jul 15, 2009 at 19:18

11 Answers 11

7

or, to avoid the PITA aspect, use reflection... (try this...)

EDIT: use property, not Field as I had originally written...

public class NewLead : Lead
{
    public bool Insert;
    public NewLead(Lead lead, bool insert)
    {
        Insert = insert;
        foreach (PropertyInfo pi in typeof(Lead).GetProperties())
            GetType().GetProperty(pi.Name).SetValue
               (this, pi.GetValue(lead,null), null);
    }
}
3
  • I think that ultimately I like the reflection approach. (I just lack the understanding.) In your sample code, I get a zero-length array from typeof(Lead).GetFields() Commented Jul 15, 2009 at 19:49
  • I believe this is the solution I am going to settle on. Thanks, Charles! The code that I've implemented and that appears to work is: <code><pre> public LeadWithImportCheckbox(Lead lead):base() { Type LeadType = typeof(Lead); PropertyInfo[] properties = LeadType.GetProperties(); foreach (PropertyInfo fi in properties) { object value = fi.GetValue(lead, null); fi.SetValue(this, value, null); } } </pre></code> Commented Jul 15, 2009 at 20:55
  • Fantastic solution. I like not having to manually update my constructor if the base class changes.
    – jocull
    Commented Apr 9, 2011 at 23:48
4
public class LeadListItem
{
    public Lead Lead { get; set; }
    public bool ShouldImport { get; set; }
}

i.e. don't copy the Lead object's contents, just store a reference to it in a new LeadListItem object, which adds extra info "outside" the original object.

If you want the properties of Lead to appear in the grid, there is almost certainly a way of doing that. Why not ask that question, instead of downvoting me for telling you the right answer to this question!

7
  • The problem with this solution is that the members of Lead don't show up in the DataGridView. Commented Jul 15, 2009 at 19:17
  • @The Demigeek Sure you can, but that's a different question.
    – exclsr
    Commented Jul 15, 2009 at 19:22
  • GreenReign, how is that a different question? The original question was about displaying a Lead with a checkbox in a DataGridView. A solution that doesn't include the members of Lead in the DataGridView isn't a solution to the problem. Commented Jul 15, 2009 at 19:29
  • 2
    The correct solution to your problem is to model the data sensibly with objects, and then tell the grid how to present the model so it displays all the parts of it you need. My answer is the correct answer to the first part, now you need to figure out the second part. That second part is a separate question. Commented Jul 15, 2009 at 19:35
  • 1
    To state it succinctly, "prefer composition over inheritance" Commented Jul 15, 2009 at 19:36
3

A couple options you might have missed:

  • You could update the Lead object itself to have an Import property (that defaults to false).
  • You could have your "ImportLead" object treat the Lead as payload (even make it generic, if you want), so you don't need the big constructor.
  • Build a new Lead object list or enumerable that only contains the objects you want to import in the first place.
3
  • I would go with creating an Import property on the Lead class itself
    – Stan R.
    Commented Jul 15, 2009 at 19:14
  • Much more eloquently stated than my clumsy attempt above, but pretty much what I was trying to say.
    – Adrien
    Commented Jul 15, 2009 at 19:18
  • 4
    I would have to disagree with Stan. There is no reason for the import property on the Lead, it is completely unrelated to the Lead and is pollution of your domain object. It's only a UI artifact and hence it should not alter your domain objects in anyway. Commented Jul 15, 2009 at 19:19
1

You can only downcast, if the object to be downcast is really an object of that type.

An easier way to solve your problem would be to have a DisplayLead class, such as:

  public class DisplayLead {
      Lead lead;
      bool bImport;
  }

which would also help you separating stored data from their representation in a GUI.

2
  • If I do this the properties of lead don't show up in the DataGridView. Commented Jul 15, 2009 at 19:31
  • 1
    With the information you provided in response to my post, this is the optimal solution. What you need to do is turn off auto generate columns and need to manually create the columns and use DataBinding expressions to reach the actual container items so you can read inside complex objects. If you look up C# Databinding Eval or similar in google you should see how to do it. Commented Jul 15, 2009 at 19:38
1

What you want to do is display the checkbox column on your grid and not have it related at all to your Lead objects. You use the marked columns (and possible the original List) to build a new set of List which will be your import list.

Then handle whatever you wish to do with the newly created List.

Edit: One thing to be careful of when working with lists is the fact every class object is actually only a pointer to the class so if you work with the original list and do something like:

List<Lead> Importable = new List<Lead>();

for(int i=0, i++, i<viewGrid.Count)
    if(viewGrid[i].CheckedColumn.Checked)
        Importable.Add(OriginalList[i]);

That objects will exist in both lists and if you edit data of a Lead on either list both will be changed.

1
  • I did originally pursue this solution. It works appropriately if I use a List<Lead>. However, if I use a SortableBindingList<T> wrapper, then the checkmarks don't stay with the Leads when the DataGridView is sorted. Since the text file will have ~1,000 potential leads, sorting is important. (I do apologize for not including this in the original problem statement; it's hard to know how much info is enough but not too much when simplifying for a post.) Commented Jul 15, 2009 at 19:22
1

I cannot downcast to something it is not. If the object was instantiated as a Lead, then it can't be downcast to any derived class. If it were instantiated as a LeadWithImportCheckbox and then returned to your code as Lead, then you can downcast it.

Protip: Check type at runtime with is operator.

1

There are many ways to do this, but the "right" way pops out because of what you said, here:

For ease of programming (and use), I'm parsing the text file into a List object, and using a DataGridView to display the leads by setting the DataSource property of the DataGridView.

What I want to do is add a column to the grid, called "Import," with a checkbox that the user can check to indicate whether or not each lead should be imported.

Your Lead object stands well on its own, and you want to attach some metadata to it -- you don't want to create another Lead classification (i.e. the LeadWithImportCheckbox class).

So, the best approach in your case is to have a class like so:

public class LeadInfo 
{
    private Lead lead;
    private bool shouldImport;

    public LeadInfo(Lead lead)
    {
        this.lead = lead;
        this.ShouldImport = false;
    }

    public bool ShouldImport 
    { 
        get { return shouldImport;  }
        set { shouldImport = value; }  
    }
}

This will scale well when you want to add more metadata to your list, like if you want to send yourself email reminders about them every week.

1
  • But if I do this, the Lead properties don't display on the DataGridView. Commented Jul 15, 2009 at 19:25
1

I've seen the correct solution listed so many times I feel like a heel posting it again, but the best way to approach this is to write a wrapper for the Lead object that includes the import flag.

If the properties of the Lead object don't appear in the GridView because you're databinding to the object, then write passthrough properties that mirror the Lead properties on the wrapper object.

The issue is that you want something displayed to the user that isn't an inherent part of the data model. The answer is to wrap the data before presenting it to the user so you can control what they see without changing the underlying model.

If you're concerned that the Lead object will change so many times in the future that changes to the wrapper will be cumbersome, you could look into dynamic code generation based on the Lead object that will automatically generate a wrapper object with the same fields as the Lead object plus the import flag. Though frankly, that's a lot more work than you'll probably need for something as straightforward as this.

2
  • I don't believe this is the optimal solution. If the structure of the underlying Lead object changes, then I need to go back and edit the code that binds the properties of the Lead to the DataGridView. Inheritance solves this problem. No matter what changes are made to Lead, if LeadWithImportCheckbox inherits from Lead, then I don't need to make any code changes. And 'LeadThatIMightWantToImport' Is-A 'Lead'. Inheritance is appropriate. I believe that using Reflection, as Charles suggested, is going to be the right way to create a LeadWithImportCheckbox from a Lead. Commented Jul 15, 2009 at 20:06
  • The reflection approach would alleviate the issue of 'underlying Lead object changes', but like I said, if your Lead object is changing that often, there are deeper issues than your coding practices. Based on your response to Charles' solution, I'm not sure reflection is going to be the right way to go for you either. Also note that using reflection in that way will impose a serious performance penalty (could be an issue if you start having many thousands of leads).
    – genki
    Commented Jul 15, 2009 at 20:33
0

As a quick and dirty solution, you can create your 'checkbox' object as a different object that contains an instance of Lead.

public GridLead {
   public bool Import { get; set; }
   public Lead Lead { get; set; }
}

This way you can easily add more 'grid' properties to this object, while still always retaining a reference to the Lead details without hardcoding property cloning into it.

1
  • If I do this the properties of lead don't show up in the DataGridView. Commented Jul 15, 2009 at 19:34
0

Recommend you try modifying (upgrading) your imported lead objects.

Try starting with the examples here...

0

If your Lead class had a copy constructor (e.g. "Lead(Lead otherLead)"), LeadWithImportCheckbox would inherit that and you could just call the base Lead constructor in the LeadWithImportCheckbox constructor - hence no need for LeadWithImportCheckbox to be aware of the details of Lead.

Not the answer you're looking for? Browse other questions tagged or ask your own question.