Silverlight日记:字符串装换成Path对象
一,需要动态绑定Path
1 <UserControl x:Class="Secom.Emx2.SL.Common.Controls.EleSafe.Ele.Line" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 mc:Ignorable="d" 7 d:DesignHeight="300" d:DesignWidth="400"> 8 9 <Path x:Name="myline" Stretch="Fill" Width="{Binding Width, ElementName=path, Mode=TwoWay}" Height="{Binding Height, ElementName=path, Mode=TwoWay}"/> 10 </UserControl>
二,生成Path
1 private LineType _lineType = LineType.VLine; 2 public LineType LineType 3 { 4 get 5 { 6 return _lineType; 7 } 8 set 9 { 10 _lineType = value; 11 var pathString = string.Empty; 12 var eff = new DropShadowEffect(); 13 eff.BlurRadius = 5; 14 eff.Color = Colors.DarkGray; 15 eff.ShadowDepth = 0; 16 var conv = new StringToPathGeometryConverter(); 17 switch (_lineType) 18 { 19 case LineType.HLine: 20 eff.Direction = 0; 21 pathString = "M0,0 L131.137,0"; 22 break; 23 case LineType.VLine: 24 default: 25 eff.Direction = 90; 26 pathString = "M87,60 L87,240.178"; 27 break; 28 } 29 30 var pathData = conv.Convert(pathString); 31 myline.SetValue(Path.DataProperty, pathData); 32 //myline.SetValue(Path.EffectProperty, eff); 33 } 34 }
三,字符串转换成Path对象类
1 /* ============================== 2 * Desc:StringToPathGeometry 3 * Author:hezp 4 * Date:2014/8/9 17:39:40 5 * ==============================*/ 6 public class StringToPathGeometryConverter : IValueConverter 7 { 8 #region Const & Private Variables 9 const bool AllowSign = true; 10 const bool AllowComma = true; 11 const bool IsFilled = true; 12 const bool IsClosed = true; 13 14 IFormatProvider _formatProvider; 15 16 PathFigure _figure = null; // Figure object, which will accept parsed segments 17 string _pathString; // Input string to be parsed 18 int _pathLength; 19 int _curIndex; // Location to read next character from 20 bool _figureStarted; // StartFigure is effective 21 22 Point _lastStart; // Last figure starting point 23 Point _lastPoint; // Last point 24 Point _secondLastPoint; // The point before last point 25 26 char _token; // Non whitespace character returned by ReadToken 27 #endregion 28 29 #region Public Functionality 30 /// <summary> 31 /// Main conversion routine - converts string path data definition to PathGeometry object 32 /// </summary> 33 /// <param name="path">String with path data definition</param> 34 /// <returns>PathGeometry object created from string definition</returns> 35 public PathGeometry Convert(string path) 36 { 37 if (null == path) 38 throw new ArgumentException("Path string cannot be null!"); 39 40 if (path.Length == 0) 41 throw new ArgumentException("Path string cannot be empty!"); 42 43 return parse(path); 44 } 45 46 /// <summary> 47 /// Main back conversion routine - converts PathGeometry object to its string equivalent 48 /// </summary> 49 /// <param name="geometry">Path Geometry object</param> 50 /// <returns>String equivalent to PathGeometry contents</returns> 51 public string ConvertBack(PathGeometry geometry) 52 { 53 if (null == geometry) 54 throw new ArgumentException("Path Geometry cannot be null!"); 55 56 return parseBack(geometry); 57 } 58 #endregion 59 60 #region Private Functionality 61 /// <summary> 62 /// Main parser routine, which loops over each char in received string, and performs actions according to command/parameter being passed 63 /// </summary> 64 /// <param name="path">String with path data definition</param> 65 /// <returns>PathGeometry object created from string definition</returns> 66 private PathGeometry parse(string path) 67 { 68 PathGeometry _pathGeometry = null; 69 70 71 _formatProvider = CultureInfo.InvariantCulture; 72 _pathString = path; 73 _pathLength = path.Length; 74 _curIndex = 0; 75 76 _secondLastPoint = new Point(0, 0); 77 _lastPoint = new Point(0, 0); 78 _lastStart = new Point(0, 0); 79 80 _figureStarted = false; 81 82 bool first = true; 83 84 char last_cmd = ' '; 85 86 while (ReadToken()) // Empty path is allowed in XAML 87 { 88 char cmd = _token; 89 90 if (first) 91 { 92 if ((cmd != 'M') && (cmd != 'm') && (cmd != 'f') && (cmd != 'F')) // Path starts with M|m 93 { 94 ThrowBadToken(); 95 } 96 97 first = false; 98 } 99 100 switch (cmd) 101 { 102 case 'f': 103 case 'F': 104 _pathGeometry = new PathGeometry(); 105 double _num = ReadNumber(!AllowComma); 106 _pathGeometry.FillRule = _num == 0 ? FillRule.EvenOdd : FillRule.Nonzero; 107 break; 108 109 case 'm': 110 case 'M': 111 // XAML allows multiple points after M/m 112 _lastPoint = ReadPoint(cmd, !AllowComma); 113 114 _figure = new PathFigure(); 115 _figure.StartPoint = _lastPoint; 116 _figure.IsFilled = IsFilled; 117 _figure.IsClosed = !IsClosed; 118 //context.BeginFigure(_lastPoint, IsFilled, !IsClosed); 119 _figureStarted = true; 120 _lastStart = _lastPoint; 121 last_cmd = 'M'; 122 123 while (IsNumber(AllowComma)) 124 { 125 _lastPoint = ReadPoint(cmd, !AllowComma); 126 127 LineSegment _lineSegment = new LineSegment(); 128 _lineSegment.Point = _lastPoint; 129 _figure.Segments.Add(_lineSegment); 130 //context.LineTo(_lastPoint, IsStroked, !IsSmoothJoin); 131 last_cmd = 'L'; 132 } 133 break; 134 135 case 'l': 136 case 'L': 137 case 'h': 138 case 'H': 139 case 'v': 140 case 'V': 141 EnsureFigure(); 142 143 do 144 { 145 switch (cmd) 146 { 147 case 'l': _lastPoint = ReadPoint(cmd, !AllowComma); break; 148 case 'L': _lastPoint = ReadPoint(cmd, !AllowComma); break; 149 case 'h': _lastPoint.X += ReadNumber(!AllowComma); break; 150 case 'H': _lastPoint.X = ReadNumber(!AllowComma); break; 151 case 'v': _lastPoint.Y += ReadNumber(!AllowComma); break; 152 case 'V': _lastPoint.Y = ReadNumber(!AllowComma); break; 153 } 154 155 LineSegment _lineSegment = new LineSegment(); 156 _lineSegment.Point = _lastPoint; 157 _figure.Segments.Add(_lineSegment); 158 //context.LineTo(_lastPoint, IsStroked, !IsSmoothJoin); 159 } 160 while (IsNumber(AllowComma)); 161 162 last_cmd = 'L'; 163 break; 164 165 case 'c': 166 case 'C': // cubic Bezier 167 case 's': 168 case 'S': // smooth cublic Bezier 169 EnsureFigure(); 170 171 do 172 { 173 Point p; 174 175 if ((cmd == 's') || (cmd == 'S')) 176 { 177 if (last_cmd == 'C') 178 { 179 p = Reflect(); 180 } 181 else 182 { 183 p = _lastPoint; 184 } 185 186 _secondLastPoint = ReadPoint(cmd, !AllowComma); 187 } 188 else 189 { 190 p = ReadPoint(cmd, !AllowComma); 191 192 _secondLastPoint = ReadPoint(cmd, AllowComma); 193 } 194 195 _lastPoint = ReadPoint(cmd, AllowComma); 196 197 BezierSegment _bizierSegment = new BezierSegment(); 198 _bizierSegment.Point1 = p; 199 _bizierSegment.Point2 = _secondLastPoint; 200 _bizierSegment.Point3 = _lastPoint; 201 _figure.Segments.Add(_bizierSegment); 202 //context.BezierTo(p, _secondLastPoint, _lastPoint, IsStroked, !IsSmoothJoin); 203 204 last_cmd = 'C'; 205 } 206 while (IsNumber(AllowComma)); 207 208 break; 209 210 case 'q': 211 case 'Q': // quadratic Bezier 212 case 't': 213 case 'T': // smooth quadratic Bezier 214 EnsureFigure(); 215 216 do 217 { 218 if ((cmd == 't') || (cmd == 'T')) 219 { 220 if (last_cmd == 'Q') 221 { 222 _secondLastPoint = Reflect(); 223 } 224 else 225 { 226 _secondLastPoint = _lastPoint; 227 } 228 229 _lastPoint = ReadPoint(cmd, !AllowComma); 230 } 231 else 232 { 233 _secondLastPoint = ReadPoint(cmd, !AllowComma); 234 _lastPoint = ReadPoint(cmd, AllowComma); 235 } 236 237 QuadraticBezierSegment _quadraticBezierSegment = new QuadraticBezierSegment(); 238 _quadraticBezierSegment.Point1 = _secondLastPoint; 239 _quadraticBezierSegment.Point2 = _lastPoint; 240 _figure.Segments.Add(_quadraticBezierSegment); 241 //context.QuadraticBezierTo(_secondLastPoint, _lastPoint, IsStroked, !IsSmoothJoin); 242 243 last_cmd = 'Q'; 244 } 245 while (IsNumber(AllowComma)); 246 247 break; 248 249 case 'a': 250 case 'A': 251 EnsureFigure(); 252 253 do 254 { 255 // A 3,4 5, 0, 0, 6,7 256 double w = ReadNumber(!AllowComma); 257 double h = ReadNumber(AllowComma); 258 double rotation = ReadNumber(AllowComma); 259 bool large = ReadBool(); 260 bool sweep = ReadBool(); 261 262 _lastPoint = ReadPoint(cmd, AllowComma); 263 264 ArcSegment _arcSegment = new ArcSegment(); 265 _arcSegment.Point = _lastPoint; 266 _arcSegment.Size = new Size(w, h); 267 _arcSegment.RotationAngle = rotation; 268 _arcSegment.IsLargeArc = large; 269 _arcSegment.SweepDirection = sweep ? SweepDirection.Clockwise : SweepDirection.Counterclockwise; 270 _figure.Segments.Add(_arcSegment); 271 //context.ArcTo( 272 // _lastPoint, 273 // new Size(w, h), 274 // rotation, 275 // large, 276 // sweep ? SweepDirection.Clockwise : SweepDirection.Counterclockwise, 277 // IsStroked, 278 // !IsSmoothJoin 279 // ); 280 } 281 while (IsNumber(AllowComma)); 282 283 last_cmd = 'A'; 284 break; 285 286 case 'z': 287 case 'Z': 288 EnsureFigure(); 289 _figure.IsClosed = IsClosed; 290 //context.SetClosedState(IsClosed); 291 292 _figureStarted = false; 293 last_cmd = 'Z'; 294 295 _lastPoint = _lastStart; // Set reference point to be first point of current figure 296 break; 297 298 default: 299 ThrowBadToken(); 300 break; 301 } 302 303 if (null != _figure) 304 { 305 if (_figure.IsClosed) 306 { 307 if (null == _pathGeometry) 308 _pathGeometry = new PathGeometry(); 309 310 _pathGeometry.Figures.Add(_figure); 311 312 _figure = null; 313 first = true; 314 } 315 } 316 317 318 } 319 320 if (null != _figure) 321 { 322 if (null == _pathGeometry) 323 _pathGeometry = new PathGeometry(); 324 325 if (!_pathGeometry.Figures.Contains(_figure)) 326 _pathGeometry.Figures.Add(_figure); 327 328 } 329 return _pathGeometry; 330 } 331 332 void SkipDigits(bool signAllowed) 333 { 334 // Allow for a sign 335 if (signAllowed && More() && ((_pathString[_curIndex] == '-') || _pathString[_curIndex] == '+')) 336 { 337 _curIndex++; 338 } 339 340 while (More() && (_pathString[_curIndex] >= '0') && (_pathString[_curIndex] <= '9')) 341 { 342 _curIndex++; 343 } 344 } 345 346 bool ReadBool() 347 { 348 SkipWhiteSpace(AllowComma); 349 350 if (More()) 351 { 352 _token = _pathString[_curIndex++]; 353 354 if (_token == '0') 355 { 356 return false; 357 } 358 else if (_token == '1') 359 { 360 return true; 361 } 362 } 363 364 ThrowBadToken(); 365 366 return false; 367 } 368 369 private Point Reflect() 370 { 371 return new Point(2 * _lastPoint.X - _secondLastPoint.X, 372 2 * _lastPoint.Y - _secondLastPoint.Y); 373 } 374 375 private void EnsureFigure() 376 { 377 if (!_figureStarted) 378 { 379 _figure = new PathFigure(); 380 _figure.StartPoint = _lastStart; 381 382 //_context.BeginFigure(_lastStart, IsFilled, !IsClosed); 383 _figureStarted = true; 384 } 385 } 386 387 double ReadNumber(bool allowComma) 388 { 389 if (!IsNumber(allowComma)) 390 { 391 ThrowBadToken(); 392 } 393 394 bool simple = true; 395 int start = _curIndex; 396 397 // 398 // Allow for a sign 399 // 400 // There are numbers that cannot be preceded with a sign, for instance, -NaN, but it's 401 // fine to ignore that at this point, since the CLR parser will catch this later. 402 // 403 if (More() && ((_pathString[_curIndex] == '-') || _pathString[_curIndex] == '+')) 404 { 405 _curIndex++; 406 } 407 408 // Check for Infinity (or -Infinity). 409 if (More() && (_pathString[_curIndex] == 'I')) 410 { 411 // 412 // Don't bother reading the characters, as the CLR parser will 413 // do this for us later. 414 // 415 _curIndex = Math.Min(_curIndex + 8, _pathLength); // "Infinity" has 8 characters 416 simple = false; 417 } 418 // Check for NaN 419 else if (More() && (_pathString[_curIndex] == 'N')) 420 { 421 // 422 // Don't bother reading the characters, as the CLR parser will 423 // do this for us later. 424 // 425 _curIndex = Math.Min(_curIndex + 3, _pathLength); // "NaN" has 3 characters 426 simple = false; 427 } 428 else 429 { 430 SkipDigits(!AllowSign); 431 432 // Optional period, followed by more digits 433 if (More() && (_pathString[_curIndex] == '.')) 434 { 435 simple = false; 436 _curIndex++; 437 SkipDigits(!AllowSign); 438 } 439 440 // Exponent 441 if (More() && ((_pathString[_curIndex] == 'E') || (_pathString[_curIndex] == 'e'))) 442 { 443 simple = false; 444 _curIndex++; 445 SkipDigits(AllowSign); 446 } 447 } 448 449 if (simple && (_curIndex <= (start + 8))) // 32-bit integer 450 { 451 int sign = 1; 452 453 if (_pathString[start] == '+') 454 { 455 start++; 456 } 457 else if (_pathString[start] == '-') 458 { 459 start++; 460 sign = -1; 461 } 462 463 int value = 0; 464 465 while (start < _curIndex) 466 { 467 value = value * 10 + (_pathString[start] - '0'); 468 start++; 469 } 470 471 return value * sign; 472 } 473 else 474 { 475 string subString = _pathString.Substring(start, _curIndex - start); 476 477 try 478 { 479 return System.Convert.ToDouble(subString, _formatProvider); 480 } 481 catch (FormatException except) 482 { 483 throw new FormatException(string.Format("Unexpected character in path '{0}' at position {1}", _pathString, _curIndex - 1), except); 484 } 485 } 486 } 487 488 private bool IsNumber(bool allowComma) 489 { 490 bool commaMet = SkipWhiteSpace(allowComma); 491 492 if (More()) 493 { 494 _token = _pathString[_curIndex]; 495 496 // Valid start of a number 497 if ((_token == '.') || (_token == '-') || (_token == '+') || ((_token >= '0') && (_token <= '9')) 498 || (_token == 'I') // Infinity 499 || (_token == 'N')) // NaN 500 { 501 return true; 502 } 503 } 504 505 if (commaMet) // Only allowed between numbers 506 { 507 ThrowBadToken(); 508 } 509 510 return false; 511 } 512 513 private Point ReadPoint(char cmd, bool allowcomma) 514 { 515 double x = ReadNumber(allowcomma); 516 double y = ReadNumber(AllowComma); 517 518 if (cmd >= 'a') // 'A' < 'a'. lower case for relative 519 { 520 x += _lastPoint.X; 521 y += _lastPoint.Y; 522 } 523 524 return new Point(x, y); 525 } 526 527 private bool ReadToken() 528 { 529 SkipWhiteSpace(!AllowComma); 530 531 // Check for end of string 532 if (More()) 533 { 534 _token = _pathString[_curIndex++]; 535 536 return true; 537 } 538 else 539 { 540 return false; 541 } 542 } 543 544 bool More() 545 { 546 return _curIndex < _pathLength; 547 } 548 549 // Skip white space, one comma if allowed 550 private bool SkipWhiteSpace(bool allowComma) 551 { 552 bool commaMet = false; 553 554 while (More()) 555 { 556 char ch = _pathString[_curIndex]; 557 558 switch (ch) 559 { 560 case ' ': 561 case '\n': 562 case '\r': 563 case '\t': // SVG whitespace 564 break; 565 566 case ',': 567 if (allowComma) 568 { 569 commaMet = true; 570 allowComma = false; // one comma only 571 } 572 else 573 { 574 ThrowBadToken(); 575 } 576 break; 577 578 default: 579 // Avoid calling IsWhiteSpace for ch in (' ' .. 'z'] 580 if (((ch > ' ') && (ch <= 'z')) || !Char.IsWhiteSpace(ch)) 581 { 582 return commaMet; 583 } 584 break; 585 } 586 587 _curIndex++; 588 } 589 590 return commaMet; 591 } 592 593 private void ThrowBadToken() 594 { 595 throw new FormatException(string.Format("Unexpected character in path '{0}' at position {1}", _pathString, _curIndex - 1)); 596 } 597 598 static internal char GetNumericListSeparator(IFormatProvider provider) 599 { 600 char numericSeparator = ','; 601 602 // Get the NumberFormatInfo out of the provider, if possible 603 // If the IFormatProvider doesn't not contain a NumberFormatInfo, then 604 // this method returns the current culture's NumberFormatInfo. 605 NumberFormatInfo numberFormat = NumberFormatInfo.GetInstance(provider); 606 607 // Is the decimal separator is the same as the list separator? 608 // If so, we use the ";". 609 if ((numberFormat.NumberDecimalSeparator.Length > 0) && (numericSeparator == numberFormat.NumberDecimalSeparator[0])) 610 { 611 numericSeparator = ';'; 612 } 613 614 return numericSeparator; 615 } 616 617 private string parseBack(PathGeometry geometry) 618 { 619 System.Text.StringBuilder sb = new System.Text.StringBuilder(); 620 IFormatProvider provider = new System.Globalization.CultureInfo("en-us"); 621 string format = null; 622 623 sb.Append("F" + (geometry.FillRule == FillRule.EvenOdd ? "0" : "1") + " "); 624 625 foreach (PathFigure figure in geometry.Figures) 626 { 627 sb.Append("M " + ((IFormattable)figure.StartPoint).ToString(format, provider) + " "); 628 629 foreach (PathSegment segment in figure.Segments) 630 { 631 char separator = GetNumericListSeparator(provider); 632 633 if (segment.GetType() == typeof(LineSegment)) 634 { 635 LineSegment _lineSegment = segment as LineSegment; 636 637 sb.Append("L " + ((IFormattable)_lineSegment.Point).ToString(format, provider) + " "); 638 } 639 else if (segment.GetType() == typeof(BezierSegment)) 640 { 641 BezierSegment _bezierSegment = segment as BezierSegment; 642 643 sb.Append(String.Format(provider, 644 "C{1:" + format + "}{0}{2:" + format + "}{0}{3:" + format + "} ", 645 separator, 646 _bezierSegment.Point1, 647 _bezierSegment.Point2, 648 _bezierSegment.Point3 649 )); 650 } 651 else if (segment.GetType() == typeof(QuadraticBezierSegment)) 652 { 653 QuadraticBezierSegment _quadraticBezierSegment = segment as QuadraticBezierSegment; 654 655 sb.Append(String.Format(provider, 656 "Q{1:" + format + "}{0}{2:" + format + "} ", 657 separator, 658 _quadraticBezierSegment.Point1, 659 _quadraticBezierSegment.Point2)); 660 } 661 else if (segment.GetType() == typeof(ArcSegment)) 662 { 663 ArcSegment _arcSegment = segment as ArcSegment; 664 665 sb.Append(String.Format(provider, 666 "A{1:" + format + "}{0}{2:" + format + "}{0}{3}{0}{4}{0}{5:" + format + "} ", 667 separator, 668 _arcSegment.Size, 669 _arcSegment.RotationAngle, 670 _arcSegment.IsLargeArc ? "1" : "0", 671 _arcSegment.SweepDirection == SweepDirection.Clockwise ? "1" : "0", 672 _arcSegment.Point)); 673 } 674 } 675 676 if (figure.IsClosed) 677 sb.Append("Z"); 678 } 679 680 return sb.ToString(); 681 } 682 #endregion 683 684 #region IValueConverter Members 685 686 public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 687 { 688 string path = value as string; 689 if (null != path) 690 return Convert(path); 691 else 692 return null; 693 } 694 695 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 696 { 697 PathGeometry geometry = value as PathGeometry; 698 699 if (null != geometry) 700 return ConvertBack(geometry); 701 else 702 return default(string); 703 } 704 705 #endregion 706 }