0
votes

I have a Winform application which is almost working, but is becoming increasingly complex around the way users update and insert data via DataGridViews, and how this is fed back into the List in the background. Currently both DataGridView's are populated from a single List, and when a user updates a cell, the List is then updated from the Grid. I'd prefer to use the List as a DataSource.

Very simplified dataset (Server, Function, Proces) - P.S. I can't change my data set:

Server1, KeepAlive, SQLService
Server1, KeepAlive, AnotherProcess
Server1, Kill, RogueProcess
Server2, KeepAlive, SQLService
Server3, KeepAlive, SQLService

DataGridView1 has 1 column containing Distinct server names and DataGridView2 has 2 columns containing all functions and processes for the selected server in DataGridView1. A user can edit any cell and currently I keep track of each change and reflect it back into the List, then refresh the Grids. I'd prefer to handle this using the DataGridView DataSource object.

My question's are: What is the most suitable data source setup (I.e. a List of objects)? How do I filter differently from the data source into the Grid's?

So far I've tried the following. Create a Config class:

class Config
{
    [DisplayName("Server")]
    public string server { get; set; }

    [DisplayName("Function")]
    public string function { get; set; }

    [DisplayName("Process")]
    public string checkType { get; set; }
}

Create a List which contains these Config objects:

List<Config> configurations = new List<Config>();

Assign the List as a Data Source:

dataGridView1.DataSource = configurations;

This displays all three columns of data as expected. How can I a) show only the Server column and b) show only a distinct list?

I'm guessing with DataGridView2 I can use RowFilter to display only the selected server:

(dataGridView2.DataSource as DataTable).DefaultView.RowFilter = ?

Thanks in advance for any help!

Edit

I have tried using LINQ:

dataGridView1.DataSource = configs.Select(o => new { Server = o.server }).ToList();

This worked, but my grid was read-only, so I used a custom view model too:

dataGridView1.DataSource = configs.Select(o => new ServerView() { Server = o.server }).ToList();

This is displaying exactly how I want, but when I edit a cell, the change is not reflected in the List. Can LINQ be used with DataSource like this?

Edit 2

Using stefankmitph's examples I can filter via a SortableBindingList:

SortableBindingList<Config> sortableBindingList = new SortableBindingList<Config>(configs.Where(o => o.server == "Server1").ToList());
BindingSource bindingSource = new BindingSource(sortableBindingList, null);
dataGridView1.DataSource = bindingSource;

This resolves the issue for DataGridView2 which needs to be filtered based on what is selected in #1. However, I still can't work out how to only display certain columns in the grids. #1 should have only the servers and #2 should have the remaining two columns. A select in the LINQ query wouldn't work because I'm dealing with a Config object ... right?

1

1 Answers

1
votes

Over the years I've struggled with a lot of problems with the DataGridView. Here's just what for me is 'best practice':

1a) I most often attach my data (List) to a SortableBindingList (there's a lot of examples out there here, here and here. Take what fits you.)

Assuming there's a List configurations;

dataGridView.DataSource = new SortableBindingList<Config>(configurations);

now your DataGridView is sortable.

1b) When it comes to filter the DataSource there's a bunch of options. As far as I'm concerned the filtering only applies to the BindingSource with a DataTable attached as DataSource

Assuming there's a DataTable dataTableConfigurations:

BindingSource bindingSource = new BindingSource(dataTableConfigurations, null);
dataGridView.DataSource = bindingSource;
bindingSource.Filter = "Server = 'Server3'";

but I'm quite sure that this won't work with a List of objects as DataSource.

What you can do:

SortableBindingList<Config> sortableBindingList = new SortableBindingList<Config>(configurations);
BindingSource bindingSource = new BindingSource(sortableBindingList, null);
dataGridView.DataSource = bindingSource;

This way it's very easy to track changes in your data. (f.e. BindingSource.Current returns the current item of your DataGridView)

BindingSource bindingSource = dataGridView.DataSource as BindingSource;
Config currentConfig = bindingSource.Current as Config;

If I would have to filter the data now, I'd do the following:

BindingSource bindingSource = dataGridView.DataSource as BindingSource;
List<Config> list = bindingSource.DataSource as List<Config>;
bindingSource.DataSource = list.Where(item => item.Server = 'banana').ToList();

If you have any bindings to the BindingSource (TextBoxes, ComboBoxes, etc.) keep in mind that attaching/detaching DataSource can cause unwanted behaviour. To avoid this I do suspending and resuming of the binding:

bindingSource.SuspendBinding();
// do the filtering
bindingSource.ResumeBinding();

This keeps all the DataBindings alive.

UPDATE: displaying only certain columns in your DataGridView

That's quite simple. (column names correlate with the names in your DataSource object)

var columnFunction = dataGridView.Columns["function"];
if(columnFunction != null)
    columnFunction.Visible = false;

var columnCheckType = dataGridView.Columns["checkType"];
if(columnCheckType != null)
    columnCheckType.Visible = false;

So only your server column will be displayed.