[转载]构建 WPF 数独游戏第一部分:WPF 和 XAML 简介

*

每次要学点新东西时,我都会发现使用教程比阅读文档要简单、省事得多。假定大多数人都有同感,难道您真的会在尝试使用某个东西之前先阅读说明吗?我肯定不会。为此,我已经决定直接付诸行动,演练如何构建一个 Windows Presentation Foundation (WPF) 应用程序。由于这是 Coding4Fun,而且世界上有足够多的企业-Web 2.0-数据-门户-小玩意,那么就让我们面对这个现实,来准备做一个游戏吧!遗憾的是,我认为在此直接制作 Halo 3 游戏并不现实,因此,我认为更小的游戏可能更合适,也就是说在这些教程结束时能够完成的游戏,比如说数独游戏。嗨,好处是您可以在工作中玩数独游戏而不会受惩罚!好吧,那么我们要怎么开始呢?按以下顺序安装:

Visual C# 2005 Express Edition

WinFX February 2006 CTP

Windows SDK February 2006 CTP

Visual Studio Extensions for WinFX February 2006 CTP

还需要运行 Windows XP SP2、Windows Server 2003 SP1 或 Vista February CTP。

当然,我的工作站上碰巧安装了所有这些软件(太好了)。不过既然一切都已经安装好了,就让我们开始吧。激活 VC#,新建一个项目,然后选择"WinFX Windows Application"。(如果看不到这一项,您应该确保安装了 Visual Studio Extensions,因为该项是一项新增的内容。)。我输入"SudokuFX"作为项目名称,您将在屏幕快照中看到该项目名,但这取决于您给项目起了什么名字。您马上就会看到以下内容:

<Window x:Class="SudokuFX.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="SudokuFX" Height="300" Width="300" > 
    <Grid> 
    
    </Grid> 
</Window>

这是 XAML,Microsoft 新的声明性编程语言。XAML 中的类可以同时具有代码(C# 或 VB.NET)组件和声明性 (XAML) 组件。Window 标记的 x:Class 属性指定了我们正在定义的类的 C# 组件名。C# 部分应当如下所示:

namespace SudokuFX
{
    public partial class Window1 : Window
    {

        public Window1()
        {
            InitializeComponent();
            
        }

    }
}

编译并运行该程序后,这两部分结合在一起从而创建一个完整的 Window1 类,该类从 WinFX 提供的 System.Windows.Window 类派生。在这里,您唯一需要了解的还不明确的事情是,用 XAML 声明的所有对象均可从代码访问,反之亦然。事实上,根本不需要使用 XAML,您也可以使用相同的 API 编写一个类!嵌套元素放在一个集合中,该集合存储为这些元素父级的 Children 属性,或者存储为分配给 Content 属性的一个对象。换言之,这段 XAML 代码创建了一个 Grid 实例,并将其放在窗口的 Content 属性中。

那么,我们如何开始着手设计应用程序的布局呢?基本上,理想的布局是:顶端有一个标题,左边是游戏菜单(类似于 Windows 资源管理器中的"任务"窗格),右边是游戏计时信息,底部是移动历史记录,中间是板。当然,这都应该与分辨率无关,而且能够动态调整大小和流动。(如果您以前使用过 Windows 窗体,那么当您偏头痛发作时,额头可能已经开始冒汗了,不过不用担心,动态调整大小和流是 WinFX 的默认设置)。最适合该任务的容器是明显命名的 DockPanel。DockPanel 是一个容器控件,它根据子级要放置到哪一边来安排它们;然后,添加的最后一个元素用于填充剩余空间。例如,用以下内容替代 Grid 标记:

<DockPanel>
    <TextBlock Background="Red" DockPanel.Dock ="Top" FontSize ="36">
Sudoku
</TextBlock>
    <StackPanel Background="Green" DockPanel.Dock ="Left">
        <Button>A</Button>
    </StackPanel>
    <StackPanel Background="Blue" DockPanel.Dock ="Right">
        <Button>B</Button>
    </StackPanel>
    <ListBox Background ="Gray" DockPanel.Dock="Bottom"/>
    <StackPanel Background ="Yellow"/> 
</DockPanel>

DockPanel.Dock 语法用于引用每个子级,但要特定于容器的属性。另一个可以说明这种情况的示例是 Canvas.Left 属性,它与 Canvas 中的元素一起使用,Canvas 是一个允许显式确定元素位置的容器控件。StackPanel 容器在垂直堆栈或水平堆栈中排列自己的子级:向上、向下、向左,或向右。在该示例中,我添加了一些按钮来填补面板,这样面板就不会缩小到没有了。我还指定了一些过分鲜艳刺眼的背景色(稍后会删除),以便准确显示所有内容的摆放位置。如果编译并运行该程序,会得到以下奇怪的结果:

alt

好吧,现在该努力编写一些代码来运行基本的用户界面 (UI) 了。首先,我们来布置左面板。其中将包括主菜单和新的游戏设置。我们应该将它们放在扩展器控件中,以防任何使用我们程序的人以 800x600 的分辨率运行(我知道这很恐怖,但我见过)。

<Expander IsExpanded ="True" Header ="Main Menu">
        <StackPanel>
            <Button>New Game</Button>
            <Button>Load Game</Button>
            <Button>Save Game</Button>
            <Button>Quit</Button>
        </StackPanel> 
    </Expander>
    <Expander IsExpanded ="True" Header ="New Game Settings">
        <StackPanel>
            <TextBlock>Board Size:</TextBlock>
            <ComboBox IsEditable ="False">
                <ComboBoxItem IsSelected ="True">9x9</ComboBoxItem>
                <ComboBoxItem>16x16</ComboBoxItem>
                <ComboBoxItem>25x25</ComboBoxItem>
                <ComboBoxItem>36x36</ComboBoxItem>
            </ComboBox>
        </StackPanel> 
    </Expander> 
</StackPanel>

这会生成以下界面:

alt

好吧,现在可能看起来不太可怕,但等一下:很快就会有其他内容了!以类似方式充实该 UI 的其余部分,并为板(在以后的教材中,我们将制作板)添加虚拟图像,我们将获得以下界面:

alt

现在,我们应该如何改进该 UI 的外观呢?一种方法是单独调整控件的所有属性,使其外观看起来更好;另一种方法是使用某种皮肤设置。遗憾的是,这两种方法向您的代码中添加了一堆乱七八糟的东西,且难以维护。相反,我们可以使用 WPF 的样式概念。通过定义一组新的默认属性值,WPF 使得在给定范围内(例如,作为一个整体的应用程序、一个特殊的对话框,或者一个容器)自定义控件变得十分简单。可以通过将样式添加到其他对象的 Resources 集合中来实现该任务。在 XAML 中,如果要将一个更复杂的对象或对象集合分配给一个属性,可以将该属性的语法从 Property="Value" 扩展为语法:

<Object.Property> 
    <ValueObject/> 
</Object.Property>

例如,如果没有显式设置为其他颜色,下面的代码将蓝色指定为应用程序中所有按钮的背景色:

<Application.Resources>
    <Style TargetType ="{x:Type Button}"> 
       <Setter Property ="Background" Value ="Blue"/> 
    </Style> 
 </Application.Resources>

也可以使用 x:Key 属性来命名样式。之所以使用 x:Key 而不使用 x:Name,是因为 <Application.Resources> 实际上是一个键值对列表。例如,我们可以定义:

<Application.Resources> 
    <Style x:Key ="BlueButton" TargetType ="{x:Type Button}"> 
       <Setter Property ="Background" Value ="Blue"/> 
    </Style>
</Application.Resources>

这段代码创建了一种仅适用于某些按钮的样式,这些按钮通过其 Style 属性引用该样式。例如:

<Button Style="{StaticResource BlueButton}"/>

也可以定义窗口、面板或其他具有 Resources 属性的对象中的资源。当代码引用某个资源时,进行应用程序级别的搜索。这样,<Window.Resources> 中定义的样式或其他资源就只能在该窗口内应用。

到目前为止,我尚未解释过一直使用的括号符号。本质上说,括号用于声明对其他对象的引用,而不是对其进行实例化。例如,{x:Type Button} 引用 Button 类,而 {StaticResource BlueButton} 搜索具有"BlueButton"键的项目的资源层次。StaticResource 指令指出,该搜索仅在首次创建对象时执行;相反,DynamicResource 指令指定该值应不断更新。

现在,我们可以开始添加一种样式以改善扩展器的外观。我将以下内容添加到 <Application.Resources> 部分中:

<Style TargetType ="{x:Type Expander}"> 
    <Setter Property ="Background"> 
      <Setter.Value> 
        <LinearGradientBrush StartPoint ="0,0" EndPoint ="1,0"> 
          <LinearGradientBrush.GradientStops> 
            <GradientStop Color ="LightGray" Offset ="0"/> 
            <GradientStop Color ="Gray" Offset ="1"/> 
          </LinearGradientBrush.GradientStops> 
        </LinearGradientBrush> 
      </Setter.Value>
    </Setter> 
    <Setter Property ="BorderBrush" Value ="DimGray"/> 
    <Setter Property ="BorderThickness" Value ="1"/> 
    <Setter Property ="Margin" Value ="5"/> 
    <Setter Property ="HorizontalContentAlignment" Value ="Stretch"/> 
    <Setter Property ="Foreground" Value ="White"/> 
    <Setter Property ="VerticalContentAlignment" Value ="Stretch"/> 
 </Style>

这是一种比较简单的样式,它很好地演示了双属性分配语法。背景设置为两种灰度之间的水平渐变,其余属性设定为与背景很好融合的合理的默认值。通过在扩展器内容中添加一个 Border 标记,我还在控件内部的周围添加了一个额外的边框,如下所示:

<Expander IsExpanded ="True" Header="Main Menu">
    <Border Margin ="5" Padding ="10" 
        Background ="#77FFFFFF" BorderBrush ="DimGray" BorderThickness ="1">
        <StackPanel>
            <Button>New Game</Button>
            <Button>Load Game</Button>
            <Button>Save Game</Button>
            <Button>Quit</Button>
        </StackPanel> 
    </Border>
</Expander>

Margin 属性指定扩展器控件外部的边框间距,Padding 属性指定内部的额外间距。请注意,只有容器控件才有内部间距。这两个值由四个逗号分隔的值(分别针对左、上、右、下)指定,如果四个值相同(如此处所示),则用一个数字指定。

如果您觉得这有点儿像杂牌军,那就对了。事实上,我本来可以使用自定义样式在控件自身内部包括额外边框。为此,您可以修改所谓的控件模板,但那是另一个话题,我不会在此进行讨论。不要担心,我们稍后会回来修正这个问题。制定了其他一些样式并为窗口背景添加了一个很好的渐变之后,应用程序看上去好多了……嗯,至少色彩更丰富了!如果下载该代码,您可以查看这些样式。

alt

最后,为了完成教程的这一部分,我们添加一些简单的事件处理以使 Quit 按钮工作。实际上非常简单。只需定义一个符合 Window1 类中 RoutedEventHandler 委托的方法。例如:

void QuitClicked(object sender, RoutedEventArgs e)
{
this.Close(); 
}

然后,只需将按钮的 Clicked 属性设置为处理程序的名称,如下所示:

<Button Click ="QuitClicked">Quit</Button>

现在,如果您单击 Quit 按钮,程序将退出。这实际上是处理简单事件所需的一切操作!

好了,眼下没有别的事情了。我希望该教程至少让您对 WPF 应用程序的工作方式有所了解,以及如何开始构建您的第一个 WPF 应用程序。我们只是向您初步介绍了使用该框架和 .NET 2.0 可以完成的工作!别走开,本教程接下来的几部分将完成该应用程序,并介绍以下几个很酷的功能:

数据绑定:UI 将自动显示并操作程序的对象

自定义控件以及控件皮肤:完全更改现有控件的外观,或者创建自己的控件

很酷的效果、反射、过渡以及自动运行的动画

触发器:将 UI 配置为自己管理,控制其外观而无需任何代码

创建插件系统:将使用任何 .NET 语言编写的插件加载到一个沙箱中

使用 .NET Framework 编写多线程代码

posted on 2011-01-14 13:23  guoxuefeng  阅读(322)  评论(0编辑  收藏  举报