0

I have a DataTable I want to use as a DataSource of a DataGridView, but with a twist: for reasons (below) I need to exclude a column from DataGridView (not just hide it, truly exclude), ideally preventing it from being generated alltogether. Theoretically, I can call Columns.RemoveAt at an appropriate moment (which is the DataBindingComplete event handler - docs), but that's too late for me (for reasons).

An obvious solution is to set AutoGenerateColumns = false and do it manually. Without having looked into the details of this, I fear I'd need to reinvent the wheel in this scenario (to keep the data bindings working etc).

My reasons for this whole esoteric are, there is huge legacy app, originally written in VB6, and there any byte-array column is just ignored by the MS Hierarchical Grid. I'm trying to emulate this behavior in a custom control derived from DataGridView, and most stuff works.

UPDATE/ANSWER Just set dt.Columns[0].ColumnMapping = MappingType.Hidden; (courtesy of https://stackoverflow.com/a/31282356/5263865)

Tymur Gubayev
  • 468
  • 4
  • 14
  • `DataBindingComplete` is not the event you're looking for, since it's raised each time you change the value of a Cell (which generates a binding event) -- Simply remove the Column right after you have set the DataSource, or - as you mentioned - add the Columns manually, if that's actually feasible (i.e., this is not a general use Control but a specialized / special-purpose one) -- The Types (or names) of the Columns to *ignore* (remove) could be a public property of the custom Control. – Jimi Sep 27 '22 at 13:32
  • Is data coming from a query to a database. Instead of using Select * replace the asterisk with the column names you want. – jdweng Sep 27 '22 at 13:40
  • @jdweng can't do that, that extra column is used elsewhere – Tymur Gubayev Sep 27 '22 at 14:07
  • @Jimi if you don't follow the docs and just remove it after setting the datasource instead of in an event, it breaks. You won't notice it immediately, but after working with the grid for a bit (via code) there are suddenly extra columns in the columns collection and so on. Also, this is a general use control, I can't just list specific columns I need. – Tymur Gubayev Sep 27 '22 at 14:11
  • You can clone the table and delete the columns. In doing this you would break the links between the database and the table so you would not be able to automatically update the database. – jdweng Sep 27 '22 at 14:20
  • Columns removed after the DataSource is set, may only *reappear* if you reset the DataSource. Since this is a general-purpose custom Control (not sure what its purpose is then), it's the code that uses it which is responsible for removing an unwanted Column (which makes the whole thing *mute*, in my view) -- If this is not the scenario, then write down a better description of it – Jimi Sep 27 '22 at 14:24
  • @jdweng I'm aware of that possibility, I do want to keep the data bindings though - i.e. in this scenario I'd need to invent some DataView that can filter columns out. – Tymur Gubayev Sep 27 '22 at 14:40
  • @Jimi would they only just reappear, I would be happy to remove them afterwards. But Microsoft in its unfathomable wisdom does strangest stuff sometimes. The general purpose control should show a datatable excluding `byte[]`-columns. – Tymur Gubayev Sep 27 '22 at 14:43
  • I don't know what *strangest stuff* you're referring to here. If you want your Custom Control to remove an unwanted Column of Type `Byte[]`, override `OnDataSourceChanged()` and remove Columns that contain that type of data, if any – Jimi Sep 27 '22 at 14:48
  • @Jimi doing it in `OnDataSourceChanged` is almost the same as doing it immediately after setting the `DataSource`, which I tried before and it breaks. I don't have a minimal example right now, so please just trust me when I say that remark by Microsoft in the documentation for both `Remove` and `RemoveAt` is not for shit and giggles, it is important and it is correct. – Tymur Gubayev Sep 27 '22 at 15:01
  • It's not the same thing: in case of a Custom Control, this automates the procedure, hence no other code anywhere else needs to handle this use-case -- When a Column is removed from the `DataGridViewColumnCollection` (note that you can also dispose of it), the Control has no way to add it back, unless - as mentioned - you reset the DataSource; in this case, the Columns are regenerated -- If you have a sample Project that negates this (excluding the described conditions), by all means post it – Jimi Sep 27 '22 at 15:24

2 Answers2

0

In modern programming, there is a tendency to separate your data (=Model) from how the data is shown to the operator (=View). An adapter class (=ViewModel) is needed to connect the Model to the View. Abbreviated this gives MVVM. If you are not familiar with this concept of separation, consider to do some background reading.

Your Data is in a DataTable. You didn't mention what kind of items are in the DataTable. To ease the discussion I'll assume that the DataTable contains a sequence of Products.

class Product
{
    ...
}

You have methods to put Products in the DataTable and to Access them. Something like:

interface IProductRepository
{
    IEnumerable<Product> AllProducts {get;}
    Product GetProductById(int productId);

    void AddProductAtEnd(Product product);
    void ReplaceProduct(Product product, int index);
    ...
}

etc. The exact methods are not important for the answer. What I try to explain is that when using this interface you hide that the Products are stored in a DataTable. This give you the freedom to change where your Products are stored: in a DataBase? A List? or maybe a file, or even the internet.

I use a generic term repository (warehouse) for something where you can store items, and later retrieve them, replace them with other items or remove them from the repository. This can be a DataTable, or a database, or maybe a file, a Dictionary, whatever. The nice thing is that I've hidden that the Products are in a DataTable.

The DataGridView

When accessing the data in a DataGridView, people tend to fiddle directly with the DataGridViewCells and DataGridViewRows.

Well, don't!

Use DataBinding instead.

In almost all forms that have DataGridViews I have the following properties:

BindingList<Product> DisplayedProducts
{
    get => (BindingList<Product>)this.DataGridView1.DataSource;
    set => this.DataGridView1.DataSource = value;
}

Product CurrentProduct => this.DataGridView1.CurrentRow as Product;

IEnumerable<Product> SelectedProducts => this.DataGridView1.SelectedRows
    .Select(row => row.DataboundItem)
    .Cast<Product>();

Back to your question

for reasons (below) I need to exclude a column from DataGridView (not just hide it, truly exclude), ideally preventing it from being generated

If I read your question literally: you don't want to generate the DataGridViewCells that are in columns that are excluded.

This does not influence the Product that each row represents, it only influences the display of these Products. For example: even though each Product has an Id, you might want not to Display this Id.

The most easy thing for this is to use visual studios designer for this. Instead of defining the columns with the DataBinder, just add the columns one by one, and use the properties of each column for the name of the column, the name of the property that it has to show, the format that is used to show the value.

Code will look like this:

DataGridView dataGridView1 = new DataGridView();

// Column to show Product.Id
DataGridViewColumn columnProductId = new DataGridViewColumn();
columnProductId.HeaderText = "ID";
columnProductId.DataPropertyName = nameof(Product.Id);

// Column to show Product.Name
DataGridViewColumn columnProductName = new DataGridViewColumn();
columnProductName.HeaderText = "Name";
columnProductName.DataPropertyName = nameof(Product.Name);

// etc. for all columns that you want to show

Note: in DataPropertyName you store the name of the Property that must be shown in this column. I use the keyword nameof, so if later the name of the property changes, this won't be a problem.

Of course, if you want some special formatting, for example for numbers or dates, you need to set the proper properties as well. This can also be done in visual studio designer.

Once that you have defined your columns, add them to the DataGridView.

To Display the Products is a two-liner:

IDataTableProducts ProductRepository {get;} // initialize in constructor

void ShowProducts()
{
    IEnumerable<Product> productsToDisplay = this.ProductRepository.AllProducts;
    this.DisplayedProducts = new BindingList<Product>(productsToDisplay.ToList());
}
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • I'd really like to keep the binding to the `DataTable`, which I think isn't there anymore in your solution. But more tragically for me, I need a general solution that works for any `DataTable`, not just one holding `Product`s. – Tymur Gubayev Sep 29 '22 at 08:43
0

I stumbled upon an answer: https://stackoverflow.com/a/31282356/5263865

Setting the column.ColumnMapping = MappingType.Hidden does exactly what I needed: the column isn't autogenerated anymore.

DataTable data;
data.Columns[0].ColumnMapping = MappingType.Hidden;
Tymur Gubayev
  • 468
  • 4
  • 14