Tuesday, July 08, 2008

How to handle thickness

Yesterday, we spoke about type converters. We even, built simple generic enum converter. Today, we’ll create more complicated converter, that very missing in Silverlight – ThicknessConverter. During the post, I also explain about tokenizing values in Silverlight

image

What is Thickness ?

What is thickness in Silverlight? It’s

<Border BorderThickness=”6”/>

or

<Border BorderThickness=”6,4,3,2”/> or <Border BorderThickness=”6, 4, 3,2”/> or, even <Border BorderThickness=”6; 4; 3 ;2”/>

How to handle it?

Tokenizing strings

What you seen here is tokenized strings. We have to split them by known token (one and own for each string) and then we can parse it for converter. How to do this? Complicated – too much cases. But, basically, you have to get string, quote character and separator. Don’t forget to check for empty spaces

private void Initialize(string str, char quoteChar, char separator)
        {
            this._str = str;
            this._strLen = (str == null) ? 0 : str.Length;
            this._currentTokenIndex = -1;
            this._quoteChar = quoteChar;
            this._argSeparator = separator;
            while (this._charIndex < this._strLen)
            {
                if (!char.IsWhiteSpace(this._str, this._charIndex))
                {
                    return;
                }
                this._charIndex++;
            }
        }

Then we have to scan string to find tokens

private void ScanToNextToken(char separator)
        {
            if (this._charIndex < this._strLen)
            {
                char c = this._str[this._charIndex];
                if ((c != separator) && !char.IsWhiteSpace(c))
                {
                    Exceptions.ThrowInvalidOperationException("No Separator Found");
                }
                int i = 0;
                while (this._charIndex < this._strLen)
                {
                    c = this._str[this._charIndex];
                    if (c == separator)
                    {
                        this._foundSeparator = true;
                        i++;
                        this._charIndex++;
                        if (i > 1)
                        {
                            Exceptions.ThrowInvalidOperationException("Empty Token Found");
                        }
                    }
                    else
                    {
                        if (!char.IsWhiteSpace(c))
                        {
                            break;
                        }
                        this._charIndex++;
                    }
                }
                if ((i > 0) && (this._charIndex >= this._strLen))
                {
                    Exceptions.ThrowInvalidOperationException("Emply Token Found");
                }
            }
        }

why not just split? Because it is not generic solution for strings with empty tokens, which is absolutely invalid. Another reason of using such helper is performance. String operation are not very fast things, thus we’ll check only the number of tokens required for future operations.

Also, we should make sure, that all tokens are required and get rid of unnecessary parts of the string, such as leading spaces, control characters etc. Now, when we have tokenized string, we can start building converter

Building thickness converter

Actually, the most significant part of this converter is tokenization , thus the most important override method for such converter is ConvertFromString

public override object ConvertFromString(string text)
        {
            Thickness res = new Thickness();
            TokenizerHelper helper = new TokenizerHelper(text);
            double[] numArray = new double[4];
            int index = 0;
            while (helper.NextToken())
            {
                if (index >= 4)
                {
                    index = 5;
                    break;
                }
                LengthConverter lc = new LengthConverter();
                numArray[index] = (double)lc.ConvertFromString(helper.GetCurrentToken());
                index++;
            }
            switch (index)
            {
                case 1:
                    res = new Thickness(numArray[0]); break;
                case 2:
                    res = new Thickness(numArray[0], numArray[1], numArray[0], numArray[1]); break;

                case 4:
                    res = new Thickness(numArray[0], numArray[1], numArray[2], numArray[3]); break;
                default:
                    typeof(Thickness).ThrowConvertFromException(text); break;
            }
            return res;
        }

The only thing to remember is to check whither we can convert from the type received from XAML

public override bool CanConvertFrom(Type sourceType)
        {
            switch (Type.GetTypeCode(sourceType))
            {
                case TypeCode.Int16:
                case TypeCode.UInt16:
                case TypeCode.Int32:
                case TypeCode.UInt32:
                case TypeCode.Int64:
                case TypeCode.UInt64:
                case TypeCode.Single:
                case TypeCode.Double:
                case TypeCode.Decimal:
                case TypeCode.String:
                    return true;
            }
            return false;
        }

Have a nice day and be good people. Stay tuned for future work process items.

No comments: