.Net Framework3.0 实践纪实(3)
来源:互联网 发布:win和linux双系统引导 编辑:程序博客网 时间:2024/05/16 23:59
.Net Framework3.0 实践纪实(3)
图形和背景
任务1.3画出棋盘上的星。要完成这个任务,一个关键的地方就是确定星在不同大小的棋盘上的数量和位置。其实TopGo对棋盘的做了限制,那就是小于9*9或者大于19*19的棋盘不被支持。在星的数量确定上,我们考虑到如果是偶数的棋盘,那么没有唯一的中心点(像19*19的中央的那个叫做“天元”的星),在这种情况下,我们仅仅设置星的数量为4(即每个角部一个)。下面的代码显示了这一过程:
protected override void OnRender(DrawingContext dc)
{
… …
if (BoardSize > 8 && BoardSize < 20)
{
Point[] stars = GetDemarkations();
for (int i = 0; i < stars.Length; i++)
{
dc.DrawEllipse(Brushes.Black, null, stars[i], 3 * scale, 3 * scale);
}
}
}
代码首先通过调用GetDemarkations来获取星的坐标位置,然后通过一个循环,调用DrawingContext对象的DrawEllipse来画出没一个星。星是一个半径3倍于直线宽度圆点。GetDemarkations的方法代码如下:
readonly int[,] demarkCount ={ { 2, 3 }, { 3, 3 }, { 3, 4 }, { 3, 5 }, { 3, 6 } };
private Point[] GetDemarkations()
{
Point[] demarks;
if (BoardSize == 9)
{
demarks = new Point[1];
demarks[0] = new Point(3, 3);
return demarks;
}
if (BoardSize % 2 == 0)
{
demarks = new Point[4];
demarks[0] = new Point(3, 3);
demarks[1] = new Point(3, BoardSize - 4);
demarks[2] = new Point(BoardSize - 4, 3);
demarks[3] = new Point(BoardSize - 4, BoardSize - 4);
return demarks;
}
demarks =new Point[9];
int index = ((int)BoardSize - 11) / 2;
int i = 0;
for (int x = demarkCount[index, 0]; x < BoardSize - 1; x += demarkCount[index, 1])
{
for (int y = demarkCount[index, 0]; y < BoardSize - 1; y += demarkCount[index, 1])
demarks[i++] =new Point(x, y);
}
return demarks;
}
代码使用了一个预先定义的数组来保存星的数量和星之间的间距,其他部分我想应该很清晰,所以就不作解释了。
虽然我们已经可以显示不同大小的棋盘,但是有一个问题,必须提供一个接口让用户来设置BoardSize,我们把这个需求加入到任务表,同时给任务1.3做上标记:
1、TopGo必须能够显示一个棋盘;
1.4 提供用户修改棋盘大小的接口
在我们做这项工作之前,让我们给目前为止的程序界面美美容,所以我们继续添加一些任务:
1.5 设置棋盘背景
…
4、设置窗体背景
棋盘背景的颜色,很自然想到的是黄色调的,这是因为棋盘大部分都是木质的,黄色调比较接近。当定下了棋盘的主色调,我们就要考虑窗体的背景颜色必须和棋盘协调。首先我想到的就是暗红色,因为暗红色可以让我想到红木家具,这仿佛棋盘置于高贵的红木桌面。
给窗体添加背景,我们可以使用渐变笔刷,WPF中有两种渐变笔刷,一种是辐射渐变笔刷,另外一种是线性渐变笔刷。我们这里使用线性渐变笔刷,xaml的代码如下:
<Windowx:Class="TopGo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:TopGo"
Title="TopGo" MinHeight="600"MinWidth="800"WindowState="Maximized"
>
<Window.Background>
<LinearGradientBrushStartPoint="0,0"EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStopOffset="0"Color="DarkRed" />
<GradientStopOffset="0.8"Color="Chocolate" />
<GradientStopOffset="1"Color="DarkRed" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Window.Background>
StartPoint设置颜色渐变的起点坐标,EndPoint设置结束点坐标,两个点的坐标决定了渐变的方向,从我们的设置看这是一个从上至下的垂直方向。
GradientStops定义了一组颜色的变化点,Offset是相对于起点沿着渐变方向的偏移值。编译运行,看看效果如何。你可以根据自己的理解来设置颜色和偏移。
给任务表任务4做上标记。接着我们设计棋盘的背景,这一次我们采用不同的方式,实际上你可以在BoardControl类的OnRender方法中通过代码来画出棋盘的背景。这里我们采用在棋盘控件的后面画一个矩形,然后给这个矩形填充颜色来作为棋盘的背景色。为了在棋盘后面放置画一个矩形,首先我们需要在Viewbox元属中插入一个Canvas(画布)元素,xaml代码如下:
…
<ViewboxGrid.Row="1"Grid.Column="1">
<CanvasWidth="19"Height="19">
<RectangleWidth="19"Height="19">
<Rectangle.Fill>
<LinearGradientBrushStartPoint="0,0"EndPoint="1,1">
<LinearGradientBrush.GradientStops>
<GradientStopOffset="0"Color="Gold" />
<GradientStopOffset="1"Color="Goldenrod" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<uc:BoardControlBoardSize="19"Width="19"Height="19" />
</Canvas>
</Viewbox>
…
编译运行,是不是发现棋盘的线条偏了?这是因为我们的线条是从画布的0,0点还是画的,解决这个问题,很简单,我们只要设置棋盘控件的Margin属性即可:
<uc:BoardControlBoardSize="19"Width="19"Height="19"Margin="0.5" />
现在再运行看看。
为什么不在代码中实现棋盘的背景呢?事实是xaml的出现就想让桌面应用程序实现asp.net那样的代码和表现分离的效果,这种分离是为了更好的让界面设计人员(如美工)和程序开发人员彼此同步工作而不相互的干扰。比如假如你是一个美工,你决定给棋盘加上阴影,那么你不用懂的编程语言,你可以很容易的做到这一点,只要在棋盘背景的那个矩形下面再画一个表示阴影的矩形就可以了,代码如下:
<ViewboxGrid.Row="1"Grid.Column="1">
<CanvasWidth="19"Height="19">
<RectangleWidth="19"Height="19"Fill="Black"Opacity="0.3">
<Rectangle.RenderTransform>
<TranslateTransformX="0.2"Y="0.2" />
</Rectangle.RenderTransform>
</Rectangle>
……
WPF也有一个叫做BitmapEffect的属性可以实现各种特殊的效果,阴影、浮雕等。不过我发现使用这个属性后,程序运行变得很慢,它们占用更多的CPU资源,也许在最终的版本会解决这个问题。
OK, 将任务1.5做上标记。
数据绑定
任务1.4 为用户提供设置棋盘大小的接口。这个任务的实现看上去很简单,我们在窗体的某一个位置放置一个组合框,用户可以从中选择棋盘的大小,然后我们通过程序更新有关控件的属性。
那么,就动手吧!
在<Viewbox>元素标签的前面一行插入xaml代码如下:
<StackPanelGrid.Row="1"Grid.Column="0"Orientation="Vertical"Margin="10">
<TextBlockForeground="White"FontWeight="Bold"FontSize="14">Game Board Size</TextBlock>
<ComboBoxName="boardSizeComboBox" >
<ComboBoxItem>9</ComboBoxItem>
<ComboBoxItem>10</ComboBoxItem>
<ComboBoxItem>11</ComboBoxItem>
<ComboBoxItem>12</ComboBoxItem>
<ComboBoxItem>13</ComboBoxItem>
<ComboBoxItem>14</ComboBoxItem>
<ComboBoxItem>15</ComboBoxItem>
<ComboBoxItem>16</ComboBoxItem>
<ComboBoxItem>17</ComboBoxItem>
<ComboBoxItem>18</ComboBoxItem>
<ComboBoxItem>19</ComboBoxItem>
</ComboBox>
</StackPanel>
我们在Grid控件的第2行第1列放置一个StackPanel面板做为容器,设置它的布局方向为垂直,然后我们放入一个文本控件和一个组合框。如果你还不明白的话,可以运行一下看看效果。
当用户选择了某一个ComboBoxItem的时候,会触发组合框的SelectionChanged事件,所以我们只要注册这个事件就可以接收到用户选择的值。
添加SelectionChanged属性到ComboBox控件:
<ComboBoxName="boardSizeComboBox"electionChanged="BoardSizeSelectionChanged" >
为了能够更新棋盘的属性,我们需要围棋控件设置一个名称:
<uc:BoardControlx:Name="boardControl" BoardSize="19"Width="19"Height="19"Margin="0.5" />
切换到Source方式,在MainWindow类中,添加一个方法:
private void BoardSizeSelectionChanged(object sender, SelectionChangedEventArgs e)
{
int boardSize = int.Parse(((ComboBoxItem)boardSizeComboBox.SelectedItem).Content.ToString());
boardControl.BoardSize = boardSize;
boardControl.Height = boardControl.Width = boardSize;
boardControl.InvalidateVisual();
}
运行程序,然后用鼠标在新添加的组合框中选择棋盘的大小。发生什么了?你重新看到了前面我们遇到的问题,也就是棋盘的并不是像我们希望的那样显示,问题的原因是我们仅仅改变了棋盘控件的属性,我们没有相应地对棋盘背景、阴影和Viewbox这些控件的尺寸做更新,当然我们可以这么做,为需要更新的控件命名,然后在BoardSizeSelectionChanged中设置它们的Width和Height的值。但是我们有更好的方法,设想如果要设置的属性不只是一个BoardSize, 我们要写许多更新的代码,很郁闷不是吗?这个更好的方法就是使用数据绑定。
要使用数据绑定,首先,我们必须设计一个数据类,然后让这个类实现INotifyPropertyChanged接口。在TopGo项目中添加一个新类:GameInfo。打开GameInfo.cs文件,修改和插入代码如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
namespace TopGo
{
public class GameInfo : INotifyPropertyChanged
{
int boardSize=19;
public int BoardSize
{
get { return boardSize; }
set
{
if (boardSize != value)
{
boardSize = value;
OnPropertyChanged("BoardSize");
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
void OnPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
回到MainWindow的Xaml方式,修改代码如下:
<Windowx:Class="TopGo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:TopGo"
Title="TopGo" MinHeight="600"MinWidth="800"WindowState="Maximized"
Loaded="WindowLoaded"
>
……
<ComboBoxName="boardSizeComboBox"Text="{Binding Path=BoardSize, Mode=TwoWay}" SelectionChanged="BoardSizeSelectionChanged" >
......
<ViewboxGrid.Row="1"Grid.Column="1">
<CanvasWidth="{Binding Path=BoardSize}"Height="{Binding Path=BoardSize}">
<RectangleWidth="{Binding Path=BoardSize}"Height="{Binding Path=BoardSize}"Fill="Black"Opacity="0.3">
<Rectangle.RenderTransform>
<TranslateTransformX="0.2"Y="0.2" />
</Rectangle.RenderTransform>
</Rectangle>
<RectangleWidth="{Binding Path=BoardSize}"Height="{Binding Path=BoardSize}">
<Rectangle.Fill>
<LinearGradientBrushStartPoint="0,0"EndPoint="1,1">
<LinearGradientBrush.GradientStops>
<GradientStopOffset="0"Color="Gold" />
<GradientStopOffset="1"Color="Goldenrod" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<uc:BoardControlx:Name="boardControl" BoardSize="{Binding Path=BoardSize}"Margin="0.5" />
</Canvas>
</Viewbox>
切换到Source方式:
修改MainWindow类的代码如下:
public partial class MainWindow : System.Windows.Window
{
GameInfo gameInfo = new GameInfo();
……
private void WindowLoaded(object sender, RoutedEventArgs e)
{
this.DataContext = gameInfo;
boardControl.Height = boardControl.Width = boardControl.BoardSize - 1;
}
private void BoardSizeSelectionChanged(object sender, SelectionChangedEventArgs e)
{
boardControl.Height = boardControl.Width = boardControl.BoardSize - 1;
boardControl.InvalidateVisual();
}
}
在WindowLoaded方法中,我们设置GameInfo实例对象到MainWindow的DataContext属性,这样Xaml中数据绑定的路径的根就是GameInfo。同时注意到我们显式的对棋盘控件的高度和宽度进行赋值,这是因为我们不能xaml中方便的对它们进行赋值,这里它们的值比BoardSize小1(想想为什么?)。
另外,我们对组合框绑定的是Text属性,同时设置绑定的模式为双向,这样当组合框的内容改变的时候,改变的内容直接更新到数据源,也就是GameInfo中的BoardSize属性。
编译运行,然后试着选择不同的棋盘大小,看看棋盘的显示是不是我们希望的那样。
Ok, 给任务1.4做上标记。如果你是用户,你对现在这个棋盘还满意吗?
我听到你在嘀咕:好像少了什么?
是的,少了什么呢?如果你在网络上下过围棋,你会发现那些棋盘旁边都显示有坐标,纵坐标从上到下是阿拉伯数字,横坐标是从左到右是英文字母。
给我们的任务表添上新的任务:
1.6 显示棋盘坐标(提供隐藏棋盘坐标的功能);
然后,休息。我们下一次再继续。
(待续)
- .Net Framework3.0 实践纪实(3)
- .Net Framework3.0 实践纪实(1)
- .Net Framework3.0 实践纪实(2)
- .Net Framework3.0 实践纪实(4)
- .Net Framework3.0 实践
- Introduction of .NET Framework3.0 (3)
- .net framework3.0
- .NET Framework3.0答疑
- Introduction of .NET framework3.0 (1)
- Introduction of .NET framework3.0 (2)
- 关于.NET FrameWork3.0的一些知识
- 关于.NET FrameWork3.0的一些知识
- 用.Net Framework3.5开发,用.Net Framework2.0部署
- 构建自己的.NET Framework3.0开发环境
- 我们需要.NET framework3.0的流程设计器吗
- Windows 2012安装.NET Framework3.5(0x800F0907)
- VS2008 and .NET Framework3.5
- 一、.NET Framework3.5简介
- Session与Cookies
- NSIS 2.19 by NSIS team
- 面试技巧:一分钟自我介绍
- C++的救赎
- acroread的一点设置
- .Net Framework3.0 实践纪实(3)
- 走向辉煌:创业家的“冠军相”
- ArmaGUI 1.5.2 by Spec0p
- VFP编写DLL,并调用
- 孙鑫VC学习笔记:第九讲外观
- 了解UGS PLM步骤
- .net的辅助工具列表
- 一条短信发错,变成了群发之后……
- eclipse qiuck key