Resolved DataTable global variable changing when DataGridView.DataSource is changed

Cragstor

New member
Joined
Aug 9, 2021
Messages
3
Programming Experience
1-3
Hello,

I'm new to here, so firstly hello everyone.

I have looked through the forums but can't find an answer and I'm struggling to find one online anywhere with how datasources work in this case.

The code below is a snippet of a program I am writing to transfer files. The problem I had is sometimes the file paths are extremely long and I wanted to shorten the presentation of the file path to the user.

To do this I thought I would just create a global DataTable variable and assign the value of the "real information" to this DataTable, before I reformat the cell text in the datagridview.

The problem I'm having is it seems to be referencing the datatables rather than assigning the values, because when I change the information in the cells of the datagridview (DgvMain) it is automatically updating my local DataTable (dt) and the global DataTable (activeDGVTable) with the trimmed strings.

I just want to assign to a DataTable and for it to not be linked to the datagridview, so that when I change the information in the datagridview, I can preserve what I stored in my global variable.

I hope I explained that in a way that can be understood.

For clarity, two things in there that I haven't referenced:

  • I created an extension for string called Right which does nothing but gets the last X amount of characters from a string.
  • SQLUtility.ExecuteQueryToDataTable handles my SQL call and it is a static class which returns a DataTable

Any help would be greatly appreciated.

Craig

C#:
public partial class TransferScreen : Form
{
    DataTable activeDGVTable = new DataTable();
    
    //function which loops through each cell in the datagridview and replaces it
    //with the last 40 characters on the right
    private void FormatDgvSourceDestination(DataGridView dgv)
    {
        foreach (DataGridViewRow row in dgv.Rows)
        {
            foreach(DataGridViewCell cell in row.Cells)
            {
                string cellString = cell.Value.ToString();
                cell.Value = ".../" + cellString.Right(40);
                UpdateLog(cell.Value.ToString(), Color.Orange);
            }
                
        }
       }
    
    //button to execute SQL query and assign the datagridview datesource and the global
    //variable with the returned information
    private void BtnReportCopyInstructions_Click(object sender, EventArgs e)
    {
        DataTable dt = SQLUtility.ExecuteQueryToDataTable("SQL CODE HERE");
        DgvMain.DataSource = dt;
        activeDGVTable = dt;
        FormatDgvSourceDestination(DgvMain);
    }
}
 
Solution
it is automatically updating my local DataTable (dt) and the global DataTable (activeDGVTable)
This is nothing to do with DataTables or the grid or anything like that. Your issue is that you don't understand how reference types work. OOP is based on the way objects work and behave in the real world so consider how real-world objects behave. Let's say that I have an ice cream. I decide to share it with you so now you have an ice cream too. Does that mean that we magically have two ice creams? Of course not. My ice cream and your ice cream are the same ice cream. There's only one ice cream.

The same thing is happening in your code. Here:
C#:
DataTable dt = SQLUtility.ExecuteQueryToDataTable("SQL CODE HERE");
you create a...
A DataGridView is supposed to be a window to your underlying data. The typical usage of the control is that if change the value in the UI, the value gets reflected down back down to the underlying data. If you really want to customize how the data displayed, but not change the underlying data, I recommend looking the CellFormatting event.


Another thing to consider: If you know that this particular path is always going to live in a specific column, it maybe worth creating your own custom DataGridViewTextBoxColumn where you override the rendering for the cells for that particular column.
 
it is automatically updating my local DataTable (dt) and the global DataTable (activeDGVTable)
This is nothing to do with DataTables or the grid or anything like that. Your issue is that you don't understand how reference types work. OOP is based on the way objects work and behave in the real world so consider how real-world objects behave. Let's say that I have an ice cream. I decide to share it with you so now you have an ice cream too. Does that mean that we magically have two ice creams? Of course not. My ice cream and your ice cream are the same ice cream. There's only one ice cream.

The same thing is happening in your code. Here:
C#:
DataTable dt = SQLUtility.ExecuteQueryToDataTable("SQL CODE HERE");
you create a DataTable and you assign it to the dt variable. Here:
C#:
activeDGVTable = dt;
you're not magically creating a second DataTable. You're simply assigning the same DataTable object to another variable, so both variables now refer to the same DataTable. If you wanted to copy that DataTable object then you would need to call its Copy method to get a new DataTable object and assign that to the second variable. That's a bad idea though. You only need one DataTable. Just add another column and put the extra data there. You can then select which columns you want displayed in the grid and which you don't.
 
Solution
Thank you both. The information on reference types is useful. I have had some experience of referencing objects vs creating a separate instance of that object, and thought that might be the case. It just wasn't immediately clear to me.

C#:
string test1 = "test";
string test2 = "test 2";
string test3;
test3 = test1;
test3 = test2;

the above here as an example does not work the same way? in this case test3 and test1 are not sharing the same ice cream? :)

I think I will try to format the cells first, an easy solution that I overlooked. If I have an issue, I will do as suggested and create an extra column for each with the display data.

Thanks again,
Craig
 
What where you expecting to see as the final value of test3?
 
Thank you both. The information on reference types is useful. I have had some experience of referencing objects vs creating a separate instance of that object, and thought that might be the case. It just wasn't immediately clear to me.

C#:
string test1 = "test";
string test2 = "test 2";
string test3;
test3 = test1;
test3 = test2;

the above here as an example does not work the same way? in this case test3 and test1 are not sharing the same ice cream? :)

I think I will try to format the cells first, an easy solution that I overlooked. If I have an issue, I will do as suggested and create an extra column for each with the display data.

Thanks again,
Craig
That example works exactly the same way. None of those lines of code makes any change to any object, so I'm not sure what you're expecting to happen. After line 4, text3 refers to the same object as test1 and then after line 5 test3 refers to the same object as test2. In that case, there are two ice creams because the first two lines each create one. In the fourth line, test1 shares its ice cream with test2 but then in line 5 test2 shares its ice cream with test3, so test1 is no longer sharing its ice cream with anyone.
 
While String is a class and thus a reference type, strings aren't great for demonstrating the behaviour of reference types because they are a little bit special. They still behave exactly like every other reference type under the hood but it's not always obvious for two reasons: you can use literal strings and strings are immutable, i.e. they cannot be changed once created. Here's a simple demonstration of how reference types work with multiple variables referencing the same object at the same time and the same variable referencing different objects at different times:
C#:
var var1 = new List<string> { "A", "B" }; // Create first list
var var2 = new List<string> { "X", "Y" }; // Create second list
List<string> var3; // Variable refers to no object by default

Console.WriteLine($"1. First list: {string.Join(",", var1)}");
Console.WriteLine($"1. Second list: {string.Join(",", var2)}");

var3 = var1;
var3.Add("C");

Console.WriteLine($"2. First list: {string.Join(",", var1)}");
Console.WriteLine($"2. Second list: {string.Join(",", var2)}");

var3 = var2;
var3.Add("Z");

Console.WriteLine($"3. First list: {string.Join(",", var1)}");
Console.WriteLine($"3. Second list: {string.Join(",", var2)}");
 
Thank you for all your help. In the end I used the properties available in the datagridview to automatically resize the cells and change the presentation so I didn't need to mess around as much (in hindsight a lot better).

The information on how reference types work is really useful. You're right I really didn't understand it. Up until this point I thought that once you assigned a value to a variable, you were copying that value, rather than creating a reference to what it was assigned from. This'll really help me in the future to make sure I don't get in a mess with object assignment!

Thanks!
 
Back
Top Bottom