补充如何将图形作为对象
Posted rainmj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了补充如何将图形作为对象相关的知识,希望对你有一定的参考价值。
分类: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="球形序列"/> 如何将 r ggplot 图存储为 html 代码片段