如何将枚举值显示在数据网格视图列中

8
我有一个数据库,虽然不是我设计的,但我必须使用它,其中包含一个类似这样的表格:
 id  |   名称      |  状态    | ...
-----+------------+----------+------
 1   |  产品1     |  2       | ...
 2   |  产品2     |  2       | ...
 3   |  产品3     |  3       | ...
 ... |  ...       |  ...     | ...
状态属性是一个枚举类型,其中:
0 = 无效
1 = 开发
2 = 活动中
3 = 老产品
当我在只读的datagridview中显示时,我希望用户看到枚举名称(开发,活动中,...)或描述,而不是数字值。datagridview绑定到来自数据访问层(DAL)的datatable,同样不是我设计的,所以我不能真正改变datatable。我找到的唯一方法是通过监听datagridview.CellFormatting事件,在其中放置此代码:
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;
        }
    }
}

这个方法很好用,但是如果有大约1k甚至更多的项,它会花费很长时间...有没有更好的方法呢?
---编辑--- 这个方法本身很好用,我的问题是当数据表中有超过1000行时,它会花费很长时间。问题在于CellFormating事件会为每一列触发,即使不需要。比如我显示15列,并且有1000行,那么该事件就会触发15000次...
是否有比使用CellFormating事件更好的方法?或者是否有一种方法只将CellFormating事件添加到一个列中?或者其他什么方法?

@Oliver,K代表千;1K = 1,000。 - Brad
如果状态描述在单独的表中定义,那么您可以加入该表并将描述作为您要显示的数据的一部分返回。这是数据库应该做的事情,我觉得提出的解决方案涉及迭代数据库返回的结果很奇怪。当然,如果所需数据不在数据库中或者您无法自行插入它,则此评论变得无意义。 - Peter M
很遗憾,状态的值不在数据库中(这不是我的设计)。 - Pierre-Olivier Goulet
你能控制用于检索数据的SQL吗?因为如果你能做到这一点,你可以向结果集中添加一个列,利用case语句从状态列中的值生成枚举名称。 - Peter M
6个回答

6

我不建议在CellFormatting中进行操作,最好直接对DataTable本身进行处理。可以添加一个包含枚举类型的行,然后遍历整个表格并添加相应值。示例如下:

    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;
        }
    }

然后,在您的DataGridView中,只需隐藏具有枚举整数表示的列即可。


1
是的,我考虑过这个问题,但我的问题是DataTable不是由我创建的,如果我添加一列,我担心代码后面会发生什么事情...另外,像这样做真的可以加快速度吗? - Pierre-Olivier Goulet
2
是的,这样做会加快速度,因为这将在绑定之前发生,您不需要处理所有格式化事件。您可以在此之前尝试克隆DataTable,这样原始表就不会被弄乱。我仍然认为这样做会更快。试一试吧。 - BFree
我会尝试克隆Datatable,添加一个新的列,进行任何其他格式设置,然后将其绑定到DGV... 谢谢。 - Pierre-Olivier Goulet

3
您可以使用相应列的CellTemplate属性。因此,首先创建一个用于单元格模板的类,并覆盖GetFormattedValue方法。
public class VATGridViewTextBoxCell : DataGridViewTextBoxCell
{
    protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
    {
        Price.VATRateEnum r = (Price.VATRateEnum)(int)value;
        switch (r)
        {
            case Price.VATRateEnum.None: return "0%";
            case Price.VATRateEnum.Low: return "14%";
            case Price.VATRateEnum.Standard: return "20%";
            default:
                throw new NotImplementedException()
        }
    }
}

然后将其的新实例分配给列的单元格模板。请注意,更改不会立即生效,直到您刷新网格,这就是为什么我将其放入构造函数中的原因:

public frmGoods()
{
    InitializeComponent();
    this.sellingVATDataGridViewTextBoxColumn.CellTemplate = new VATGridViewTextBoxCell();
    this.buyingVATDataGridViewTextBoxColumn.CellTemplate = new VATGridViewTextBoxCell();
}

2

由于您说这个DGV是“只读”的,因此您可以将数据表读入一个自定义类型的列表中,在此过程中执行转换。

您可以摆脱try-catch和自定义方法,只需编写以下代码:

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

如果值无法解析,它将显示为其整数值。这样可以稍微提高速度。

我使用了BFree的答案,但是采用了您的强制转换值的方式来摆脱try..catch以加快速度。感谢您的回答。 - Pierre-Olivier Goulet
我刚意识到其实不需要进行强制类型转换。 - Tergiver
糟糕,如果数据表中的类型是整数,那么您确实需要进行强制转换。我需要更多的咖啡。 - Tergiver
在哪个事件中? - miguelmpn

1
你可以使用DataGridView的RowPostPaint事件。你可以按照以下步骤进行操作。
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.
    }

在这个方法中,你需要检查你想要更新的值,否则这将使你陷入无限循环来更新行。一旦值被更新,你应该避免再次更新它。这个解决方案只适用于只读单元格。
我建议使用BFree的解决方案,如果不可能的话你可以考虑这个。

0

目前,我不太明白你所说的1k项是什么意思。

但是,你只需要自己创建枚举即可,例如:

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

而在你的格式化事件中调用这个函数

/// <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;
}

那样

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

你需要做的就是确认定义了所有可能的枚举值,并且操作正确的列。

如果你的状态列有1000个值,那么在 .Net 中也没有什么可以帮助你自动化这个过程。但这是一项必须完成的工作,所以并不难。


抱歉如果我表达不清楚...只有6种不同的状态,我的意思是当datatable中有超过1000列时,它会变得非常缓慢...此外,您提供的解决方案是我目前使用的getEnumStringValue方法,但我想知道是否有更好的方法来处理格式化事件,因为它会为每一列触发,即使不需要格式化...我将编辑我的帖子以使事情更清晰。 - Pierre-Olivier Goulet

0

(简而言之) 就我个人而言,只需使用GridViewDataTextColumnFieldName="CurrentStatus"就可以正常工作(显示枚举名称而不是值)。

我搜索这个问题是因为我不知道要使用什么类型的GridViewData来正确显示我的枚举,而答案让我得出结论,这比实际上要困难。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接