views:

91

answers:

5

I have this Database, not of my design but I have to work with it, that contains a table like so :

 id  |   Name     |  status  | ...
-----+------------+----------+------
 1   |  Product1  |  2       | ...
 2   |  Product2  |  2       | ...
 3   |  Product3  |  3       | ...
 ... |  ...       |  ...     | ...

The status property refers to an enum where

0 = Invalid
1 = Dev
2 = Activ
3 = Old

When I display this in a read-only datagridview, I would like the user to see the name of the enum (Dev, Activ, ...) or a description instead of the numeric value. The datagridview is bound to a datatable that comes from a DAL, again not of my design, so I can't really change the datatable. The only way I found how to do that is by listening to the datagridview.CellFormating event where I put this code:

private void dataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    if (e.ColumnIndex == 3) // column of the enum
    {
        try
        {
            e.Value = getEnumStringValue(e.Value);
        }
        catch (Exception ex)
        {
            e.Value = ex.Message;
        }
    }
}

This works fine, except that if I have around 1k (not that much) or more items, it takes forever... Is there a better way to do this ?

---Edit---
This works fine as it is, my problem is that if there are more than 1000 rows in the datatable, it takes for ever. The problem is that the CellFormating event fires for every column, even the ones that don't need it. Let say I display 15 columns and there is 1000 rows, then that event fires 15 000 time...

Is there a better way than using the CellFormating Event ? Or is there a way to add the CellFormating Event to only one Column ? Or what ?

A: 

Currently i doesn't quite understand what you mean with 1k items.

But what you have to do is just create the enum for yourself like:

public enum States
{
     Invalid = 0,
     [Description("In developement")]
     Dev,
     Activ,
     Old,
     ...
}

And in your your formatting event you call this function

/// <summary>
/// Gets the string of an DescriptionAttribute of an Enum.
/// </summary>
/// <param name="value">The Enum value for which the description is needed.</param>
/// <returns>If a DescriptionAttribute is set it return the content of it.
/// Otherwise just the raw name as string.</returns>
public static string Description(this Enum value)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }

    string description = value.ToString();
    FieldInfo fieldInfo = value.GetType().GetField(description);
    DescriptionAttribute[] attributes =
       (DescriptionAttribute[])
     fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

    if (attributes != null && attributes.Length > 0)
    {
        description = attributes[0].Description;
    }

    return description;
}

that way

e.Value = Enum.GetName(typeof(States), e.Value).Description;

All you have to do is checking that you have defined all the enum values that are possible and that you're operating on the correct column.

If you have 1000 values in your status column there is nothing that can help you to automate this in .Net, but it's a job that has to be done once. So it's not that hard.

Oliver
Sorry if I wasn't clear... There is only 6 different status, what I meant was when there is over 1000 columns in the datatable it get really slow... Also, the solution you provided is what I am currently doing with the getEnumStringValue method, but I want to know if there is a better way than the formatting event witch fires for every column, even the ones that don't need formating... I will edit my post to make things clearer
Pierre-Olivier Goulet
+3  A: 

I wouldn't do it on CellFormatting. I would attack the DataTable itself. I would add a row that has the type of the enum, and the loop through the table and add the values. Something like this:

    private void Transform(DataTable table)
    {
        table.Columns.Add("EnumValue", typeof(SomeEnum));
        foreach (DataRow row in table.Rows)
        {
            int value = (int)row[1]; //int representation of enum
            row[2] = (SomeEnum)value;
        }
    }

Then, in your DataGridView just hide the column that has the integer representation of your enum.

BFree
Yeah, I tought about that, but my problem is that the DataTable doesn't come from me, and I'm afraid of what could happen later in the code if I add a column... Also, would doing it like this really speed things up ?
Pierre-Olivier Goulet
Yes, it would speed things up because this would happen before you bind, and you wouldn't need to mess with all the formatting events. You can try cloning the DataTable before you do this, that way the original one doesn't get messed up. I still think it'd be faster. Give it a shot.
BFree
I will try that... clone the Datatable, add a new column, do any other formating, and then bind it to the DGV... Thanks
Pierre-Olivier Goulet
+2  A: 

Since you say this DGV is "read-only", you could read the data table into a list of a custom type that performs the conversion in-place.

You can get rid of the try-catch and your custom method and simply write:

e.Value = ((StatusType)e.Value).ToString();

If the value doesn't parse, it will be displayed as its integer value. That will speed things up a bit.

Tergiver
I used BFree's awnser but with your way of casting the value to get rid of the try..catch to speed things up. Thanks for your anwser
Pierre-Olivier Goulet
I just realized that the cast was not even needed.
Tergiver
Oops, you do need the cast if the type in the data table is integer. I need more coffee.
Tergiver
Yeah... coffee : the savior of us all !!!
Pierre-Olivier Goulet
A: 

You can use RowPostPaint event of DataGridView. You can do as following.

private void TestGridView_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
    {
      if(e.RowIndex == -1) return;
        TestGridView[YourColumnIndex, e.RowIndex].Value = YourEnumValue; // You can get your enum string value here.
    }

In this method you need to check for the value you want to update otherwise this will throw you into infinite loop for updating the row. Once value is updated you should avoid updating it again. This solution is only applicable if this is readonly cell.

I would suggest to go with BFree's solution, if that's not possible then you can think of this.

JPReddy
A: 

first of all u can check the row is within selected rows or not (I think u can save updated rows index).

also u can use delegates to Asynchronous update :

        delegate void Sample(object sender, DataGridViewCellFormattingEventArgs e);


        public static void SampleMethod(object sender, DataGridViewCellFormattingEventArgs e)
        {
            // update grid
        }
        static void Callback(object obj)
        {
            IAsyncResult result = obj as IAsyncResult;

            Sample s = result.AsyncState as Sample;
            s.EndInvoke(result);
        }

        void SampleCall(arguments...)
        {
            for (int i = 0; i < 5; i++)
            {
                Sample s = SampleMethod;
                s.BeginInvoke("Hello: " + i.ToString(), Callback, d1);
            }
            Debug.WriteLine("return from function call");
        }

I don't now is there a better event or not, call SampleCall to update ur grid.

SaeedAlg