5

I was searching for a way to insert an ellipsis in a C# path, and found an answer here on stackoverflow: C# Path Ellipsis without Win32 API call

Using the RTM versions of VS2010 and .Net 4.0, I was unable to get the suggested method to work. I searched the 'Net and found example code that uses the same method, but it failed in the same way.

You can see the string I'm trying to shorten in my code below.

After calling the MeasureText method, both the input string (OriginalName) and the output string (ellipsisedName) look like this:

d:\abcd\efgh\ijkl\mnop\qrst\...\test.txt\0F\GHIJ\KLMN\OPQR\STIV\WXYZ\test.txt

Two problems:

1) The resulting string is narfed (the path is truncated as expected, but is followed by what looks like a C-style terminating null and a chunk of the original path).

2) My original string is changed to be identical to the output string.

Am I doing something wrong?

namespace WindowsFormsApplication2 {
   public partial class Form1 : Form {
      public Form1()
      {
         InitializeComponent();

         string OriginalPath = @"d:\abcd\efgh\ijkl\mnop\qrst\uvwx\yzAB\CDEF\GHIJ\KLMN\OPQR\STIV\WXYZ\test.txt";
         string ellipsisedPath = OriginalPath;

         Size proposedSize = new Size(label1.Width, label1.Height);

         TextRenderer.MeasureText(ellipsisedPath, label1.Font, proposedSize, TextFormatFlags.ModifyString | TextFormatFlags.PathEllipsis);
      }
   }
}
Community
  • 1
  • 1
casterle
  • 1,037
  • 2
  • 12
  • 27
  • I don't see how your call to MeasureText() could possibly modify OriginalPath. If that is really happening, the MeasureText() method is doing something really funky. – Igby Largeman Apr 14 '10 at 20:22

2 Answers2

4

Holy moly, you've found a whopper of a bug. The P/Invoke used inside the TextRenderer class that calls DrawTextEx() is borked. That API function is writing back into the string, which it is allowed to do since the cchText argument is a LPTSTR, not a LPCTSTR. That destroys the .NET string content for both variables because the string is interned.

The bug isn't specific to .NET 4.0, I see it wrong in the ReferenceSource for .NET 3.5 SP1 as well and can repro it on VS2008. The trouble is in the internal WindowsGraphics.MeasureText function. You can report the bug at connect.microsoft.com.

A possible workaround is to alter the string so it gets copied and can't affect the original:

  string ellipsisedPath = OriginalPath + '\0';

But the better workaround in this case is to simply not pass the ModifyString option, it serves no purpose. Which is safer too, there is still a possibility of destroying the garbage collected heap with the first workaround. The fix for Microsoft is similarly simple, it should just mask out the ModifyString option. It is documented to have no effect.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    You can use `string.Copy` to be clear about copying from the interned string to a new buffer. – Ron Warholic Apr 14 '10 at 20:37
  • Thank you for the explanation. Your original work-around (and Ron's) both solve the overwritten-original-string problem, but neither makes MeasureText work as I wanted, as the output string is still not ellipsised properly. If I don't specify ModifyString the output string is not ellipsised (as expected), which defeats my purpose (which was to get an ellipsised path string). What I'm trying to do is probably a misuse of ModifyString anyway. I was using this technique as it was suggested as the way to do this here: http://tinyurl.com/y6rmdfr – casterle Apr 14 '10 at 21:16
  • 1
    @casterle: Ugh. Your code actually relied on the bug. I was wondering why you'd use MeasureText when you actually need DrawText to display the ellipses. Which is what you'll need to do, override the OnPaint method. Epic thread here, I'll tell my grand kids about it some day. – Hans Passant Apr 14 '10 at 21:30
  • @casterle: here's the proper way to do it: http://stackoverflow.com/questions/2397860/smart-winforms-textbox-to-display-path – Hans Passant Apr 14 '10 at 21:38
2

My original string is changed to be identical to the output string.

You've asked for this to happen by specifying TextFormatFlags.ModifyString, which the docs say

Modifies the specified string to match the displayed text. This value has no effect unless EndEllipsis or PathEllipsis is also specified.

This is (to my mind) an unusual way for a .NET Framework call to operate, but it does clearly say it will do this. Both the 'original' string and the 'output' string end up being modified, because string is a reference type (though usually with immutable value semantics) - when you say

string ellipsisedPath = OriginalPath;

you are actually just making ellipsisedPath refer to the same string instance as OriginalPath does. When this instance gets modified by the API call, both the references to it will see the modification.

As for

the path is truncated as expected, but is followed by what looks like a C-style terminating null and a chunk of the original path

my guess would be that the abstraction this managed wrapper provides around the Win32 API call is being somewhat leaky, as abstractions are prone to being - it's not shielding you from the fact that the underlying call works with C-style strings. It might be that you'll have to deal with yourself.

AakashM
  • 62,551
  • 17
  • 151
  • 186
  • "when you say string ellipsisedPath = OriginalPath; you are actually just making ellipsisedPath refer to the same string instance as OriginalPath does. [...]" This is **wrong**. Assigning string1 to string2 makes a value copy of string1. Any change to string1 will have **no** effect on string2. And although string is a Class, it is immutable and behaves like a value type. Strings are never *changed*, they are always *copied* to a new string with the modification. – Igby Largeman Apr 14 '10 at 20:23
  • @Hans: It may describe what happened due to the bug, but it also gives blatantly false information regarding assigning one string to another in any normal situation, and fails to point out that this only happened because of a major bug in TextRenderer. Please at least acknowledge that, as someone with your rep shouldn't be contributing to such a misunderstanding. – Igby Largeman Apr 14 '10 at 22:55
  • 1
    @Charles, you'll have to sniff the way the world is not perfect. Only when you truly understand how the bug came around can you enjoy the Enlightenment. Your assumption that System.String is immutable got a kick in the head here. Find out why. – Hans Passant Apr 14 '10 at 23:25
  • I understand the bug well enough (not as well as you, but well enough), and my point stands. The text of this answer is specifically misleading. However, it's not worth arguing about. – Igby Largeman Apr 15 '10 at 00:15
  • @HansPassant Probably something to do with 8.2.1 which states that `string` values are immutable, 11.2.3 which states that `string` is an alias for `System.String`, and Annex D page 483 which doesn't seem to define any methods for mutating strings... and numerous other parts of the C# language spec... – autistic Feb 22 '16 at 21:10