Question

I have an unbound DataGridView (in VS 2008), of which one column contains a file path. I'd like to format the string using the TextRenderer class on the ColumnWidthChanged event without actually modifying the underlying value. The problem is that the contents of the table are saved when the form is closed and I don't want to save the formatted value. I think I'm just in too deep to see the obvious solution so I'm depending on you guys to point it out :-).

The idea is to display this:

C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe

...as this (depending on the width of the column):

C:\Program Files\Microso…\gacutil.exe


It appears I spoke too soon. I'm getting some very strange results from TextRenderer.MeasureText(). If I hardcode the path value as "C:\Documents and Settings\jluce\My Documents\Downloads" it comes ends up as C:\Documents and Settings\jluce\M...\Downloads\0wnloads". If I don't hardcode it (like below) it gets further corrupted each time I resize the column.

Here's what it looks like after a couple resizes: Screenshot

Here's what I'm currently doing.

  if (e.ColumnIndex == 1)
  {
    foreach (DataGridViewRow Row in mappingsDataGrid.Rows)
    {
      string Path = (string)Row.Cells[1].Value;
      Path = Path.Trim();

      TextRenderer.MeasureText(Path, e.CellStyle.Font,
        new Size(mappingsDataGrid.Columns[e.ColumnIndex].Width, Row.Height),
          TextFormatFlags.ModifyString | TextFormatFlags.PathEllipsis);

      e.Value = Path;
    }
  }

This just keeps getting weirder!!

I managed to fix the problem of the mangled string by iterating through each char and removing the bad ones. However, now I've got an even crazier problem. A local variable I'm assigning in the event handler is retaining its value between calls.

Here's the relevant code:

     string Path = ""; // <-- #1
     Path = "C:\\Documents and Settings\\jluce\\My Documents\\Downloads"; // <-- #2

      TextRenderer.MeasureText(Path, Row.Cells[1].Style.Font,
        new Size((mappingsDataGrid.Columns[e.Column.Index].Width), Row.Height),
          TextFormatFlags.ModifyString | TextFormatFlags.PathEllipsis);

      // Left out code that strips invalid chars

      Row.Cells[1].Value = Path; // <-- #3
      Path = null;

First time resizing column (refer to #'s in the comments above):

  1. After this line Path contains "".
  2. After this line Path contains string just as it appears above.
  3. Path contains truncated file path as it should (i.e. "C:\Documents and Setti...\Downloads")

Second time resizing:

  1. After this line Path contains "", as it should.
  2. After this line Path contains "C:\Documents and Set...\Downloads\0 Documents\Downloads", which was the invalid value from the previous iteration before I stripped out the invalid characters (seen here as '\0')!!
  3. Now the path is FUBAR because I started with a screwed up string and it just keeps getting worse.

Why would Path be assigned the invalid value from the previous function call (after correctly assigning an empty string!) when I'm explicitly assigning a value to it?!!!!!

Was it helpful?

Solution

TextRenderer.MeasureText method is a nasty one -- it changes the actual string passed as parameter, so it's changing the actual string referenced by the DataGridView. It actually makes a .Net string mutable.

It also seems that this ridiculous method doesn't change the actual Length of the string, but merely overwrites one of the characters with \0 to indicate the end of the string (like null-terminated strings in plain C). That's some funny stuff!

This can have a serious impact on the stability of your app. If you take into account that .Net uses string interning, you can start getting all sorts of weird results, as you did notice that your string constants no longer seem constant.

First step is to create a copy of your string (a new instance with same characters):

string Path = String.Copy(e.Value as string ?? "");

instead of

string Path = (string)Row.Cells[1].Value;

This will ensure that no matter what TextRenderer does, original string will remain unchanged.

After that, you need to get rid of the null-character in the modified string.

By doing this:

if (Path.IndexOf('\0') >= 0)
   e.Value = Path.Substring(0, Path.IndexOf('\0'));
else
   e.Value = Path;

you will create a new instance of a clean, modified string (leaving our temporary Path copy unreferenced for garbage collection).

OTHER TIPS

You need to use CellFormatting event to CHANGE the given value before it's printed (the original object value won't be modified). In your case, you could check if it's the right column by verifying e.ColumnIndex variable and change the e.Value text as I did below:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        dataGridView1.DataSource = new List<Person>(new Person[] { new Person() { Name = "André", Adress = "Brazil" } });
    }

    private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
    {
        e.Value = e.Value + " modified";
    }
}

class Person
{
    public String Name { get; set; }
    public String Adress { get; set; }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top