WPF Canvas 平滑筆跡

?WPF Canvas 平滑筆跡

控件名:CanvasHandWriting

作者:小封(鄺攀升)

原文鏈接:? ?https://github.com/WPFDevelopersOrg/WPFDevelopers

編輯:驚鏵

完整的思路如下

  • 收集路徑點集。

  • 平均采樣路徑點集。

  • 將路徑點集轉為 LineB

  • LineB 數據傳給 Path

4ec686048b47b36e3752d68e89a66376.gif

1)Vector2D.cs 代碼如下

using?System;
using?System.Collections.Generic;
using?System.Linq;
using?System.Text;namespace?WPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{public?class?Vector2D{public?double?X?{?get;?set;?}?=?0;public?double?Y?{?get;?set;?}?=?0;///?<summary>///?向量的模///?</summary>public?double?Mold{get{//自身各分量平方運算.double?X?=?this.X?*?this.X;double?Y?=?this.Y?*?this.Y;return?Math.Sqrt(X?+?Y);//開根號,最終返回向量的長度/模/大小.}}///?<summary>///?單位向量///?</summary>public?Vector2D?UnitVector{get{double?sumSquares?=?(X?*?X)?+?(Y?*?Y);return?new?Vector2D(X?/?Math.Sqrt(sumSquares),?Y?/?Math.Sqrt(sumSquares));}}public?Vector2D(){}public?Vector2D(double?x,?double?y){X?=?x;Y?=?y;}public?Vector2D(System.Windows.Point?point){X?=?point.X;Y?=?point.Y;}public?void?Offset(double?angle,?double?distance,?AngleType?angleType?=?AngleType.Radian){var?vector2D?=?Vector2D.CalculateVectorOffset(this,?angle,?distance,?angleType);X?=?vector2D.X;Y?=?vector2D.Y;}public?void?Rotate(double?angle,?Vector2D?vectorCenter?=?null,?AngleType?angleType?=?AngleType.Radian){vectorCenter?=?vectorCenter?==?null???this?:?vectorCenter;var?vector2D?=?Vector2D.CalculateVectorRotation(this,?vectorCenter,?angle,?angleType);X?=?vector2D.X;Y?=?vector2D.Y;}#region?靜態方法///?<summary>///?計算兩個向量之間的距離///?</summary>public?static?double?CalculateVectorDistance(Vector2D?vector2DA,?Vector2D?vector2DB){Vector2D?vector2D?=?vector2DA?-?vector2DB;return?vector2D.Mold;}///?<summary>///?計算兩點夾角,右側X軸線為0度,向下為正,向上為負///?</summary>public?static?double?IncludedAngleXAxis(Vector2D?vector2DA,?Vector2D?vector2DB,?AngleType?angleType?=?AngleType.Radian){double?radian?=?Math.Atan2(vector2DB.Y?-?vector2DA.Y,?vector2DB.X?-?vector2DA.X);?//弧度:1.1071487177940904return?angleType?==?AngleType.Radian???radian?:?ComputingHelper.RadianToAngle(radian);}///?<summary>///?計算兩點夾角,下側Y軸線為0度,向右為正,向左為負///?</summary>public?static?double?IncludedAngleYAxis(Vector2D?vector2DA,?Vector2D?vector2DB,?AngleType?angleType?=?AngleType.Radian){double?radian?=?Math.Atan2(vector2DB.X?-?vector2DA.X,?vector2DB.Y?-?vector2DA.Y);?//弧度:0.46364760900080609return?angleType?==?AngleType.Radian???radian?:?ComputingHelper.RadianToAngle(radian);}///?<summary>///?偏移向量到指定角度,指定距離///?</summary>public?static?Vector2D?CalculateVectorOffset(Vector2D?vector2D,?double?angle,?double?distance,?AngleType?angleType?=?AngleType.Radian){Vector2D?pointVector2D?=?new?Vector2D();if?(angleType?==?AngleType.Angle){angle?=?angle?/?(180?/?Math.PI);//角度轉弧度}double?width?=?Math.Cos(Math.Abs(angle))?*?distance;double?height?=?Math.Sin(Math.Abs(angle))?*?distance;if(angle?<=?Math.PI?&&?angle?>=?0)//if?(angle?is?<=?Math.PI?and?>=?0){pointVector2D.X?=?vector2D.X?-?width;pointVector2D.Y?=?vector2D.Y?-?height;}if?(angle?>=?(-Math.PI)?&&?angle?<=?0)//if?(angle?is?>=?(-Math.PI)?and?<=?0){pointVector2D.X?=?vector2D.X?-?width;pointVector2D.Y?=?vector2D.Y?+?height;}return?pointVector2D;}///?<summary>///?圍繞一個中心點,旋轉一個向量,相對旋轉///?</summary>public?static?Vector2D?CalculateVectorRotation(Vector2D?vector2D,?Vector2D?vectorCenter,?double?radian,?AngleType?angleType?=?AngleType.Radian){radian?=?angleType?==?AngleType.Radian???radian?:?ComputingHelper.RadianToAngle(radian);double?x1?=?(vector2D.X?-?vectorCenter.X)?*?Math.Sin(radian)?+?(vector2D.Y?-?vectorCenter.Y)?*?Math.Cos(radian)?+?vectorCenter.X;double?y1?=?-(vector2D.X?-?vectorCenter.X)?*?Math.Cos(radian)?+?(vector2D.Y?-?vectorCenter.Y)?*?Math.Sin(radian)?+?vectorCenter.Y;return?new?Vector2D(x1,?y1);}public?static?Vector2D?CalculateVectorCenter(Vector2D?vector2DA,?Vector2D?vector2DB){return?new?Vector2D((vector2DA.X?+?vector2DB.X)?/?2,?(vector2DA.Y?+?vector2DB.Y)?/?2);}///?<summary>///?判斷坐標點是否在多邊形區域內,射線法///?</summary>public?static?bool?IsPointPolygonalArea(Vector2D?vector2D,?List<Vector2D>?aolygonaArrayList){var?N?=?aolygonaArrayList.Count;var?boundOrVertex?=?true;?//如果點位于多邊形的頂點或邊上,也算做點在多邊形內,直接返回truevar?crossNumber?=?0;?//x的交叉點計數var?precision?=?2e-10;?//浮點類型計算時候與0比較時候的容差Vector2D?p1,?p2;?//neighbour?bound?verticesvar?p?=?vector2D;?//測試點p1?=?aolygonaArrayList[0];?//left?vertex????????for?(var?i?=?1;?i?<=?N;?++i){//check?all?rays????????????if?(p.X.Equals(p1.X)?&&?p.Y.Equals(p1.Y)){return?boundOrVertex;?//p?is?an?vertex}p2?=?aolygonaArrayList[i?%?N];?//right?vertex????????????if?(p.X?<?Math.Min(p1.X,?p2.X)?||?p.X?>?Math.Max(p1.X,?p2.X)){//ray?is?outside?of?our?interests????????????????p1?=?p2;continue;?//next?ray?left?point}if?(p.X?>?Math.Min(p1.X,?p2.X)?&&?p.X?<?Math.Max(p1.X,?p2.X)){//ray?is?crossing?over?by?the?algorithm?(common?part?of)if?(p.Y?<=?Math.Max(p1.Y,?p2.Y)){//x?is?before?of?ray????????????????????if?(p1.X?==?p2.X?&&?p.Y?>=?Math.Min(p1.Y,?p2.Y)){//overlies?on?a?horizontal?rayreturn?boundOrVertex;}if?(p1.Y?==?p2.Y){//ray?is?vertical????????????????????????if?(p1.Y?==?p.Y){//overlies?on?a?vertical?rayreturn?boundOrVertex;}else{//before?ray++crossNumber;}}else{//cross?point?on?the?left?side????????????????????????var?xinters?=(p.X?-?p1.X)?*?(p2.Y?-?p1.Y)?/?(p2.X?-?p1.X)?+p1.Y;?//cross?point?of?Y????????????????????????if?(Math.Abs(p.Y?-?xinters)?<?precision){//overlies?on?a?rayreturn?boundOrVertex;}if?(p.Y?<?xinters){//before?ray++crossNumber;}}}}else{//special?case?when?ray?is?crossing?through?the?vertex????????????????if?(p.X?==?p2.X?&&?p.Y?<=?p2.Y){//p?crossing?over?p2????????????????????var?p3?=?aolygonaArrayList[(i?+?1)?%?N];?//next?vertex????????????????????if?(p.X?>=?Math.Min(p1.X,?p3.X)?&&?p.X?<=?Math.Max(p1.X,?p3.X)){//p.X?lies?between?p1.X?&?p3.X++crossNumber;}else{crossNumber?+=?2;}}}p1?=?p2;?//next?ray?left?point}if?(crossNumber?%?2?==?0){//偶數在多邊形外return?false;}else{//奇數在多邊形內return?true;}}///?<summary>///?判斷一個點是否在一條邊內///?</summary>public?static?bool?IsPointEdge(Vector2D?point,?Vector2D?startPoint,?Vector2D?endPoint){return?(point.X?-?startPoint.X)?*?(endPoint.Y?-?startPoint.Y)?==?(endPoint.X?-?startPoint.X)?*?(point.Y?-?startPoint.Y)&&?Math.Min(startPoint.X,?endPoint.X)?<=?point.X?&&?point.X?<=?Math.Max(startPoint.X,?endPoint.X)&&?Math.Min(startPoint.Y,?endPoint.Y)?<=?point.Y?&&?point.Y?<=?Math.Max(startPoint.Y,?endPoint.Y);}#endregion?靜態方法#region?運算符重載///?<summary>///?重載運算符,和運算,可以用來計算兩向量距離///?</summary>public?static?Vector2D?operator?+(Vector2D?vector2DA,?Vector2D?vector2DB){Vector2D?vector2D?=?new?Vector2D();vector2D.X?=?vector2DA.X?+?vector2DB.X;vector2D.Y?=?vector2DA.Y?+?vector2DB.Y;return?vector2D;}///?<summary>///?重載運算符,差運算,可以用來計算兩向量距離///?</summary>public?static?Vector2D?operator?-(Vector2D?vector2DA,?Vector2D?vector2DB){Vector2D?vector2D?=?new?Vector2D();vector2D.X?=?vector2DA.X?-?vector2DB.X;vector2D.Y?=?vector2DA.Y?-?vector2DB.Y;return?vector2D;}///?<summary>///?重載運算符,差運算,可以用來計算兩向量距離///?</summary>public?static?Vector2D?operator?-(Vector2D?vector2D,?double?_float){return?new?Vector2D(vector2D.X?-?_float,?vector2D.Y?-?_float);}///?<summary>///?重載運算符,點積運算,可以用來計算兩向量夾角///?</summary>public?static?double?operator?*(Vector2D?vector2DA,?Vector2D?vector2DB){return?(vector2DA.X?*?vector2DB.X)?+?(vector2DA.Y?*?vector2DB.Y);}public?static?double?operator?*(Vector2D?vector2D,?double?_float){return?(vector2D.X?*?_float)?+?(vector2D.Y?*?_float);}///?<summary>///?重載運算符,點積運算,可以用來計算兩向量夾角///?</summary>public?static?double?operator?/(Vector2D?vector2D,?double?para){return?(vector2D.X?/?para)?+?(vector2D.Y?/?para);}///?<summary>///?重載運算符///?</summary>public?static?bool?operator?>=(Vector2D?vector2D,?double?para){if?(vector2D.Mold?>=?para){return?true;}else{return?false;}}public?static?bool?operator?<=(Vector2D?vector2D,?double?para){if?(vector2D.Mold?<=?para){return?true;}else{return?false;}}public?static?bool?operator?>(Vector2D?vector2D,?double?para){if?(vector2D.Mold?>?para){return?true;}else{return?false;}}public?static?bool?operator?<(Vector2D?vector2D,?double?para){if?(vector2D.Mold?<?para){return?true;}else{return?false;}}#endregion?運算符重載#region?隱式轉換///?<summary>///?重載隱式轉換,可以直接使用Point///?</summary>///?<param?name="v"></param>public?static?implicit?operator?Vector2D(System.Windows.Point?v)//隱式轉換{return?new?Vector2D(v.X,?v.Y);}///?<summary>///?重載隱式轉換,可以直接使用Point///?</summary>///?<param?name="v"></param>public?static?implicit?operator?System.Windows.Point(Vector2D?v)//隱式轉換{return?new?System.Windows.Point(v.X,?v.Y);}///?<summary>///?重載隱式轉換,可以直接使用double///?</summary>///?<param?name="v"></param>public?static?implicit?operator?Vector2D(double?v)//隱式轉換{return?new?Vector2D(v,?v);}#endregion?隱式轉換#region?ToStringpublic?override?string?ToString(){return?X.ToString()?+?","?+?Y.ToString();}public?string?ToString(string?symbol){return?X.ToString()?+?symbol?+?Y.ToString();}public?string?ToString(string?sender,?string?symbol){return?X.ToString(sender)?+?symbol?+?Y.ToString(sender);}#endregion}public?enum?AngleType?{Angle,Radian}
}

2)ComputingHelper.cs 代碼如下

using?System;
using?System.Collections.Generic;
using?System.Linq;
using?System.Text;namespace?WPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{public?static?class?ComputingHelper{public?static?double?AngleToRadian(double?angle){return?angle?*?(Math.PI?/?180);}public?static?double?RadianToAngle(double?radian){return?radian?*?(180?/?Math.PI);}///?<summary>///?將一個值從一個范圍映射到另一個范圍///?</summary>public?static?double?RangeMapping(double?inputValue,?double?enterLowerLimit,?double?enterUpperLimit,?double?outputLowerLimit,?double?OutputUpperLimit,?CurveType?curveType?=?CurveType.None){var?percentage?=?(enterUpperLimit?-?inputValue)?/?(enterUpperLimit?-?enterLowerLimit);switch?(curveType){case?CurveType.Sine:percentage?=?Math.Sin(percentage);break;case?CurveType.CoSine:percentage?=?Math.Cos(percentage);break;case?CurveType.Tangent:percentage?=?Math.Tan(percentage);break;case?CurveType.Cotangent:percentage?=?Math.Atan(percentage);break;default:break;}double?outputValue?=?OutputUpperLimit?-?((OutputUpperLimit?-?outputLowerLimit)?*?percentage);return?outputValue;}public?static?string?ByteToKB(double?_byte){List<string>?unit?=?new?List<string>()?{?"B",?"KB",?"MB",?"GB",?"TB",?"P",?"PB"?};int?i?=?0;while?(_byte?>?1024){_byte?/=?1024;i++;}_byte?=?Math.Round(_byte,?3);//保留三位小數return?_byte?+?unit[i];}///?<summary>///?縮短一個數組,對其進行平均采樣///?</summary>public?static?double[]?AverageSampling(double[]?sourceArray,?int?number){if?(sourceArray.Length?<=?number){return?sourceArray;//throw?new?Exception("新的數組必須比原有的要小!");}double[]?arrayList?=?new?double[number];double?stride?=?(double)sourceArray.Length?/?number;for?(int?i?=?0,?jIndex?=?0;?i?<?number;?i++,?jIndex++){double?strideIncrement?=?i?*?stride;strideIncrement?=?Math.Round(strideIncrement,?6);double?sum?=?0;int?firstIndex?=?(int)(strideIncrement);double?firstDecimal?=?strideIncrement?-?firstIndex;int?tailIndex?=?(int)(strideIncrement?+?stride);double?tailDecimal?=?(strideIncrement?+?stride)?-?tailIndex;if?(firstDecimal?!=?0)sum?+=?sourceArray[firstIndex]?*?(1?-?firstDecimal);if?(tailDecimal?!=?0?&&?tailIndex?!=?sourceArray.Length)sum?+=?sourceArray[tailIndex]?*?(tailDecimal);int?startIndex?=?firstDecimal?==?0???firstIndex?:?firstIndex?+?1;int?endIndex?=?tailIndex;for?(int?j?=?startIndex;?j?<?endIndex;?j++)sum?+=?sourceArray[j];arrayList[jIndex]?=?sum?/?stride;}return?arrayList;}public?static?List<Vector2D>?AverageSampling(List<Vector2D>?sourceArray,?int?number){if?(sourceArray.Count?<=?number?-?2){return?sourceArray;}double[]?x?=?new?double[sourceArray.Count];double[]?y?=?new?double[sourceArray.Count];for?(int?i?=?0;?i?<?sourceArray.Count;?i++){x[i]?=?sourceArray[i].X;y[i]?=?sourceArray[i].Y;}double[]?X?=?AverageSampling(x,?number?-?2);double[]?Y?=?AverageSampling(y,?number?-?2);List<Vector2D>?arrayList?=?new?List<Vector2D>();for?(int?i?=?0;?i?<?number?-?2;?i++){arrayList.Add(new?Vector2D(X[i],?Y[i]));}arrayList.Insert(0,?sourceArray[0]);//添加首arrayList.Add(sourceArray[sourceArray.Count?-?1]);//添加尾return?arrayList;}}public?enum?CurveType?{Sine,CoSine,Tangent,Cotangent,None}
}

3)LineB.cs 代碼如下

using?System;
using?System.Collections.Generic;
using?System.Linq;
using?System.Text;
using?System.Windows.Media;namespace?WPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{public?class?LineB{private?List<Vector2D>?_vector2DList?=?new?List<Vector2D>();public?List<Vector2D>?Vector2DList{get?{?return?_vector2DList;?}set{_vector2DList?=?value;}}private?List<BezierCurve>?_bezierCurveList?=?new?List<BezierCurve>();public?List<BezierCurve>?BezierCurveList{get?{?return?_bezierCurveList;?}private?set?{?_bezierCurveList?=?value;?}}private?double?_tension?=?0.618;public?double?Tension{get?{?return?_tension;?}set{_tension?=?value;if?(_tension?>?10)_tension?=?10;if?(_tension?<?0)_tension?=?0;}}private?bool?_isClosedCurve?=?true;public?bool?IsClosedCurve{get?{?return?_isClosedCurve;?}set?{?_isClosedCurve?=?value;?}}private?string?_pathData?=?string.Empty;public?string?PathData{get{if?(_pathData?==?string.Empty){_pathData?=?Vector2DToBezierCurve();}return?_pathData;}}private?string?Vector2DToBezierCurve()?{if?(Vector2DList.Count?<?3)return?string.Empty;BezierCurveList.Clear();for?(int?i?=?0;?i?<?Vector2DList.Count;?i++){int?pointTwoIndex?=?i?+?1?<?Vector2DList.Count???i?+?1?:?0;int?pointThreeIndex?=?i?+?2?<?Vector2DList.Count???i?+?2?:?i?+?2?-?Vector2DList.Count;Vector2D?vector2D1?=?Vector2DList[i];Vector2D?vector2D2?=?Vector2DList[pointTwoIndex];Vector2D?vector2D3?=?Vector2DList[pointThreeIndex];Vector2D?startVector2D?=?Vector2D.CalculateVectorCenter(vector2D1,?vector2D2);double?startAngle?=?Vector2D.IncludedAngleXAxis(vector2D1,?vector2D2);double?startDistance?=?Vector2D.CalculateVectorDistance(startVector2D,?vector2D2)?*?(1?-?Tension);Vector2D?startControlPoint?=?Vector2D.CalculateVectorOffset(vector2D2,?startAngle,?startDistance);Vector2D?endVector2D?=?Vector2D.CalculateVectorCenter(vector2D2,?vector2D3);double?endAngle?=?Vector2D.IncludedAngleXAxis(endVector2D,?vector2D2);double?endDistance?=?Vector2D.CalculateVectorDistance(endVector2D,?vector2D2)?*?(1?-?Tension);Vector2D?endControlPoint?=?Vector2D.CalculateVectorOffset(endVector2D,?endAngle,?endDistance);BezierCurve?bezierCurve?=?new?BezierCurve();bezierCurve.StartVector2D?=?startVector2D;bezierCurve.StartControlPoint?=?startControlPoint;bezierCurve.EndVector2D?=?endVector2D;bezierCurve.EndControlPoint?=?endControlPoint;BezierCurveList.Add(bezierCurve);}if?(!IsClosedCurve){BezierCurveList[0].StartVector2D?=?Vector2DList[0];BezierCurveList.RemoveAt(BezierCurveList.Count?-?1);BezierCurveList[BezierCurveList.Count?-?1].EndVector2D?=?Vector2DList[Vector2DList.Count?-?1];BezierCurveList[BezierCurveList.Count?-?1].EndControlPoint?=?BezierCurveList[BezierCurveList.Count?-?1].EndVector2D;}string?path?=?$"M?{BezierCurveList[0].StartVector2D.ToString()}?";foreach?(var?item?in?BezierCurveList){path?+=?$"C?{item.StartControlPoint.ToString("?")},{item.EndControlPoint.ToString("?")},{item.EndVector2D.ToString("?")}?";}return?path;}public?LineB(){}public?LineB(List<Vector2D>?verVector2DList,?bool?isClosedCurve?=?true){this.Vector2DList?=?verVector2DList;this.IsClosedCurve?=?isClosedCurve;}///?<summary>///?重載隱式轉換,可以直接使用Point///?</summary>///?<param?name="v"></param>public?static?implicit?operator?Geometry(LineB?lineB)//隱式轉換{return?Geometry.Parse(lineB.PathData);}}public?class?BezierCurve{private?Vector2D?_startVector2D?=?new?Vector2D(0,?0);public?Vector2D?StartVector2D{get?{?return?_startVector2D;?}set?{?_startVector2D?=?value;?}}private?Vector2D?_startControlPoint?=?new?Vector2D(0,?100);public?Vector2D?StartControlPoint{get?{?return?_startControlPoint;?}set?{?_startControlPoint?=?value;?}}private?Vector2D?_endControlPoint?=?new?Vector2D(100,?0);public?Vector2D?EndControlPoint{get?{?return?_endControlPoint;?}set?{?_endControlPoint?=?value;?}}private?Vector2D?_endVector2D?=?new?Vector2D(100,?100);public?Vector2D?EndVector2D{get?{?return?_endVector2D;?}set?{?_endVector2D?=?value;?}}}
}

4)CanvasHandWritingExample.xaml 代碼如下

<UserControl?x:Class="WPFDevelopers.Samples.ExampleViews.CanvasHandWriting.CanvasHandWritingExample"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"?xmlns:d="http://schemas.microsoft.com/expression/blend/2008"?xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews.CanvasHandWriting"mc:Ignorable="d"?d:DesignHeight="450"?d:DesignWidth="800"><UserControl.Resources><Style?TargetType="{x:Type?TextBlock}"><Setter?Property="Foreground"?Value="{StaticResource?PrimaryTextSolidColorBrush}"?/></Style></UserControl.Resources><Grid><Grid.RowDefinitions><RowDefinition?Height="auto"/><RowDefinition/></Grid.RowDefinitions><StackPanel?Orientation="Horizontal"?Margin="4"><TextBlock?Text="張力:"?VerticalAlignment="Center"/><TextBox?Text="{Binding?Tension,RelativeSource={RelativeSource?AncestorType=local:CanvasHandWritingExample}}"/><Slider?Width="100"?SmallChange="0.01"?Value="{Binding?Tension,RelativeSource={RelativeSource?AncestorType=local:CanvasHandWritingExample}}"?Maximum="1"?VerticalAlignment="Center"?Margin="5,0"/><TextBlock??Text="平滑采樣:"?VerticalAlignment="Center"/><TextBox?Text="{Binding?SmoothSampling,RelativeSource={RelativeSource?AncestorType=local:CanvasHandWritingExample}}"Margin="5,0"/><Slider?Value="{Binding?SmoothSampling,RelativeSource={RelativeSource?AncestorType=local:CanvasHandWritingExample}}"Width="100"?VerticalAlignment="Center"?SmallChange="0.01"?Maximum="1"?TickFrequency="0.1"/><CheckBox?Content="橡皮擦"?VerticalAlignment="Center"Margin="5,0"IsChecked="{Binding?IsEraser,RelativeSource={RelativeSource?AncestorType=local:CanvasHandWritingExample}}"/><Button?Content="清空畫布"?Click="btnClertCanvas_Click"/></StackPanel><Canvas?x:Name="drawingCanvas"?Grid.Row="1"?Background="Black"?PreviewMouseLeftButtonDown="DrawingCanvas_PreviewMouseLeftButtonDown"PreviewMouseMove="DrawingCanvas_PreviewMouseMove"PreviewMouseLeftButtonUp="DrawingCanvas_PreviewMouseLeftButtonUp"/></Grid>
</UserControl>

5)CanvasHandWritingExample.xaml.cs 代碼如下

using?System;
using?System.Collections.Generic;
using?System.Threading;
using?System.Threading.Tasks;
using?System.Windows;
using?System.Windows.Controls;
using?System.Windows.Input;
using?System.Windows.Media;
using?System.Windows.Shapes;namespace?WPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{///?<summary>///?????CanvasHandWritingExample.xaml?的交互邏輯///?</summary>public?partial?class?CanvasHandWritingExample?:?UserControl{public?static?readonly?DependencyProperty?TensionProperty?=DependencyProperty.Register("Tension",?typeof(double),?typeof(CanvasHandWritingExample),new?PropertyMetadata(0.618));public?static?readonly?DependencyProperty?SmoothSamplingProperty?=DependencyProperty.Register("SmoothSampling",?typeof(double),?typeof(CanvasHandWritingExample),new?UIPropertyMetadata(OnSmoothSamplingChanged));public?static?readonly?DependencyProperty?IsEraserProperty?=DependencyProperty.Register("IsEraser",?typeof(bool),?typeof(CanvasHandWritingExample),new?PropertyMetadata(false));private?readonly?Dictionary<Path,?List<Vector2D>>?_PathVector2DDictionary?;volatile?bool?_IsStart?=?false;Path?_DrawingPath?=?default;private?static?void?OnSmoothSamplingChanged(DependencyObject?d,?DependencyPropertyChangedEventArgs?e){var?mWindow?=?(CanvasHandWritingExample)d;foreach?(var?item?in?mWindow._PathVector2DDictionary.Keys){mWindow.DrawLine(item);}}public?CanvasHandWritingExample(){InitializeComponent();_PathVector2DDictionary?=?new?Dictionary<Path,?List<Vector2D>>();SmoothSampling?=?0.8;}private?void?DrawingCanvas_PreviewMouseLeftButtonDown(object?sender,?MouseButtonEventArgs?e){_IsStart?=?true;_DrawingPath?=?new?Path(){StrokeDashCap?=?PenLineCap.Round,StrokeStartLineCap?=?PenLineCap.Round,StrokeEndLineCap?=?PenLineCap.Round,StrokeLineJoin?=?PenLineJoin.Round,};if?(IsEraser){_DrawingPath.Stroke?=?new?SolidColorBrush(Colors.Black);_DrawingPath.StrokeThickness?=?40;}else{var?random?=?new?Random();var?strokeBrush?=?new?SolidColorBrush(Color.FromRgb((byte)random.Next(200,?255),?(byte)random.Next(0,?255),?(byte)random.Next(0,?255)));_DrawingPath.Stroke?=?strokeBrush;_DrawingPath.StrokeThickness?=?10;}_PathVector2DDictionary.Add(_DrawingPath,?new?List<Vector2D>());drawingCanvas.Children.Add(_DrawingPath);}private?void?DrawingCanvas_PreviewMouseLeftButtonUp(object?sender,?MouseButtonEventArgs?e){_IsStart?=?false;_DrawingPath?=?default;}private?void?DrawingCanvas_PreviewMouseMove(object?sender,?MouseEventArgs?e){if?(!_IsStart)return;if?(_DrawingPath?is?null)return;Vector2D?currenPoint?=?e.GetPosition(drawingCanvas);if?(currenPoint.X?<?0?||?currenPoint.Y?<?0)return;if?(currenPoint.X?>?drawingCanvas.ActualWidth?||?currenPoint.Y?>?drawingCanvas.ActualHeight)return;if?(_PathVector2DDictionary[_DrawingPath].Count?>?0){if?(Vector2D.CalculateVectorDistance(currenPoint,?_PathVector2DDictionary[_DrawingPath][_PathVector2DDictionary[_DrawingPath].Count?-?1])?>?1)_PathVector2DDictionary[_DrawingPath].Add(e.GetPosition(drawingCanvas));}else_PathVector2DDictionary[_DrawingPath].Add(e.GetPosition(drawingCanvas));DrawLine(_DrawingPath);}public?double?Tension{get?=>?(double)GetValue(TensionProperty);set?=>?SetValue(TensionProperty,?value);}public?double?SmoothSampling{get?=>?(double)GetValue(SmoothSamplingProperty);set?=>?SetValue(SmoothSamplingProperty,?value);}public?bool?IsEraser{get?=>?(bool)GetValue(IsEraserProperty);set?=>?SetValue(IsEraserProperty,?value);}private?void?DrawLine(Path?path){if?(_PathVector2DDictionary[path].Count?>?2){var?pathVector2Ds?=?_PathVector2DDictionary[path];var?smoothNum?=?(int)(_PathVector2DDictionary[path].Count?*?SmoothSampling);if?(smoothNum?>?1)pathVector2Ds?=?ComputingHelper.AverageSampling(_PathVector2DDictionary[path],?smoothNum);var?lineB?=?new?LineB(pathVector2Ds,?false);lineB.Tension?=?Tension;path.Data?=?lineB;}}private?void?btnClertCanvas_Click(object?sender,?RoutedEventArgs?e){drawingCanvas.Children.Clear();_PathVector2DDictionary.Clear();}}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/283010.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/283010.shtml
英文地址,請注明出處:http://en.pswp.cn/news/283010.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

IIS應用程序池相關問題及連接池已滿的解決方法

關于應用程序池 在 IIS 6.0 中,引入了應用程序池&#xff0c;應用程序池是將一個或多個應用程序鏈接到一個或多個工作進程集合的配置。因為應用程序池中的應用程序與其他應用程序被工作進程邊界分隔&#xff0c;所以某個應用程序池中的應用程序不會受到其他應用程序池中應用程序…

echo -n 和echo -e 參數意義

echo -n 不換行輸出 $echo -n "123" $echo "456" 12最終輸出 123456而不是 123 456 123456echo -e 處理特殊字符 若字符串中出現以下字符&#xff0c;則特別加以處理&#xff0c;而不會將它當成一般文字輸出&#xff1a; \a 發出警告聲&#xff1b; \b 刪…

NetSpeed

NetSpeed公司提供的NOC包括三部分&#xff0c;可以通過NocStudio進行配置生成。 1)NetSpeed Orion&#xff0c;面向快速SoC design的可綜合平臺。 2)Linley NetSpeed NoC面向復雜的interconnect實現&#xff0c;同時優化內部physical implementation和timing closure. NoC是基于…

js ajax java傳參_ajax參數傳遞與后臺接收

ajax參數傳遞與后臺接收Servlet中讀取http參數的方法Enumeration getParameterNames() 返回一個 String 對象的枚舉&#xff0c;包含在該請求中包含的參數的名稱String getParameter(String name) 以字符串形式返回請求參數的值&#xff0c;或者如果參數不存在則返回 null。Str…

init 訪問器只能初始化時賦值,是真的嗎?

前言C# 提供的 init 關鍵字用于在屬性中定義訪問器方法&#xff0c;可以讓屬性僅能在對象初始化的時候被賦值&#xff0c;其他時候只能為只讀屬性的形式。例如下面代碼可以正常執行&#xff1a;public class Demo {public string Name { get; init; } }var demo new Demo { Na…

eclipse實現代碼塊折疊-類似于VS中的#region……#endregion

背 景 剛才在寫代碼的時候&#xff0c;寫了十幾行可以說是重復的代碼&#xff1a; 如果整個方法或類中代碼多了&#xff0c;感覺它們太TM占地方了&#xff0c;給讀者在閱讀代碼上造成很大的困難&#xff0c;于是想到能不能把他們“濃縮”成一行&#xff0c;腦子里第一個閃現出的…

添加Chrome插件(Github上下載的壓縮文件)

首先把壓縮包解壓到某個文件夾 然后按照以下步驟進行即可&#xff1a; 點擊Chrome瀏覽器上的設置->擴展程序->開發者模式->點擊加載已解壓的壓縮文件->選中解壓過的文件夾確定即可。轉載于:https://www.cnblogs.com/yijianzhongqing/p/6277838.html

java定義基礎變量語句_java語言基礎-變量

一丶變量的基本概念1.什么是變量(1).內存中的一個存儲區域(2).該區域有自己的名稱(變量名),和類型(數據類型)(3.)該區域的數據可以在同一類型范圍內不斷變化(定義變量的主要目的是因為數據的不確定性)2.為什么要定義變量用來不斷存放同一類型的常量&#xff0c;并可以重復使用3…

C# WPF MVVM模式[經典]案例

01—前言Caliburn.Micro(簡稱CM)一經推出便備受推崇&#xff0c;作為一款MVVM開發模式的經典框架&#xff0c;越來越多的受到wpf開發者的青睞.我們看一下官方的描述&#xff1a;Caliburn是一個為Xaml平臺設計的小型但功能強大的框架。Micro實現了各種UI模式&#xff0c;用于解決…

shell數組

定義數組[rootwy shell]# a(1 2 3 4)顯示數組[rootwy shell]# echo ${a[]}1 2 3 4[rootwy shell]# echo ${a[*]}1 2 3 4顯示數組中的某個元素[rootwy shell]# echo ${a[0]}1增加元素[rootwy shell]# a[4]9[rootwy shell]# echo ${a[*]}1 2 3 4 9修改元素值 [rootwy shell]# a[2…

java二級程序題兩個角度_兩個角度圖_【SCME大一】使用JAVA語言深入理解程序邏輯答案_學小易找答案...

【填空題】《蝶戀花 佇倚危樓風細細 》的作者( )。【簡答題】簡要概述問卷調查的整體設計?【填空題】父母在,( ),游必有方。【填空題】白居易與劉禹錫并稱“( )”。【填空題】白居易,字( )。【填空題】白居易,是唐代偉大的( )主義詩人。【單選題】《紅樓夢》最成功處在于塑造了…

LINUX中常用操作命令

LINUX中常用操作命令 引用&#xff1a;http://www.daniubiji.cn/archives/25 Linux簡介及Ubuntu安裝 常見指令系統管理命令打包壓縮相關命令關機/重啟機器Linux管道Linux軟件包管理vim使用用戶及用戶組管理文件權限管理Linux簡介及Ubuntu安裝 Linux&#xff0c;免費開源&#x…

Log4j編寫

來自: http://www.blogjava.net/zJun/archive/2006/06/28/55511.html Log4J的配置文件(Configuration File)就是用來設置記錄器的級別、存放器和布局的&#xff0c;它可接keyvalue格式的設置或xml格式的設置信息。通過配置&#xff0c;可以創建出Log4J的運行環境。1. 配置文件L…

C# 為什么高手喜歡用StartsWith而不是Substring進行字符串匹配?

字符串的截取匹配操作在開發中非常常見&#xff0c;比如下面這個示例&#xff1a;我要匹配查找出來字符串數組中以“abc”開頭的字符串并打印&#xff0c;我下面分別用了兩種方式實現&#xff0c;代碼如下&#xff1a;using System;namespace ConsoleApp23 {class Program{stat…

Nginx 服務器開啟status頁面檢測服務狀態

原文&#xff1a;http://www.cnblogs.com/hanyifeng/p/5830013.html 一、Nginx status monitor 和apache 中服務器狀態一樣。輸出的內容如&#xff1a; 第1列&#xff1a; 當前與http建立的連接數&#xff0c;包括等待的客戶端連接&#xff1a;2第2列&#xff1a;接受的客戶端連…

elif是不是java關鍵字_C# 中的#if、#elif、#else、#endif等條件編譯符號 (轉載)

這些是C#中的條件編譯符號。這些指令我在項目中遇到過&#xff0c;查過網絡&#xff0c;問過人(當然&#xff0c;既不認識大牛&#xff0c;也不認識小牛&#xff0c;所以沒什么收獲)。今天翻看一本資料&#xff0c;有提到這個方面的東西&#xff0c;所以寫下來和能看到這篇文章…

從零開始React項目架構(四)

前言 使用當前的webpack配置能不能打包構建項目呢&#xff1f;當然可以&#xff0c;但這不是我們想要的&#xff0c;所以&#xff0c;讓我們來看一看生產環境需要怎么配置webpack吧 開發 生產環境配置 在根目錄創建webpack.pro.config.jsconst path require(path) const webpa…

在OpenCloudOS 上安裝.NET 6

開源操作系統社區 OpenCloudOS 由騰訊與合作伙伴共同倡議發起&#xff0c;是完全中立、全面開放、安全穩定、高性能的操作系統及生態。OpenCloudOS 沉淀了多家廠商在軟件和開源生態的優勢&#xff0c;繼承了騰訊在操作系統和內核層面超過10年的技術積累&#xff0c;在云原生、穩…

Linux 命令詳解(二)awk 命令

AWK是一種處理文本文件的語言&#xff0c;是一個強大的文本分析工具。之所以叫AWK是因為其取了三位創始人 Alfred Aho&#xff0c;Peter Weinberger, 和 Brian Kernighan 的Family Name的首字符。 語法&#xff1a; awk [選項參數] script varvalue file(s) 或 awk [選項參數] …

linux下vtune使用

安裝&#xff1a;http://www.cnblogs.com/jiu0821/p/5943533.html 終端輸入amplxe-gui,打開vtune界面。 點擊new project&#xff0c;進入project properties界面。進行配置&#xff1a; target&#xff1a;target type選擇launch application&#xff0c;application選擇程序可…