欢迎光临
我们一直在努力

.Net Framework3.0 实践纪实(4)-.NET教程,.NET Framework

建站超值云服务器,限时71元/月
可视对象和棋子
任务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=1grid.column=0orientation=verticalmargin=10>
        ……
                <checkboxname=coordinatecheckboxclick=showcoordinateclickischecked=true
                  fontsize=14foreground=whitefontweight=boldmargin=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做上标记。
(待续)
赞(0)
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » .Net Framework3.0 实践纪实(4)-.NET教程,.NET Framework
分享到: 更多 (0)