简体   繁体   中英

How do I use variable names as a column header for DataGridView when the variable name is only known during runtime?

Preface

Not sure if it matters but I'll explain how my project is structured. It has three layers:

  • native C++: Purpose is to read multiple files and perform calculations. The outputs are doubles and vectors.

  • C++/CLI Wrapper: Communication layer between my native C++ program and the C# GUI. It also converts my native C++ vectors into Generic Lists.

  • C#: This layer is used to design the GUI and basically just to present the results in charts and tables.


My goal

My goal is to populate a DataGridView with my parameters from class Evaluation as column and the values of each parsed file as a row.

What I have done so far is:

  • Created the class Evaluation with each parameter as property

     public ref class Evaluation { /.../ constructor exists System::Collections::Generic::List<double> ^GetTAFValues(); System::Collections::Generic::List<double> ^GetTAFTemps(); property double parameterA { double get() { return getParameterA(); } } property double parameterB { double get() { return getParameterB(); } } /.../ } 
  • For each parsed file (using a foreach loop) I'd create a new Evaluation object and add that object into a List called results of type List<Evaluation>

  • After that I'd bind the List<Evaluation> to the DataGridView by doing dataGridView1.DataSource = results;

This works pretty good so far but only for Parameter A and B. What I am missing is my parameter TAF.

TAF consists of a temperature and a value. For example:

List<double> TAFValues =  new List<double> {1, 2, 3, 4};
List<double> TAFTemps = new List<double> {-30, -10, 25, 50,};

What I need is to add columns called TAF-30, TAF-10, TAF25, TAF50 with the values attached from TAFValue . These four parameters can be used for all remaining files.

I can't add TAF as property though because I don't know how many temperature steps are being used during compile time and what the property name (read: which temperature) would be.

Questions/Ideas:

  • How do I add columns that include the temperature strings as HeaderText?

  • Can I somehow combine my List<Evaluation> with my List<double> and use it as a DataSource? I know you'd have to make a List but I'm not really able to declare any class variables.

  • Am I able to manually insert the TAF columns + values for each row/file AFTER the DataSource has been declared with dataGridView1.DataSource = results; ?

  • Are there any other data structures I could use as a Datasource instead of a List, that could possibly help me out?

I managed to solve my problem by using a Dictionary<string, object> and DataTable . I think in order to solve my problem using class properties I'd have to implement Reflections in order to define properties during runtime.

This route doesn't require any reflections. The temperature values for TAZ is known once I run Calculate() for the first time. The parameters are applicable for all remaining files.


  • First step: Instead of storing my parameters as class properties and bind it to a DataGridView , I decided to use a Dictionary<string, object> to store my parameters instead. Since Dictionary stores a string I was able to declare the dynamic column names by doing "TAZ" + temperature.ToString() ( temperature is a vector<int> ).

     // C++/CLI Wrapper file using namespace System::Collections::Generic; Dictionary<String^, Object^>^ EvaluationCLI::GetParameters() { Dictionary<String^, Object^>^ d = gcnew Dictionary<String^, Object^>(); List<int>^ temps = GetTAFTemps(); List<double>^ TAZ = GetTAZValues(); d->Add("Parameter A", GetParameterA()); d->Add("Parameter B", GetParameterB()); for (int i = 0; i < temps->Count; i++) { d->Add("TAZ_" + temps[i].ToString(), TAZ[i] ); } return d; } 
  • Second Step: Instead of using properties from Evaluation , and add these to a List<Evaluation> , I would use a Dictionary<string, object> and populate a DataTable as a DataSource .

     // C# /.../ var dt = new System.Data.DataTable(); foreach (string path in listBox.Items) // file(paths) are stored in a ListBox { Evaluation eval = new Evaluation(path); // create Evaluation for that file eval.Calculate(); // does calculations and stores results inside the native C++ vectors/doubles Dictionary<string, object> dict = eval.GetParameters(); // retrieve the parameters from native C++ as a Dictionary if (table.Columns.Count < 1) // only add columns when DataTable is empty { foreach (var c in dict.Keys) table.Columns.Add(new DataColumn(c)); // add new column for each parameter string } DataRow row = table.NewRow(); // create new row for each file int i = 0; foreach (var value in dict.Values) { row[i] = value; // inserts values column by column i++; } table.Rows.Add(row); // add the populated row to the DataTable } dataGridView1.DataSource = table; // bind DataTable as a DataSource /.../ 

Results:

  • A DataGridView with runtime parameter names as columns
  • Each Row represents a new file/evaluation
  • Row values match the parameter columns

     |---------------------|------------------|------------------|------------------| | Parameter A | Parameter B | TAZ_10 | TAZ_50 | |---------------------|------------------|------------------|------------------| | 12 | 26 | 96 | 89 | |---------------------|------------------|------------------|------------------| | 85 | 34 | 91 | 72 | |---------------------|------------------|------------------|------------------| | ... | ... | ... | ... | |---------------------|------------------|------------------|------------------| 

I had the same issues here's my solution... passing in a list of header names

    public void dgv_SetHeaderNames(DataGridView dgv, List<string> colNames, bool withColNum = false)
    {
        foreach (DataGridViewColumn dgvCol in dgv.Columns)
        {
            int currCol = dgvCol.Index;
            string colText = "";
            if (currCol >= colNames.Count)
            {
                // if there are more columns than name we will use the column number, anyway.
                colText = currCol.ToString();
            }
            else
            {
                if (withColNum == true)
                {
                    colText = currCol.ToString() + " - " + colNames[currCol];
                }
                else
                {
                    colText = colNames[currCol];
                }
            }
            dgv.Columns[currCol].HeaderText = colText;
        }
    }

The only problem I've run into is in VS2017 I get an IDE0028 for my initialization.

But, as you can see I reuse the list for multiple DataGridViews in my project...

        List<string> colNames = new List<string>();
        colNames.Add("Desc");
        colNames.Add("Freq");
        colNames.Add("Date");
        colNames.Add("Amount");
        colNames.Add("Pay From");
        dgv_SetHeaderNames(dgvDebits, colNames);
        dgvDebits.ColumnCount = colNames.Count;
        colNames[4] = "Type";
        dgv_SetHeaderNames(dgvIncome, colNames);
        dgvIncome.ColumnCount = colNames.Count;

        colNames.Clear();
        colNames.Add("Key");
        colNames.Add("Description");
        colNames.Add("Date");
        colNames.Add("Freq");
        colNames.Add("Amount");
        colNames.Add("Pay From");
        colNames.Add("USAA Checking");
        colNames.Add("Amazon VISA");
        dgv_SetHeaderNames(dgvWorking, colNames);

I think I prefer the simplicity of VS2010!

Another nice little trick I've learned is using a list to populate a given row...

dgvWorking.Rows.Add(wList.ToArray());

I haven't found any examples to do the same with Headers though...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM