none
Formatting strings into columns of an exact size - is there a way to overlap strings? RRS feed

  • Question

  • Hi I was trying use string.format to place strings into columns.  Each column has an exact beginning and length that should never change.  But I find that if a string exceeds the length of the column, all the subsequent entries on that row get shifted and the alignment is broken.

    My question is, is there a way to tell the formatter to overlap the strings, so if the first string is too long by 3 characters, the next column still starts where it's supposed to and overwrites the ending 3 chars of the previous string.



    • Edited by sjs1978 Monday, September 24, 2012 3:53 PM
    Monday, September 24, 2012 3:52 PM

Answers

  • Good that you found one more solution. This was what I was also trying in the meantime but little slightly.

    If you really like it, here is the source code:

    public class MyString : System.IFormattable
        {
            String _underlying;
            Int32 _underlyingLength = -1;
    
            public MyString (String underlying)
            {
                _underlying = underlying;
    
                if (_underlying != null)
                {
                    _underlyingLength = underlying.Length;
                }
            }
    
            public override string ToString ()
            {
                return this.ToString (null);
            }
    
            public string ToString (String format)
            {
                return this.ToString (format, null);
            }
    
            public string ToString (String format, System.IFormatProvider formatProvider)
            {
                if (_underlyingLength < 0)
                {
                    return String.Empty;
                }
    
                formatProvider = formatProvider ?? System.Globalization.CultureInfo.CurrentCulture;
    
                if (String.IsNullOrEmpty (format))
                {
                    format = "-1";
                }
    
                int alignLength = -1;
    
                if (!Int32.TryParse (format, out alignLength))
                {
                    alignLength = -1;
                }
    
                if ((alignLength < 0) || (alignLength == _underlyingLength))
                {
                    return _underlying;
                }
                else if (alignLength > _underlying.Length)
                {
                    return _underlying.PadLeft (alignLength);
                }
                else
                {
                    return _underlying.Substring (0, alignLength);
                }
            }
        }

    Then, you can use this string inside any Format specifiers like:

    // returns "Hello      World"
    String.Format ("{0:5} {1:10}", new MyString ("Hello"), new MyString ("World"));
    // returns "Hello World"
    String.Format ("{0:5} {1}", new MyString ("HelloHello"), new MyString ("World"));

    In my opinion, using IFormattable instead of IFormatProvider keeps your code looking elegant. ;-). Ideally, a programmer wants his code to look almost like using normal String instances with String.Format. Isn't it?

    I have checked my code and it is working fine. Please feel free to validate this logic yourself.

    I am so sorry if you find it not helping you again though.

    EDIT: I observed there is a small TODO thing there - making use of supplied IFormatProvider. I hope you can do it yourself. :-)

    • Edited by Rajesh_Kannan Monday, September 24, 2012 5:21 PM
    • Proposed as answer by Mike FengModerator Wednesday, September 26, 2012 10:46 AM
    • Unproposed as answer by Mike FengModerator Wednesday, September 26, 2012 10:46 AM
    • Marked as answer by sjs1978 Wednesday, September 26, 2012 3:52 PM
    Monday, September 24, 2012 5:15 PM
  • Learning is always a pleasure - those AHA moments of satisfaction! Isn't it? :-). Happy that it is working for you.

    (1) 

    One thing I don't understand though is how the {0:5} {0:10}...strings you specify as the format, get passed into the class.  Can someone please explain?

    I am mostly reiterating what is mentioned in the documentation for String.Format - consider the examples {0:5}, {1:10}:

    - 0 and 1 are the positional indices of the parameters following the format item in the call to String.Format

    - usually the part that comes after the ":" symbol represents the "formatString"s like Numeric, Date, Enum format strings - but we can utilize this part to customize the behavior inside our own IFormattable implementation.

    The underlying logic of String.Format identifies that our object implements IFormattable and so it happily offloads its usual formatting task to our implementation.

    It passes both this "formatString" part and "IFormatProvider" (if supplied, otherwise null) to our implementation and expects the final formatted string as the return value. In our case, it passes 5 and 10 as the "formatString" and "null" for "IFormatProvider".

    (2) 

    Also I never specified the format provider, is that even necessary?

    No, it is not necessary. As I mentioned, it will be passed to our IFormattable.ToString method as "null" then - I prefer to use the default current culture in this case inside our IFormattable.ToString implementation (Please refer my version).

    (3) 

    Can I mention one more thing as a bonus point if you want? There is a very nice answer here in another forum that talks about when to use IFormattable, IFormatProvider and ICustomFormatter. I always refer this whenever I am in doubt during my designs. Hope you don't see it as irrelevant here.

    • Edited by Rajesh_Kannan Wednesday, September 26, 2012 3:50 PM
    • Marked as answer by sjs1978 Wednesday, September 26, 2012 3:52 PM
    Wednesday, September 26, 2012 3:47 PM

All replies

  • In my opinion, the String.Format method automatically supports Alignments as you require.

    You can please refer the documentation for String.Format method.

    Excerpts from the documentation as relevant here:

    The syntax for the format item is described as:

    { index[,alignment][ : formatString] }

    A format item has the following elements:

    • index - The zero-based index of the argument whose string representation is to be included at this position in the string. If the argument is null, an empty string will be included at this position in the string.
    • alignment - This is what relevant for you - A signed integer that indicates the total length of the field into which the argument is inserted and whether it is right-aligned (a positive integer) or left-aligned (a negative integer). If alignment is omitted, the string representation of the corresponding argument is inserted in a field with no leading or trailing spaces.
    • formatString - A format string that specifies the format of the corresponding argument's result string.

    Hope I understood your requirement properly in writing this.

    Monday, September 24, 2012 4:09 PM
  • Its not the alignment that is the issue, the problem occurs when a string is longer than the column width defined in the formatter.  if it is longer, the string in the next column position is shifted.

    string sFormat= "{0, -5}{1, -10}";

    string strText = string.Format(sFormat, str[0], str[1]);

    Example

    col1    col2            

    123    1234567890

    123451234567890

    1234  1234567890

    123456781234567890  <--shifts

    if the first column is longer than 5 characters it pushes the next column to the right


    Iin other words I don't want the position of the next string to be relative to the preceding string.  I want it to start at a specific character position even if it means it must overwrite part of the preceding string.
    • Edited by sjs1978 Monday, September 24, 2012 4:24 PM
    Monday, September 24, 2012 4:21 PM
  • Hi sjs,

    Please ignore my previous reply. I overlooked your requirement.

    The Alignment option provided by String.Format is just a guidance and not a mandate.

    Sorry for my mistake and misleading please. I will try to find a solution.

    Best Regards

    Rajesh Kannan

    Monday, September 24, 2012 4:25 PM
  • Yes, my mistake please.
    Monday, September 24, 2012 4:26 PM
  • I managed to find this post which suggest several solutions

    http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/e8681e3d-4d9e-4757-82fb-fc6c629b346d/

    Would this be the only way to limit the length of the strings in each column? 

    i.e. edit the string before it is passed to the formatter

    • Edited by sjs1978 Monday, September 24, 2012 4:43 PM
    Monday, September 24, 2012 4:41 PM
  • Good that you found one more solution. This was what I was also trying in the meantime but little slightly.

    If you really like it, here is the source code:

    public class MyString : System.IFormattable
        {
            String _underlying;
            Int32 _underlyingLength = -1;
    
            public MyString (String underlying)
            {
                _underlying = underlying;
    
                if (_underlying != null)
                {
                    _underlyingLength = underlying.Length;
                }
            }
    
            public override string ToString ()
            {
                return this.ToString (null);
            }
    
            public string ToString (String format)
            {
                return this.ToString (format, null);
            }
    
            public string ToString (String format, System.IFormatProvider formatProvider)
            {
                if (_underlyingLength < 0)
                {
                    return String.Empty;
                }
    
                formatProvider = formatProvider ?? System.Globalization.CultureInfo.CurrentCulture;
    
                if (String.IsNullOrEmpty (format))
                {
                    format = "-1";
                }
    
                int alignLength = -1;
    
                if (!Int32.TryParse (format, out alignLength))
                {
                    alignLength = -1;
                }
    
                if ((alignLength < 0) || (alignLength == _underlyingLength))
                {
                    return _underlying;
                }
                else if (alignLength > _underlying.Length)
                {
                    return _underlying.PadLeft (alignLength);
                }
                else
                {
                    return _underlying.Substring (0, alignLength);
                }
            }
        }

    Then, you can use this string inside any Format specifiers like:

    // returns "Hello      World"
    String.Format ("{0:5} {1:10}", new MyString ("Hello"), new MyString ("World"));
    // returns "Hello World"
    String.Format ("{0:5} {1}", new MyString ("HelloHello"), new MyString ("World"));

    In my opinion, using IFormattable instead of IFormatProvider keeps your code looking elegant. ;-). Ideally, a programmer wants his code to look almost like using normal String instances with String.Format. Isn't it?

    I have checked my code and it is working fine. Please feel free to validate this logic yourself.

    I am so sorry if you find it not helping you again though.

    EDIT: I observed there is a small TODO thing there - making use of supplied IFormatProvider. I hope you can do it yourself. :-)

    • Edited by Rajesh_Kannan Monday, September 24, 2012 5:21 PM
    • Proposed as answer by Mike FengModerator Wednesday, September 26, 2012 10:46 AM
    • Unproposed as answer by Mike FengModerator Wednesday, September 26, 2012 10:46 AM
    • Marked as answer by sjs1978 Wednesday, September 26, 2012 3:52 PM
    Monday, September 24, 2012 5:15 PM
  • Thanks for your help, however it was not working for me.  I also tried the following code from the other post but to be honest I don't know how to use the format provider to do this. I never did this before.

        class StringLimiter : IFormatProvider, ICustomFormatter
        {
          public object GetFormat(Type formatType)
          {
            return this;
          }
    
          public string Format(string format, object arg, IFormatProvider formatProvider)
          {
            string s = arg as string;
            if (s != null)
            {
              int length;
              if (int.TryParse(format, out length))
                return s.Substring(0, length);
            }
            return string.Format("{0:" + format + "}", arg);
          }
        }

    Monday, September 24, 2012 5:52 PM
  • Ok. Where and what is the problem? More information please.

    Unfortunately, I am unable to upload my solution project. But, this is the only class in my solution.

    For your information, my application was a simple WinForms with only one "Form" and "MyString" class.

    If you wanted to use the sample from other thread, the help was at its bottom. I placed it for your easier reference here

    string sText = string.Format(
      new StringLimiter(),
      "##{0:5}##{0,8:5}##{0,-8:5}##Non-string value: {1:0.00}",
      "test with a long string",
      456);

    Is it the one not working for you? Then I need to spend more time with this.

    In the meantime, could you mention what was wrong with my code?

    Monday, September 24, 2012 7:12 PM
  • Hi Sis,

    Would you like to follow up this thread to post your further issue here since Rajes_Kannan is trying to help you.

    @Rajes_Kannan

    Thank you for your contribution on MSDN forum.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, September 26, 2012 10:48 AM
    Moderator
  • Hi thank you,

    The issue I had with the code, was it was not limiting the string length.  Perhaps I was not using it correctly.  I need to do some more investigation and will post back.

    Wednesday, September 26, 2012 1:38 PM
  • Hi I modified the code slightly and it works for me now. Thanks very much

    One thing I don't understand though is how the {0:5} {0:10}...strings you specify as the format, get passed into the class.  Can someone please explain?

    Also I never specified the format provider, is that even necessary?

    (I was trying to follow this example on msdn - http://msdn.microsoft.com/en-us/library/system.iformattable%28v=vs.80%29.aspx)

        public class MyString : System.IFormattable
        {
            String _underlying;
            Int32 _underlyingLength = -1;
    
            public MyString(String underlying)
            {
                _underlying = underlying;
    
                if (_underlying != null)
                {
                    _underlyingLength = underlying.Length;
                }
            }
    
            public override string ToString()
            {
                return this.ToString(null);
            }
    
            public string ToString(String format)
            {
                return this.ToString(format, null);
            }
    
            public string ToString(String format, System.IFormatProvider formatProvider)
            {
                if (_underlyingLength < 0)
                {
                    return String.Empty;
                }
    
                if (String.IsNullOrEmpty(format))
                {
                    format = "-1";
                }
    
                int alignLength = -1;
    
                if (!Int32.TryParse(format, out alignLength))
                {
                    alignLength = -1;
                }
    
                if ((alignLength < 0) || (alignLength == _underlyingLength))
                {
                    return _underlying;
                }
                else if (alignLength > _underlying.Length)
                {
                    return _underlying.PadRight(alignLength);
                }
                else
                {
                    return _underlying.Substring(0, alignLength);
                }
            }
        }



    • Edited by sjs1978 Wednesday, September 26, 2012 3:19 PM
    Wednesday, September 26, 2012 3:16 PM
  • Learning is always a pleasure - those AHA moments of satisfaction! Isn't it? :-). Happy that it is working for you.

    (1) 

    One thing I don't understand though is how the {0:5} {0:10}...strings you specify as the format, get passed into the class.  Can someone please explain?

    I am mostly reiterating what is mentioned in the documentation for String.Format - consider the examples {0:5}, {1:10}:

    - 0 and 1 are the positional indices of the parameters following the format item in the call to String.Format

    - usually the part that comes after the ":" symbol represents the "formatString"s like Numeric, Date, Enum format strings - but we can utilize this part to customize the behavior inside our own IFormattable implementation.

    The underlying logic of String.Format identifies that our object implements IFormattable and so it happily offloads its usual formatting task to our implementation.

    It passes both this "formatString" part and "IFormatProvider" (if supplied, otherwise null) to our implementation and expects the final formatted string as the return value. In our case, it passes 5 and 10 as the "formatString" and "null" for "IFormatProvider".

    (2) 

    Also I never specified the format provider, is that even necessary?

    No, it is not necessary. As I mentioned, it will be passed to our IFormattable.ToString method as "null" then - I prefer to use the default current culture in this case inside our IFormattable.ToString implementation (Please refer my version).

    (3) 

    Can I mention one more thing as a bonus point if you want? There is a very nice answer here in another forum that talks about when to use IFormattable, IFormatProvider and ICustomFormatter. I always refer this whenever I am in doubt during my designs. Hope you don't see it as irrelevant here.

    • Edited by Rajesh_Kannan Wednesday, September 26, 2012 3:50 PM
    • Marked as answer by sjs1978 Wednesday, September 26, 2012 3:52 PM
    Wednesday, September 26, 2012 3:47 PM
  • @MIKE: Sorry, I did not notice your reply. Thank you very much for your appreciation. Cheers!
    Wednesday, September 26, 2012 3:56 PM