分類:C#、VS2015
創建日期:2016-06-23
使用教材:(十二五國家級規劃教材)《C#程序設計及應用教程》(第3版)
該例子屬於高級技術中的基本用法。對於初學者來說這是難點(難在還沒有學習第13章WPF相關的繪圖技術),因此,這裡的關鍵是理解設計思路,而不是一開始就陷於細節的實現上。或者說,一旦你掌握了這些基本的設計思路,就會極大地提高你對面向對象編程的理解。
用到的技術:封裝、繼承、多態。
本補充示例的運行效果:
1、新建項目
項目名:WpfAdvanceDemo2
模板:WPF應用程序項目。
2、添加W0_DrawObject.cs文件
鼠標右擊解決方案資源管理器中的項目名,選擇【添加】->【類】,輸入文件名W0_DrawObject.cs,然後將代碼改為下面的內容:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Media;
namespace WpfAdvanceDemo2
{
public abstract class W0_DrawObject : DynamicRenderer
{
protected Point previousPoint;
public MyInkCanvas myInkCanvas { get; private set; }
public DrawObjectStroke InkStroke { get; protected set; }
public DrawingAttributes inkDA { get; set; }
public abstract void CreateNewStroke(InkCanvasStrokeCollectedEventArgs e);
public abstract Point Draw(Point first, DrawingContext dc, StylusPointCollection points);
[ThreadStatic]
protected Brush brush = Brushes.Gray;
public W0_DrawObject(MyInkCanvas myInkCanvas)
{
this.myInkCanvas = myInkCanvas;
this.inkDA = myInkCanvas.inkDA.Clone();
this.DrawingAttributes = inkDA;
}
protected override void OnStylusDown(RawStylusInput rawStylusInput)
{
inkDA = myInkCanvas.inkDA.Clone();
this.DrawingAttributes = inkDA;
previousPoint = new Point(double.NegativeInfinity, double.NegativeInfinity);
base.OnStylusDown(rawStylusInput);
}
protected override void OnStylusUp(RawStylusInput rawStylusInput)
{
base.OnStylusUp(rawStylusInput);
this.InkStroke = null;
}
}
public class DrawObjectStroke : Stroke
{
protected W0_DrawObject ink;
public DrawObjectStroke(W0_DrawObject ink, StylusPointCollection stylusPoints)
: base(stylusPoints)
{
this.ink = ink;
this.DrawingAttributes = ink.inkDA.Clone();
this.DrawingAttributes.Color = Colors.Transparent;
}
protected virtual void RemoveDirtyStylusPoints()
{
if (StylusPoints.Count > 2)
{
for (int i = StylusPoints.Count - 2; i > 0; i--)
{
StylusPoints.RemoveAt(i);
}
}
}
}
}
3、添加W1_DrawRectangle.cs文件
鼠標右擊解決方案資源管理器中的項目名,選擇【添加】->【類】,輸入文件名W1_DrawRectangle.cs,然後將代碼改為下面的內容:
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Media;
namespace WpfAdvanceDemo2
{
public class W1_DrawRectangle : W0_DrawObject
{
public W1_DrawRectangle(MyInkCanvas myInkCanvas) : base(myInkCanvas)
{
}
public override void CreateNewStroke(InkCanvasStrokeCollectedEventArgs e)
{
InkStroke = new DrawRectangleStroke(this, e.Stroke.StylusPoints);
}
public override Point Draw(Point first, DrawingContext dc, StylusPointCollection points)
{
Point pt = (Point)points.Last();
Vector v = Point.Subtract(pt, first);
if (v.Length > 4)
{
Rect rect = new Rect(first, v);
//填充
var b = new RadialGradientBrush(Colors.White, Colors.Red);
dc.DrawRectangle(b, null, rect);
//畫輪廓
Pen pen = new Pen(Brushes.DarkRed, 1.0);
dc.DrawRectangle(null, pen, rect);
}
return first;
}
protected override void OnStylusDown(RawStylusInput rawStylusInput)
{
base.OnStylusDown(rawStylusInput);
previousPoint = (Point)rawStylusInput.GetStylusPoints().First();
}
protected override void OnStylusMove(RawStylusInput rawStylusInput)
{
StylusPointCollection stylusPoints = rawStylusInput.GetStylusPoints();
this.Reset(Stylus.CurrentStylusDevice, stylusPoints);
base.OnStylusMove(rawStylusInput);
}
protected override void OnDraw(DrawingContext drawingContext, StylusPointCollection stylusPoints, Geometry geometry, Brush fillBrush)
{
Draw(previousPoint, drawingContext, stylusPoints);
base.OnDraw(drawingContext, stylusPoints, geometry, brush);
}
}
public class DrawRectangleStroke : DrawObjectStroke
{
public DrawRectangleStroke(W1_DrawRectangle ink, StylusPointCollection stylusPoints)
: base(ink, stylusPoints)
{
this.RemoveDirtyStylusPoints();
}
protected override void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
{
base.DrawCore(drawingContext, drawingAttributes);
Point pt1 = (Point)StylusPoints.First();
ink.Draw(pt1, drawingContext, StylusPoints);
}
}
}
4、添加W2_DrawEllipse.cs文件
鼠標右擊解決方案資源管理器中的項目名,選擇【添加】->【類】,輸入文件名W2_DrawEllipse.cs,然後將代碼改為下面的內容:
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Media;
namespace WpfAdvanceDemo2
{
public class W2_DrawEllipse : W0_DrawObject
{
public W2_DrawEllipse(MyInkCanvas myInkCanvas) : base(myInkCanvas)
{
}
public override void CreateNewStroke(InkCanvasStrokeCollectedEventArgs e)
{
InkStroke = new DrawEllipseStroke(this, e.Stroke.StylusPoints);
}
public override Point Draw(Point first, DrawingContext dc, StylusPointCollection points)
{
Point pt = (Point)points.Last();
Vector v = Point.Subtract(pt, first);
double radiusX = (pt.X - first.X) / 2.0;
double radiusY = (pt.Y - first.Y) / 2.0;
Point center = new Point((pt.X + first.X) / 2.0, (pt.Y + first.Y) / 2.0);
//填充
var b = new RadialGradientBrush(Colors.White, Colors.Red);
dc.DrawEllipse(b, null, center, radiusX, radiusY);
//畫輪廓
Pen pen = new Pen(Brushes.DarkRed, 1.0);
dc.DrawEllipse(null, pen, center, radiusX, radiusY);
return first;
}
protected override void OnStylusDown(RawStylusInput rawStylusInput)
{
base.OnStylusDown(rawStylusInput);
previousPoint = (Point)rawStylusInput.GetStylusPoints().First();
}
protected override void OnStylusMove(RawStylusInput rawStylusInput)
{
StylusPointCollection stylusPoints = rawStylusInput.GetStylusPoints();
this.Reset(Stylus.CurrentStylusDevice, stylusPoints);
base.OnStylusMove(rawStylusInput);
}
protected override void OnDraw(DrawingContext drawingContext, StylusPointCollection stylusPoints, Geometry geometry, Brush fillBrush)
{
Draw(previousPoint, drawingContext, stylusPoints);
base.OnDraw(drawingContext, stylusPoints, geometry, brush);
}
}
public class DrawEllipseStroke : DrawObjectStroke
{
public DrawEllipseStroke(W2_DrawEllipse ink, StylusPointCollection stylusPoints)
: base(ink, stylusPoints)
{
this.RemoveDirtyStylusPoints();
}
protected override void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
{
base.DrawCore(drawingContext, drawingAttributes);
Point pt1 = (Point)StylusPoints.First();
ink.Draw(pt1, drawingContext, StylusPoints);
}
}
}
5、添加W3_DrawCurve.cs文件
鼠標右擊解決方案資源管理器中的項目名,選擇【添加】->【類】,輸入文件名W3_DrawCurve.cs,然後將代碼改為下面的內容:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
namespace WpfAdvanceDemo2
{
public class W3_DrawCurve : W0_DrawObject
{
public W3_DrawCurve(MyInkCanvas myInkCanvas)
: base(myInkCanvas)
{
}
public override void CreateNewStroke(InkCanvasStrokeCollectedEventArgs e)
{
InkStroke = new DrawCurveStroke(this, e.Stroke.StylusPoints);
}
public override Point Draw(Point first, DrawingContext dc, StylusPointCollection points)
{
return first;
}
protected override void OnDraw(DrawingContext drawingContext, StylusPointCollection stylusPoints, Geometry geometry, Brush fillBrush)
{
base.OnDraw(drawingContext, stylusPoints, geometry, Brushes.Black);
}
}
public class DrawCurveStroke : DrawObjectStroke
{
public DrawCurveStroke(W0_DrawObject ink, StylusPointCollection stylusPoints)
: base(ink, stylusPoints)
{
this.DrawingAttributes.FitToCurve = true;
}
protected override void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
{
base.DrawCore(drawingContext, drawingAttributes);
Geometry geometry = this.GetGeometry();
drawingContext.DrawGeometry(Brushes.Black, null, geometry);
}
}
}
6、添加MyInkCanvas.cs文件
鼠標右擊解決方案資源管理器中的項目名,選擇【添加】->【類】,輸入文件名MyInkCanvas.cs,然後將代碼改為下面的內容:
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
namespace WpfAdvanceDemo2
{
public class MyInkCanvas : InkCanvas
{
private W0_DrawObject ink;
public DrawingAttributes inkDA { get; private set; }
public MyInkCanvas()
{
inkDA = new DrawingAttributes()
{
Color = Colors.Red,
Width = 15,
Height = 15,
StylusTip = StylusTip.Rectangle,
IgnorePressure = true,
FitToCurve = false
};
this.DefaultDrawingAttributes = inkDA;
ink = new W1_DrawRectangle(this);
UpdateInkParams();
}
/// <summary>當收集墨跡時,會自動調用此方法</summary>
protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
{
this.Strokes.Remove(e.Stroke);
ink.CreateNewStroke(e);
this.Strokes.Add(ink.InkStroke);
InkCanvasStrokeCollectedEventArgs args = new InkCanvasStrokeCollectedEventArgs(ink.InkStroke);
base.OnStrokeCollected(args);
}
/// <summary>初始化墨跡參數</summary>
public void SetInkAttributes(string name)
{
switch (name)
{
//---------------墨跡類型---------------------
case "矩形":
ink = new W1_DrawRectangle(this);
inkDA.Width = inkDA.Height = 15;
inkDA.StylusTip = StylusTip.Rectangle;
this.UseCustomCursor = false;
break;
case "球形":
ink = new W2_DrawEllipse(this);
inkDA.Width = inkDA.Height = 15;
inkDA.StylusTip = StylusTip.Ellipse;
this.UseCustomCursor = false;
break;
case "毛筆":
ink = new W3_DrawCurve(this);
inkDA.Width = inkDA.Height = 10;
this.Cursor = Cursors.Pen;
this.UseCustomCursor = true;
break;
}
UpdateInkParams();
}
/// <summary>
/// 根據墨跡類型和筆尖信息,設置MyInkCanvas中的相關參數
/// </summary>
private void UpdateInkParams()
{
this.DynamicRenderer = ink;
this.EditingMode = InkCanvasEditingMode.Ink;
}
}
}
7、修改MainWindow.xaml文件
將其改為下面的內容。
<Window x:Class="WpfAdvanceDemo2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAdvanceDemo2"
mc:Ignorable="d"
Title="將圖形作為對象--簡單示例(http://cnblogs.com/rainmj)" Height="400" Width="700" WindowStartupLocation="CenterScreen" Background="#FFE4EEDE">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<TextBlock Text="提示:選擇一種繪制類型,然後在繪圖框區域內按住鼠標左鍵拖動繪制。" Margin="0 20" FontSize="16" Foreground="Blue" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Separator/>
<WrapPanel ButtonBase.Click="RadioButton_Click" Margin="0 10 0 0">
<TextBlock Text="繪制類型:" VerticalAlignment="Center"/>
<RadioButton Content="矩形" IsChecked="True" Margin="5"/>
<RadioButton Content="球形" Margin="5"/>
<RadioButton Content="毛筆" Margin="5"/>
</WrapPanel>
</StackPanel>
<Frame Name="frame1" Grid.Row="1" Margin="10" BorderThickness="1" BorderBrush="Blue"
NavigationUIVisibility="Hidden" />
<TextBlock Grid.Row="2" Text="(完整例子在【網絡應用編程】課程中還會介紹,該例子僅演示了最基本的用法)" Margin="0 0 0 5" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Window>
8、修改MainWindow.xaml.cs文件
將其改為下面的內容。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace WpfAdvanceDemo2
{
/// <summary>
/// MainWindow.xaml 的交互邏輯
/// </summary>
public partial class MainWindow : Window
{
MyInkCanvas mycanvas;
public MainWindow()
{
InitializeComponent();
mycanvas = new MyInkCanvas();
frame1.Content = mycanvas;
}
private void RadioButton_Click(object sender, RoutedEventArgs e)
{
string s = (e.Source as RadioButton).Content.ToString();
mycanvas.SetInkAttributes(s);
}
}
}
9、運行
按<F5>鍵調試運行。
OK,這個例子雖然簡單,但是卻演示了封裝、繼承、多態在實際項目中的基本應用設計思路。請耐著性子仔細分析該例子的源代碼,相信你掌握設計思路和技巧後一定會對C#面向對象編程的理解有一個大的飛躍。
在此基礎上,你就可以繼續學習復雜的例子了。實際上,任何內容都可以通過拖放繪制出來,包括視頻。
下面的截圖演示了高級用法示例的運行效果(選擇某種繪圖類型以及其他選項後,按住鼠標左鍵隨意拖放即可):
該例子更接近於實際項目,雖然例子看起來好像很復雜,但是基本的設計思路還是這個簡單例子的思路,只不過是在簡單例子基礎上多添加了一些類而已。
這裡順便解釋一下,類似Office的工具箱界面是如何實現的(用到了Ribbon控件):
(1)鼠標右擊【引用】->【添加引用】,然後按下圖所示添加Ribbon引用。
(2)在項目中添加一個Windows窗體,然後就可以在該窗體中使用Ribbon控件設計工具箱的內容了。下面是高級例子對應的XAML代碼(為了方便快速理解,這裡去掉了重復的內容,僅列出了其中的一部分代碼):
<Window x:Class="WpfExamples.ch03.Ex02.WpfAdvanceDemo3.Demo3MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfExamples.ch03.Ex02.WpfAdvanceDemo3"
mc:Ignorable="d"
Title="將圖形圖像作為對象--高級功能" Height="460" Width="980" Background="#FFF0F9D8" WindowState="Maximized">
<Grid x:Name="root">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Ribbon Name="ribbon" Grid.Row="0">
<Ribbon.Resources>
<Style TargetType="RibbonRadioButton">
<Setter Property="LargeImageSource" Value="/Resources/Images/b1.png"/>
<Setter Property="SmallImageSource" Value="/Resources/Images/b1.gif"/>
<Setter Property="CornerRadius" Value="13"/>
<Setter Property="Margin" Value="5 0 0 0"/>
<EventSetter Event="Checked" Handler="RibbonRadioButton_Checked"/>
</Style>
</Ribbon.Resources>
<Ribbon.ApplicationMenu>
<RibbonApplicationMenu Name="appMenu1" ToolTip="主菜單">
<RibbonApplicationMenu.Resources>
<Style TargetType="RibbonApplicationMenuItem">
<Setter Property="ImageSource" Value="/Resources/Images/b1.gif"/>
<EventSetter Event="Click" Handler="RibbonApplicationMenuItem_Click"/>
</Style>
</RibbonApplicationMenu.Resources>
<RibbonApplicationMenuItem Header="打開"/>
<RibbonApplicationMenuItem Header="另存為"/>
<RibbonSeparator/>
<RibbonApplicationMenuItem Header="退出"/>
</RibbonApplicationMenu>
</Ribbon.ApplicationMenu>
<RibbonTab Name="rt1" Header="工具箱">
<RibbonGroup Header="墨跡類型">
<RibbonGroup.GroupSizeDefinitions>
<RibbonGroupSizeDefinition>
<RibbonControlSizeDefinition ImageSize="Small"/>
<RibbonControlSizeDefinition ImageSize="Small"/>
......(略,內容都一樣,個數與下面的RibbonRadioButton個數對應即可)
</RibbonGroupSizeDefinition>
</RibbonGroup.GroupSizeDefinitions>
<RibbonRadioButton x:Name="rrbEllipseType" Label="球形" IsChecked="True"/>
<RibbonRadioButton Label="矩形"/>
<RibbonRadioButton Label="圖像"/>
<RibbonRadioButton Label="球形序列"/>
<RibbonRadioButton Label="矩形序列"/>
<RibbonRadioButton Label="圖像序列"/>
<RibbonRadioButton Label="直線"/>
<RibbonRadioButton Label="曲線"/>
<RibbonRadioButton Label="文字"/>
</RibbonGroup>
<RibbonGroup Header="筆尖類型">
<RibbonRadioButton x:Name="rrbEllipseStylus" Label="圓筆" IsChecked="True" GroupName="edit" />
<RibbonRadioButton Label="豎筆" GroupName="edit"/>
<RibbonRadioButton Label="橫筆" GroupName="edit"/>
<RibbonRadioButton Label="鋼筆" GroupName="edit"/>
</RibbonGroup>
.....(後面的代碼和前面類似,不再列出了)
</RibbonTab>
</Ribbon>
<Grid x:Name="grid1" Margin="10" Grid.Row="1" Visibility="Visible">
<Rectangle Grid.ColumnSpan="2" Fill="white"
RadiusX="14" RadiusY="14"
Stroke="Blue" StrokeDashArray="3" />
<local:MyInkCanvas x:Name="ink1"/>
</Grid>
</Grid>
</Window>
注意:練習時要一行一行的敲,不要用復制粘貼的辦法,否則系統不會自動在後台代碼(代碼隱藏類)中添加對應的事件處理程序。
在後續的章節中,我們還會學習該高級例子涉及的更多概念(比如利用序列化和反序列化將繪圖結果保存到文件中,並將序列化後的結果讀取出來還原為截圖中的各個繪圖對象等)。這裡暫不列出高級例子的設計步驟,准備等後續章節把相關的概念介紹完畢後,再學習高級例子的源代碼也不晚。
說明:這些例子全部都是本人原創的,轉載請注明出處:http://cnblogs.com/rainmj。