From 1bfa5b6854444e9e04fed20da1590cd8be60aad8 Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Thu, 19 Sep 2019 00:31:18 +0800 Subject: [PATCH] add ColLayoutConverter --- .../UserControl/Controls/GridDemoCtl.xaml | 4 +- .../HandyControl_Shared.projitems | 2 + .../Tools/Converter/ColLayoutConverter.cs | 161 ++++++++++++ .../Tools/Extension/ColLayout.cs | 54 ++++ .../Tools/Helper/TokenizerHelper.cs | 233 ++++++++++++++++++ 5 files changed, 452 insertions(+), 2 deletions(-) create mode 100644 src/Shared/HandyControl_Shared/Tools/Converter/ColLayoutConverter.cs create mode 100644 src/Shared/HandyControl_Shared/Tools/Helper/TokenizerHelper.cs diff --git a/src/Shared/HandyControlDemo_Shared/UserControl/Controls/GridDemoCtl.xaml b/src/Shared/HandyControlDemo_Shared/UserControl/Controls/GridDemoCtl.xaml index eccc1566..df298e28 100644 --- a/src/Shared/HandyControlDemo_Shared/UserControl/Controls/GridDemoCtl.xaml +++ b/src/Shared/HandyControlDemo_Shared/UserControl/Controls/GridDemoCtl.xaml @@ -139,10 +139,10 @@ - + - + diff --git a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems index 34d3cec7..5ef63b50 100644 --- a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems +++ b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems @@ -41,6 +41,7 @@ + @@ -275,6 +276,7 @@ + diff --git a/src/Shared/HandyControl_Shared/Tools/Converter/ColLayoutConverter.cs b/src/Shared/HandyControl_Shared/Tools/Converter/ColLayoutConverter.cs new file mode 100644 index 00000000..c4bbfb88 --- /dev/null +++ b/src/Shared/HandyControl_Shared/Tools/Converter/ColLayoutConverter.cs @@ -0,0 +1,161 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; +using System.Security; +using System.Text; +using HandyControl.Tools.Extension; + +namespace HandyControl.Tools.Converter +{ + public class ColLayoutConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext typeDescriptorContext, 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; + default: + return false; + } + } + + public override bool CanConvertTo(ITypeDescriptorContext typeDescriptorContext, Type destinationType) => + destinationType == typeof(InstanceDescriptor) || destinationType == typeof(string); + + public override object ConvertFrom(ITypeDescriptorContext typeDescriptorContext, CultureInfo cultureInfo, object source) + { + if (source == null) throw GetConvertFromException(null); + + switch (source) + { + case string s: + return FromString(s, cultureInfo); + case double d: + return new ColLayout((int)d); + default: + return new ColLayout(Convert.ToInt32(source, cultureInfo)); + } + } + + [SecurityCritical] + public override object ConvertTo(ITypeDescriptorContext typeDescriptorContext, CultureInfo cultureInfo, object value, Type destinationType) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + + if (destinationType == null) throw new ArgumentNullException(nameof(destinationType)); + + if (!(value is ColLayout)) throw new ArgumentException("UnexpectedParameterType"); + + var th = (ColLayout)value; + if (destinationType == typeof(string)) return ToString(th, cultureInfo); + if (destinationType == typeof(InstanceDescriptor)) + { + var ci = typeof(ColLayout).GetConstructor(new[] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int) }); + return new InstanceDescriptor(ci, new object[] { th.Xs, th.Sm, th.Md, th.Lg, th.Xl, th.Xxl }); + } + + throw new ArgumentException("CannotConvertType"); + } + + private static string ToString(ColLayout th, CultureInfo cultureInfo) + { + var listSeparator = TokenizerHelper.GetNumericListSeparator(cultureInfo); + + // Initial capacity [128] is an estimate based on a sum of: + // 72 = 6x double (twelve digits is generous for the range of values likely) + // 4 = 4x separator characters + var sb = new StringBuilder(128); + + sb.Append(th.Xs.ToString(cultureInfo)); + sb.Append(listSeparator); + sb.Append(th.Sm.ToString(cultureInfo)); + sb.Append(listSeparator); + sb.Append(th.Md.ToString(cultureInfo)); + sb.Append(listSeparator); + sb.Append(th.Lg.ToString(cultureInfo)); + sb.Append(listSeparator); + sb.Append(th.Xl.ToString(cultureInfo)); + sb.Append(listSeparator); + sb.Append(th.Xxl.ToString(cultureInfo)); + return th.ToString(); + } + + private static ColLayout FromString(string s, CultureInfo cultureInfo) + { + var th = new TokenizerHelper(s, cultureInfo); + var lengths = new int[6]; + var i = 0; + + while (th.NextToken()) + { + if (i >= 6) + { + i = 7; // Set i to a bad value. + break; + } + + lengths[i] = th.GetCurrentToken().Value(); + i++; + } + + switch (i) + { + case 1: + return new ColLayout(lengths[0]); + case 2: + return new ColLayout + { + Xs = lengths[0], + Sm = lengths[1] + }; + case 3: + return new ColLayout + { + Xs = lengths[0], + Sm = lengths[1], + Md = lengths[2] + }; + case 4: + return new ColLayout + { + Xs = lengths[0], + Sm = lengths[1], + Md = lengths[2], + Lg = lengths[3] + }; + case 5: + return new ColLayout + { + Xs = lengths[0], + Sm = lengths[1], + Md = lengths[2], + Lg = lengths[3], + Xl = lengths[4] + }; + case 6: + return new ColLayout + { + Xs = lengths[0], + Sm = lengths[1], + Md = lengths[2], + Lg = lengths[3], + Xl = lengths[4], + Xxl = lengths[5] + }; + } + + throw new FormatException("InvalidStringColLayout"); + } + } +} \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Tools/Extension/ColLayout.cs b/src/Shared/HandyControl_Shared/Tools/Extension/ColLayout.cs index f0573647..91785099 100644 --- a/src/Shared/HandyControl_Shared/Tools/Extension/ColLayout.cs +++ b/src/Shared/HandyControl_Shared/Tools/Extension/ColLayout.cs @@ -1,9 +1,14 @@ using System; +using System.ComponentModel; +using System.Globalization; +using System.Text; using System.Windows.Markup; using HandyControl.Data; +using HandyControl.Tools.Converter; namespace HandyControl.Tools.Extension { + [TypeConverter(typeof(ColLayoutConverter))] public class ColLayout : MarkupExtension { public static readonly int ColMaxCellCount = 24; @@ -30,6 +35,31 @@ namespace HandyControl.Tools.Extension public int Xxl { get; set; } = 2; + public ColLayout() + { + + } + + public ColLayout(int uniformWidth) + { + Xs = uniformWidth; + Sm = uniformWidth; + Md = uniformWidth; + Lg = uniformWidth; + Xl = uniformWidth; + Xxl = uniformWidth; + } + + public ColLayout(int xs, int sm, int md, int lg, int xl, int xxl) + { + Xs = xs; + Sm = sm; + Md = md; + Lg = lg; + Xl = xl; + Xxl = xxl; + } + public override object ProvideValue(IServiceProvider serviceProvider) { return new ColLayout @@ -67,5 +97,29 @@ namespace HandyControl.Tools.Extension } return ColLayoutStatus.Xxl; } + + public override string ToString() + { + var cultureInfo = CultureInfo.CurrentCulture; + var listSeparator = TokenizerHelper.GetNumericListSeparator(cultureInfo); + + // Initial capacity [128] is an estimate based on a sum of: + // 72 = 6x double (twelve digits is generous for the range of values likely) + // 4 = 4x separator characters + var sb = new StringBuilder(128); + + sb.Append(Xs.ToString(cultureInfo)); + sb.Append(listSeparator); + sb.Append(Sm.ToString(cultureInfo)); + sb.Append(listSeparator); + sb.Append(Md.ToString(cultureInfo)); + sb.Append(listSeparator); + sb.Append(Lg.ToString(cultureInfo)); + sb.Append(listSeparator); + sb.Append(Xl.ToString(cultureInfo)); + sb.Append(listSeparator); + sb.Append(Xxl.ToString(cultureInfo)); + return sb.ToString(); + } } } \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Tools/Helper/TokenizerHelper.cs b/src/Shared/HandyControl_Shared/Tools/Helper/TokenizerHelper.cs new file mode 100644 index 00000000..2911bf39 --- /dev/null +++ b/src/Shared/HandyControl_Shared/Tools/Helper/TokenizerHelper.cs @@ -0,0 +1,233 @@ +//--------------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// +// File: TokenizerHelper.cs +// +// Description: This file contains the implementation of TokenizerHelper. +// This class should be used by most - if not all - MIL parsers. +// +// History: +// 05/19/2003 : Microsoft - Created it +// 05/20/2003 : Microsoft - Moved to Shared +// +//--------------------------------------------------------------------------- + +using System; +using System.Globalization; + +namespace HandyControl.Tools +{ + internal class TokenizerHelper + { + private char _quoteChar; + + private char _argSeparator; + + private string _str; + + private int _strLen; + + private int _charIndex; + + private int _currentTokenIndex; + + private int _currentTokenLength; + + public bool FoundSeparator { get; private set; } + + public TokenizerHelper(string str, IFormatProvider formatProvider) + { + var numberSeparator = GetNumericListSeparator(formatProvider); + Initialize(str, '\'', numberSeparator); + } + + private void Initialize(string str, char quoteChar, char separator) + { + _str = str; + _strLen = str?.Length ?? 0; + _currentTokenIndex = -1; + _quoteChar = quoteChar; + _argSeparator = separator; + + // immediately forward past any whitespace so + // NextToken() logic always starts on the first + // character of the next token. + while (_charIndex < _strLen) + { + if (!char.IsWhiteSpace(_str, _charIndex)) + { + break; + } + + ++_charIndex; + } + } + + public string GetCurrentToken() => + _currentTokenIndex < 0 ? null : _str.Substring(_currentTokenIndex, _currentTokenLength); + + internal bool NextToken() => NextToken(false); + + public bool NextToken(bool allowQuotedToken) => NextToken(allowQuotedToken, _argSeparator); + + public bool NextToken(bool allowQuotedToken, char separator) + { + _currentTokenIndex = -1; // reset the currentTokenIndex + FoundSeparator = false; // reset + + // If we're at end of the string, just return false. + if (_charIndex >= _strLen) + { + return false; + } + + var currentChar = _str[_charIndex]; + + // setup the quoteCount + var quoteCount = 0; + + // If we are allowing a quoted token and this token begins with a quote, + // set up the quote count and skip the initial quote + if (allowQuotedToken && + currentChar == _quoteChar) + { + quoteCount++; // increment quote count + ++_charIndex; // move to next character + } + + var newTokenIndex = _charIndex; + var newTokenLength = 0; + + // loop until hit end of string or hit a , or whitespace + // if at end of string ust return false. + while (_charIndex < _strLen) + { + currentChar = _str[_charIndex]; + + // if have a QuoteCount and this is a quote + // decrement the quoteCount + if (quoteCount > 0) + { + // if anything but a quoteChar we move on + if (currentChar == _quoteChar) + { + --quoteCount; + + // if at zero which it always should for now + // break out of the loop + if (0 == quoteCount) + { + ++_charIndex; // move past the quote + break; + } + } + } + else if (char.IsWhiteSpace(currentChar) || currentChar == separator) + { + if (currentChar == separator) + { + FoundSeparator = true; + } + break; + } + + ++_charIndex; + ++newTokenLength; + } + + // if quoteCount isn't zero we hit the end of the string + // before the ending quote + if (quoteCount > 0) + { + throw new InvalidOperationException("TokenizerHelperMissingEndQuote"); + } + + ScanToNextToken(separator); // move so at the start of the nextToken for next call + + // finally made it, update the _currentToken values + _currentTokenIndex = newTokenIndex; + _currentTokenLength = newTokenLength; + + if (_currentTokenLength < 1) + { + throw new InvalidOperationException("TokenizerHelperEmptyToken"); + } + + return true; + } + + private void ScanToNextToken(char separator) + { + // if already at end of the string don't bother + if (_charIndex >= _strLen) return; + + var currentChar = _str[_charIndex]; + + // check that the currentChar is a space or the separator. If not + // we have an error. this can happen in the quote case + // that the char after the quotes string isn't a char. + if (currentChar != separator && !char.IsWhiteSpace(currentChar)) + { + throw new InvalidOperationException("TokenizerHelperExtraDataEncountered"); + } + + // loop until hit a character that isn't + // an argument separator or whitespace. + // !!!Todo: if more than one argSet throw an exception + var argSepCount = 0; + while (_charIndex < _strLen) + { + currentChar = _str[_charIndex]; + + if (currentChar == separator) + { + FoundSeparator = true; + ++argSepCount; + _charIndex++; + + if (argSepCount > 1) + { + throw new InvalidOperationException("TokenizerHelperEmptyToken"); + } + } + else if (char.IsWhiteSpace(currentChar)) + { + ++_charIndex; + } + else + { + break; + } + } + + // if there was a separatorChar then we shouldn't be + // at the end of string or means there was a separator + // but there isn't an arg + + if (argSepCount > 0 && _charIndex >= _strLen) + { + throw new InvalidOperationException("TokenizerHelperEmptyToken"); + } + } + + internal static char GetNumericListSeparator(IFormatProvider provider) + { + var numericSeparator = ','; + + // Get the NumberFormatInfo out of the provider, if possible + // If the IFormatProvider doesn't not contain a NumberFormatInfo, then + // this method returns the current culture's NumberFormatInfo. + var numberFormat = NumberFormatInfo.GetInstance(provider); + + // Is the decimal separator is the same as the list separator? + // If so, we use the ";". + if (numberFormat.NumberDecimalSeparator.Length > 0 && numericSeparator == numberFormat.NumberDecimalSeparator[0]) + { + numericSeparator = ';'; + } + + return numericSeparator; + } + } +} \ No newline at end of file