今天在公司比較閑所以就連續學了兩節WPF,下面是要跟大家探討一下關於如何用WPF繪制與配置2D圖像界面,說來好笑,剛剛公司的同事遇到一個有關WPF的問題——怎樣在WPF的設計頁面化一個紅色箭頭。剛剛看到這個問題,我思考了一下,也動手做了一下,不過能力有限還是自己沒有解決,於是開始Google,Baidu。在網上有很多解決方案,但是用的方法都是差不多的:寫一個畫箭頭的class,然后在需要用到箭頭的地方實例化箭頭Class,並給相應的坐標賦值,最后將對象Add到Canvas控件中。
用WPF畫箭頭 |
自定義的畫箭頭Class代碼:

namespace ZhangWei.WPF.Shapes
{
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
/// <summary>
/// TODO: Update summary.
/// </summary>
public sealed class Arrow : Shape
{
#region Dependency Properties
public static readonly DependencyProperty X1Property = DependencyProperty.Register("X1", typeof(double), typeof(Arrow), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty Y1Property = DependencyProperty.Register("Y1", typeof(double), typeof(Arrow), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty X2Property = DependencyProperty.Register("X2", typeof(double), typeof(Arrow), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty Y2Property = DependencyProperty.Register("Y2", typeof(double), typeof(Arrow), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty HeadWidthProperty = DependencyProperty.Register("HeadWidth", typeof(double), typeof(Arrow), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty HeadHeightProperty = DependencyProperty.Register("HeadHeight", typeof(double), typeof(Arrow), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
#endregion
#region CLR Properties
[TypeConverter(typeof(LengthConverter))]
public double X1
{
get { return (double)base.GetValue(X1Property); }
set { base.SetValue(X1Property, value); }
}
[TypeConverter(typeof(LengthConverter))]
public double Y1
{
get { return (double)base.GetValue(Y1Property); }
set { base.SetValue(Y1Property, value); }
}
[TypeConverter(typeof(LengthConverter))]
public double X2
{
get { return (double)base.GetValue(X2Property); }
set { base.SetValue(X2Property, value); }
}
[TypeConverter(typeof(LengthConverter))]
public double Y2
{
get { return (double)base.GetValue(Y2Property); }
set { base.SetValue(Y2Property, value); }
}
[TypeConverter(typeof(LengthConverter))]
public double HeadWidth
{
get { return (double)base.GetValue(HeadWidthProperty); }
set { base.SetValue(HeadWidthProperty, value); }
}
[TypeConverter(typeof(LengthConverter))]
public double HeadHeight
{
get { return (double)base.GetValue(HeadHeightProperty); }
set { base.SetValue(HeadHeightProperty, value); }
}
#endregion
#region Overrides
protected override Geometry DefiningGeometry
{
get
{
// Create a StreamGeometry for describing the shape
StreamGeometry geometry = new StreamGeometry();
geometry.FillRule = FillRule.EvenOdd;
using (StreamGeometryContext context = geometry.Open())
{
InternalDrawArrowGeometry(context);
}
// Freeze the geometry for performance benefits
geometry.Freeze();
return geometry;
}
}
#endregion
#region Privates
private void InternalDrawArrowGeometry(StreamGeometryContext context)
{
double theta = Math.Atan2(Y1 - Y2, X1 - X2);
double sint = Math.Sin(theta);
double cost = Math.Cos(theta);
Point pt1 = new Point(X1, this.Y1);
Point pt2 = new Point(X2, this.Y2);
Point pt3 = new Point(
X2 + (HeadWidth * cost - HeadHeight * sint),
Y2 + (HeadWidth * sint + HeadHeight * cost));
Point pt4 = new Point(
X2 + (HeadWidth * cost + HeadHeight * sint),
Y2 - (HeadHeight * cost - HeadWidth * sint));
context.BeginFigure(pt1, true, false);
context.LineTo(pt2, true, true);
context.LineTo(pt3, true, true);
context.LineTo(pt2, true, true);
context.LineTo(pt4, true, true);
}
#endregion
}
}
給一個調用的例子:
首先在頁面中放一個Canvas控件用來在其中顯示箭頭,
<Window x:Class="Tomers.WPF.Shapes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Canvas Height="104" Margin="42,45,313,0" Name="canvas1" VerticalAlignment="Top" >
</Canvas>
</Grid>
</Window>
下面就可以在后台代碼中讓箭頭出現在Canvas中,代碼:
using System.Windows;
using System.Windows.Media;
namespace Tomers.WPF.Shapes
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Arrow arrow = new Arrow();
arrow.X1 = 2;
arrow.Y1 = 10;
arrow.X2 = 60;
arrow.Y2 = 10;
arrow.HeadWidth = 10;
arrow.HeadHeight = 2;
arrow.Stroke = Brushes.Red;
arrow.StrokeThickness = 2;
canvas1.Children.Add(arrow);
}
}
}
在代碼中
arrow.X1 = 2;
arrow.Y1 = 10;
arrow.X2 = 60;
arrow.Y2 = 10;
用來指定箭頭在畫布Canvas中的位置,其中X1,Y1表示箭頭的開始坐標,X2,Y2表示箭頭的結束坐標。當配置完成之后將對象Add到Canvase的子節點中。
當我很興奮的告訴同事問題已經被我拿下來啦,但是當她看到那個箭頭Class時,直接否定了我。原因代碼太多了接受不了。說要用XAML代碼來完成,不用C#。於是讓我尋求第二種解決方法的動力就來了。正在我焦頭亂額時,突然讓我看到了曙光,那就是——WPF繪制與配置2D圖像界面。
其實也不是什么高深的東東,主要用到了Line和Path,這兩個東西簡直太神奇了,我先前不知道第二種方法的原因是:我以為只能用ToolBox里面提供的控件,但是在Toolbox里面都沒有可以畫直線是東東。讓我找到了Line顧名思義是用來畫直線的,Path可以用來畫曲線,有了這兩個東西,畫箭頭俺還不是小菜嗎。呵呵。。。。。。
只需要短短的幾行XAML就搞定了上面大量代碼搞定的事咯:
<Window x:Class="Tomers.WPF.Shapes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Canvas Height="152" Name="canvas2" Width="200" Canvas.Left="43" Canvas.Top="90">
<Line X1="30" Y1="100" X2="100" Y2="100" Stroke="Red" StrokeThickness="3" Width="226" Height="140" />
<Path Stroke="Red" StrokeThickness="3" Data="M85,105 100,100 85,95" />
</Canvas>
</Grid>
</Window>
一句C#代碼都不要太爽啦。
下面就更深一步的來研究一下其他的用來畫圖的東東,來看看下面這個圖吧:
很簡單,這些都是小Case,你們可以自由發揮,用最簡單最基本的東西可以畫出很多好看的圖形。實現上面圖像的XAML代碼是:
<Window x:Class="Demo2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Name="grid1">
<Line X1="100" Y1="150" X2="200" Y2="200" Stroke="Red" StrokeThickness="2"/>
<Ellipse Height="237" HorizontalAlignment="Left" Margin="299,12,0,0" Name="ellipse1" Stroke="Black" VerticalAlignment="Top" Width="143" Fill="Yellow" StrokeThickness="6" />
<Button Content="Button" Height="77" HorizontalAlignment="Left" Margin="42,222,0,0" Name="button1" VerticalAlignment="Top" Width="203" Click="button1_Click" />
<Button Content="Button" Height="60" HorizontalAlignment="Left" Margin="278,239,0,0" Name="button2" VerticalAlignment="Top" Width="75" Click="button2_Click" />
<Canvas Height="139" HorizontalAlignment="Left" Margin="10,10,0,0" Name="canvas1" VerticalAlignment="Top" Width="252" >
<Ellipse Canvas.Left="10" Canvas.Top="10" Height="25" Name="ellipse2" Stroke="Black" Width="50" />
<Path Stroke="Blue" StrokeThickness="2" Data="M2,2 Q30,40 4,50"/>
<Polygon Stroke="Red" StrokeThickness="4" Points="40,100 80,120 60,160">
<Polygon.Fill>
<SolidColorBrush Color="Yellow"/>
</Polygon.Fill>
</Polygon>
</Canvas>
</Grid>
</Window>
很少吧。這里很有趣,大家可以自由發揮,我實在沒什么美術天分,所以畫不出來好東西,在這里也不給大家畫啦。
我把源碼分享給大家吧(源碼下載)