王朝网络
分享
 
 
 

.Net Framework3.0 实践纪实(4)

王朝c#·作者佚名  2006-08-15
宽屏版  字体: |||超大  

.Net Framework3.0 实践纪实(4)

可视对象和棋子

任务1.6的实现和画棋纵横线没有很大的差别,设置好字体、要显示的坐标的文字,然后调用DrawingContext的DrawText方法在指定的位置画出对象即可。为了满足任务中的要求,我们设置一个开关字段和对应的属性,代码如下:

public partial class BoardControl : Canvas

{

double scale = 0.03;

readonly int[,] demarkCount ={ { 2, 3 }, { 3, 3 }, { 3, 4 }, { 3, 5 }, { 3, 6 } };

bool showCoordinate = true;

Brush coordinateColor =

Brushes.White;

……

public bool ShowCoordinate

{

get { return showCoordinate; }

set

{

if (showCoordinate != value)

{

showCoordinate = value;

InvalidateVisual();

}

}

}

……..

protected override void OnRender(DrawingContext dc)

{

……

if (showCoordinate)

{

double fontSize = 0.4;

for (int i = 1; i <= BoardSize; i++)

{

FormattedText fmtText = new FormattedText(i.ToString(), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Arial"), fontSize, coordinateColor);

fmtText.TextAlignment = TextAlignment.Right;

dc.DrawText(fmtText, new Point(-0.5, (i - 1 - fmtText.Height * .5)));

fmtText =new FormattedText(((char)(i + 64)).ToString(), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Arial"), fontSize, coordinateColor);

fmtText.TextAlignment = TextAlignment.Center;

dc.DrawText(fmtText, new Point(i - 1, BoardSize - 0.5));

}

}

}

……

编译运行。为了测试显示隐藏坐标的功能,切换到MainWindow的Xaml方式,插入如下代码:

<StackPanelGrid.Row="1"Grid.Column="0"Orientation="Vertical"Margin="10">

……

<CheckBoxName="coordinateCheckBox"Click="ShowCoordinateClick"IsChecked="True"

FontSize="14"Foreground="White"FontWeight="Bold"Margin="10">Show Coordinate</CheckBox>

</StackPanel>

切换到Source方式,添加ShowCoordinateClick方法,代码如下:

……

private void ShowCoordinateClick(object sender, RoutedEventArgs e)

{

boardControl.ShowCoordinate = coordinateCheckBox.IsChecked == true ? true : false;

}

……

重新运行程序,测试一下显示隐藏坐标的功能。

给任务1.6做上标记,到目前任务1中全部的子任务现在都已经完成,所以我们给任务1也做上标记。检查任务表,还有两个任务,那就是任务2和任务3。任务2明显要先于任务3。所以我们先处理任务2。

任务2:TopGo的棋盘必须可以在一个指定的位置显示指定颜色的棋子;

要完成任务2,同样我们可以把它分解成2个小任务:

2.1 在指定位置画黑色棋子

2.2 在指定位置画白色棋子

我们开始任务2.1:棋子怎么画?最简单的方法就是画一个黑色的圆,但为了更好的显示效果,我们可以给棋子加上光线效果,让棋子有立体感。因为棋盘背景的光线是从左上角到右下角,所以棋子的光线方向应该和棋盘一致。受目前的cider版本的限制,我们无法可视化的在xaml中设计棋子,但你可以使用sdk自带的xmalpad来进行这项工作,具体的操作就不详细描述了,我只是提一下设计思路:先画一个黑色的圆,然后在这个圆的右上角附近画一个小的椭圆,用白色到透明的辐射渐变填充这个小的椭圆来表示高光,调整小椭圆的位置。

在xmal的棋盘控件元素的子元素中,放置显示棋子的xaml代码,就可以显示棋子。不过进一步考虑之后我们发现:棋子的位置我们事先是不知道的。它必须先是在用户指定的地方,要解决这个问题,有几种方法,我们可以把棋子的显示绑定到一个棋子的集合中,但这种方法效率不高,因为每一次数据源发生变化,棋子就会重画,也就是说一盘棋右上百个棋子,如果每次都重画它,效果肯定好不了。如果我们在OnRender方法中像画棋盘纵横线那样画棋子,也存在其他方面的问题,比如窗体大小发生改变的时候,也需要手工的调用代码来重画每个棋子。在WPF中,对象是按照树型结构组织的,当显示父控件的时候,子控件是自动被计算并显示。所以我们可以把画出表示棋子的对象插入到棋盘的子控件集合中。不过,我们还是一步一步来进行,给我们的任务表插入如下任务:

2.1.1 画出黑色圆形

2.1.2 画出高光

任务2.1.1:

首先在OnRender方法中添加语句(仅仅为了开始我们的任务),然后添加DrawStone的方法:

protected override void OnRender(DrawingContext dc)

{

……

DrawBlackStone(dc, new Point(0,0));

}

private void DrawBlackStone(DrawingContext dc, Point pos)

{

EllipseGeometry stone = new EllipseGeometry(pos, 0.49, 0.49);

dc.DrawGeometry(

Brushes.Black, null, stone);

}

我们这里没有使用Ellipse类,而是使用Geometry几何对象,主要的好处是我们可以直接指定圆的中点和半径,因为棋盘的网格的大小是1*1,我们的棋子必须略小于网格,这里设置为0.98,所以半径就是0.49。

编译运行。

任务2.1.2:

画棋的高光,我们先在左边的边缘画出一个椭圆高光,然后在旋转45度到正确的位置。在DrawStone方法中添加代码:

private void DrawBlackStone(DrawingContext dc, Point pos)

{

EllipseGeometry stone = new EllipseGeometry(pos, 0.49, 0.49);

EllipseGeometry highLight = new EllipseGeometry();

highLight.Center = pos - new Vector(0.3, 0);

highLight.RadiusX = 0.15;

highLight.RadiusY = 0.25;

LinearGradientBrush highLightBrush = new LinearGradientBrush(Colors.White, Colors.Transparent, new Point(0, 0), new Point(1, 0));

dc.PushTransform(new RotateTransform(45, pos.X, pos.Y));

dc.DrawGeometry(

Brushes.Black, null, stone);

dc.DrawGeometry(highLightBrush, null, highLight);

}

编译运行,观看效果,基本能够达到我们的要求。通过设置pos参数,可以在不同的位置显示棋子(自行测试)。如果没有问题,就给任务2.1.1和任务2.1.2以及任务2.1做上标记。

能够画出黑棋,用同样的原理,我们也可以画出白棋,不过有一点不同就是白棋的底色我们做成了辐射渐变,具体参见代码:

在OnRender方法中添加代码:

protected override void OnRender(DrawingContext dc)

{

……

DrawBlackStone(dc, new Point(0,0));

DrawWhiteStone(dc, new Point(1, 0));

}

private void DrawWhiteStone(DrawingContext dc, Point pos)

{

EllipseGeometry stone = new EllipseGeometry(pos, 0.49, 0.49);

EllipseGeometry highLight = new EllipseGeometry();

highLight.Center = pos - new Vector(0.3, 0);

highLight.RadiusX = 0.15;

highLight.RadiusY = 0.25;

LinearGradientBrush highLightBrush = new LinearGradientBrush(Colors.White, Colors.Transparent, new Point(0, 0), new Point(1, 0));

RadialGradientBrush stoneBrush =new RadialGradientBrush(Colors.White, Colors.Gray);

stoneBrush.GradientOrigin = new Point(0.25, 0.5);

stoneBrush.RadiusX = 1.5;

stoneBrush.RadiusY = 1.7;

dc.PushTransform(new RotateTransform(45, pos.X, pos.Y));

dc.DrawGeometry(stoneBrush, null, stone);

dc.DrawGeometry(highLightBrush, null, highLight);

dc.Pop();

}

编译运行。

给任务2.2做标记。似乎也可以给任务2做标记,但是还不能,看一下代码,我们发现两个画棋子的方法有许多重复的代码,而且也有许多硬编码的数字。这是应该对代码进行重构的信号。

通过分析两个画棋子的代阿,我们发现除了黑白棋子使用的笔刷不同,其他的都一样,所以我们可以合并两个方法为一个方法,为了区分画的是白棋还是黑棋,我们引入一个枚举类型:

首先,在项目中添加一个新的文件:StoneColor.cs

代码如下:

public enum StoneColor

{

None,

Black,

White

}

接着,我们在BoardControl类中,创建一个新的方法DrawStone:

private void DrawStone(DrawingContext dc, Point pos, StoneColor color)

{

if(color==StoneColor.None)

return;

EllipseGeometry stone = new EllipseGeometry(pos, 0.49, 0.49);

EllipseGeometry highLight = new EllipseGeometry();

highLight.Center = pos - new Vector(0.3, 0);

highLight.RadiusX = 0.15;

highLight.RadiusY = 0.25;

LinearGradientBrush highLightBrush = new LinearGradientBrush(Colors.White, Colors.Transparent, new Point(0, 0), new Point(1, 0));

Brush stoneBrush = null;

if (color == StoneColor.Black)

stoneBrush =

Brushes.Black;

else

{

stoneBrush =new RadialGradientBrush(Colors.White, Colors.Gray);

((RadialGradientBrush)stoneBrush).GradientOrigin = new Point(0.25, 0.5);

((RadialGradientBrush)stoneBrush).RadiusX = 1.5;

((RadialGradientBrush)stoneBrush).RadiusY = 1.7;

}

dc.PushTransform(new RotateTransform(45, pos.X, pos.Y));

dc.DrawGeometry(stoneBrush, null, stone);

dc.DrawGeometry(highLightBrush, null, highLight);

dc.Pop();

}

修改OnRender中的两个调用方法为:

DrawStone(dc, new Point(0,0),StoneColor.Black);

DrawStone(dc, new Point(1, 0),StoneColor.White);

……

删除原来的DrawBlackStone和DrawWhiteStone

最后我们把硬编码的数字全部替换为常量:

重构的一个原则就是不能对原来的功能作修改。重构后的代码如下:

const double STONE_R = 0.49;

const double HIGH_LIGHT_OFFSET_X = 0.3;

const double HIGH_LIGHT_OFFSET_Y = 0;

const double HIGH_LIGHT_RX = 0.15;

const double HIGH_LIGHT_RY = 0.25;

const double STONE_BRUSH_ORIGIN_X = 0.25;

const double STONE_BRUSH_ORIGIN_Y = 0.5;

const double STONE_BRUSH_RX = 1.5;

const double STONE_BRUSH_RY = 1.7;

const double STONE_ROTATE_ANGLE = 45;

private void DrawStone(DrawingContext dc, Point pos, StoneColor color)

{

if(color==StoneColor.None)

return;

EllipseGeometry stone = new EllipseGeometry(pos, STONE_R, STONE_R);

EllipseGeometry highLight = new EllipseGeometry();

highLight.Center = pos - new Vector(HIGH_LIGHT_OFFSET_X, HIGH_LIGHT_OFFSET_Y);

highLight.RadiusX = HIGH_LIGHT_RX;

highLight.RadiusY = HIGH_LIGHT_RY;

LinearGradientBrush highLightBrush = new LinearGradientBrush(Colors.White, Colors.Transparent, new Point(0, 0), new Point(1, 0));

Brush stoneBrush = null;

if (color == StoneColor.Black)

stoneBrush =

Brushes.Black;

else

{

stoneBrush =new RadialGradientBrush(Colors.White, Colors.Gray);

((RadialGradientBrush)stoneBrush).GradientOrigin = new Point(STONE_BRUSH_ORIGIN_X, STONE_BRUSH_ORIGIN_Y);

((RadialGradientBrush)stoneBrush).RadiusX = STONE_BRUSH_RX;

((RadialGradientBrush)stoneBrush).RadiusY = STONE_BRUSH_RY;

}

dc.PushTransform(new RotateTransform(STONE_ROTATE_ANGLE, pos.X, pos.Y));

dc.DrawGeometry(stoneBrush, null, stone);

dc.DrawGeometry(highLightBrush, null, highLight);

dc.Pop();

}

现在可以对任务2做上标记了。

任务3:棋子的位置可以通过鼠标来指定(准确地说是鼠标左键)

任务3看上去好像并不难,你只要注册BoardControl的鼠标事件,就可以得到鼠标的位置,然后把位置和颜色的信息传递回BoardControl就可以画出棋子,不过有些问题需要解决,让我们边做边看会是什么问题。

首先,我们不能在Xaml文件中像其他控件那样写MouseLeftButtonDown=”…”, Cider并不是别用户控件的事件,除非是重载这个事件,所以我们可以显式的在代码中进行注册:

在MainWindow的构造器中添加一行代码:

public MainWindow()

{

InitializeComponent();

boardControl.MouseLeftButtonDown +=new MouseButtonEventHandler(boardControl_MouseLeftButtonDown);

}

通过智能提示,自动生成下面的方法签名:

void boardControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

{

throw new Exception("The method or operation is not implemented.");

}

为了传递数据给棋盘控件,我们也设计一个新的类Stone来表示我们的棋子对象,对项目添加一个新的类Stone, 代码如下:

public class Stone

{

StoneColor color = StoneColor.None;

Point position;

public Stone() : this(StoneColor.None, new Point(-1, -1)) { }

public Stone(StoneColor color, Point position)

{

this.color = color;

this.position = position;

}

public StoneColor Color

{

get { return color; }

set { color = value; }

}

public Point Position

{

get { return position; }

set { position = value; }

}

}

向BoardControl传递数据,我们设想BoardControl有一个方法叫做AddStone(Stone stone)提供我们调用,所以boardControl_MouseLeftButtonDown中的代码如下:

void boardControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

{

Point pos = e.GetPosition(boardControl);

pos.X=(int)Math.Floor(pos.X+0.5);

pos.Y=(int)Math.Floor(pos.Y+0.5);

Stone stone =new Stone(StoneColor.Black, pos);

boardControl.AddStone(stone);

}

代码中,我们必须鼠标返回的位置做四舍五入取整。否则棋子的位置可能会出现偏差。

然后我们在BoardControl中添加新的方法:

public void AddStone(Stone stone)

{

DrawingVisual dv = new DrawingVisual();

DrawingContext dc = dv.RenderOpen();

DrawStone(dc, stone.Position, stone.Color);

dc.Close();

}

DrawingVisual是我们第一次见到的WPF的可视对象,它派生于Visual类,有关的资料参阅文档中的描述。

AddStone通过dv对象打开一个DrawingContext, 这样我们就可以调用DrawStone来画出棋子。删除OnRender中的最后两个调用DrawStone的语句,我们不需要它了。如果你现在编译运行,当你点击棋盘,期待出现黑色的棋子,结果一定让你失望。因为什么都没有发生!

这是因为在AddStone的方法中,创建的dv没有加入到棋盘的子控件中,很不幸的是,你不能把dv添加到Children中,因为Children要求UIEmemt类型或者派生类的对象。 解决的方法就是我们自己定义一个集合,然后重载BoardControld的GetVisualChild和VisualChildrenCount两个方法。

在BoardControl类中添加字段:

VisualCollection stoneVisuals = null;

在构造器的开始创建stoneVisuals的实例:

public BoardControl()

{

stoneVisuals =new VisualCollection(this);

InitializeComponent();

}

在AddStone方法的最后面添加一行代码:

public void AddStone(Stone stone)

{

……

stoneVisuals.Add(dv);

}

两个重载方法的代码:

protected override Visual GetVisualChild(int index)

{

if (index < 0 || index > stoneVisuals.Count)

return base.GetVisualChild(index);

return stoneVisuals[index];

}

protected override int VisualChildrenCount

{

Get{ return stoneVisuals.Count;}

}

重新编译运行。

你可以通过修改调用AddStone方法的stone参数的Color属性来看白色棋子的显示。

我们可以模拟下棋的时候黑白棋子交替的场景,只要做些许的修改:

切换到MainWindow的Source方式,添加一个字段:

StoneColor currentColor = StoneColor.Black;

添加一个方法:

private void ExchangColor()

{

currentColor = currentColor == StoneColor.White ? StoneColor.Black : StoneColor.White;

}

最后修改boardControl_MouseLeftButtonDown方法中的代码如下:

void boardControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

{

Point pos = e.GetPosition(boardControl);

pos.X=(int)Math.Floor(pos.X+0.5);

pos.Y=(int)Math.Floor(pos.Y+0.5);

Stone stone =new Stone(currentColor, pos);

boardControl.AddStone(stone);

ExchangColor();

}

通过模拟,我们发现了新的任务,我们注意到了几个问题,那就是不允许在已经有棋子的地方落子;第二个问题那就是我们可以落子,也必须能够删除已有的棋子,这在以后的功能开发中是必要的,比如在Undo或者提子(吃子)的场合。第三,为了方便打谱,必须有一个选项能够在棋子上面显示该棋子的手序。为此我们添加新的任务到任务表。

5、 检测禁止落子点

6、 显示和隐藏棋子的手序数

7、 提供回退和自动提子功能

别忘了给任务3做上标记。

(待续)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有