views:

636

answers:

4

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

+2  A: 

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; }
}
Ciwee
I had a feeling it was something simple. Thank you!!
jluce50
A: 

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;
    }
  }
jluce50
+2  A: 

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).

Groo
Will do, but there's still the issue of MeasureText corrupting the data. It happens even if I replace the line you're referring to with a hardcoded string.
jluce50
Thanks, that seems to work! I agree, this method is a strange one. Very little documentation and very unpredictable results.
jluce50
A: 

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?!!!!!

jluce50
Value is being retained because .Net uses String interning. That means that it keeps only one instance of your constant string, and `Path` gets a reference to that same instance every time (to save memory). Since String are (or rather SHOULD BE) immutable, this is supposed to be safe, because no method should be able to change them (except this one). That's why I explicitly added `String.Copy` in the first line of my example, to force creating a separate instance of the string each time.
Groo