.Net Framework3.0 實踐紀實(3)

  

.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.1 棋盤在界面上的位置
1.2 畫棋盤的縱橫線(標準爲19*19),棋盤的大小必須可以動態設置比如說(10*10
1.3 畫出棋盤上的星(星的數量應該和棋盤大小一致)
        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 顯示棋盤座標(提供隱藏棋盤座標的功能);
然後,休息。我們下一次再繼續。
(待續)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章