WPF程序设计读书笔记(1-1)
第1章
应用程序和窗口
为 WPF
开发应用程序,一般来说,一开始需要花一点点时间创建Application
对象与Window
对象。下面是一个很简单的WPF
程序:
//*********************************************************
//SayHello.cs 2010 by mouyong
//*********************************************************
using System;
using System.Windows;
//System.Windows这个命名空间包含了所有的基本WPF类、结构、
//接口、委托、以及枚举类型。
namespace part1.ch01
{
class SayHello
{
[STAThread]
//[STAThread]是Single Thread Apartment单线程套间
//的意思,是一种线程模型,用在程序的入口方法上
//(即Main方法),其它方法无效
public static void Main()
{
Window win = new Window();
win.Title = "Say Hello";
win.Show();
Application app = new Application();
app.Run();
}
}
}
本书的范例具有一致的命名方式。每个程序都属于一个特定的 Microsoft Visual Studio
项目。项目中的所有代码都被包含在一个统一的命名空间,开头是本书的部分,然后是章节名。以第一个范例来说,由于它是第一部分的第一章,所以就是part1.ch01
。工程中的每个类都有一个独立的文件,而且文件名一般都与类名一致。
任何一个 WPF
程序,Main
的前面都必须有个[STAThread]
属性,否则会出现运行时错误。这个属性是用来申明该应用程序的初始线程模型为单线程,以便和COM
(Component Object Model
)兼容。它是.NET
之前的词汇,但基于我们此刻的目的,你可以把它理解成:STAThread
表示我们的应用程序不会使用“源自运行环境”的多线程。
在 SayHello
程序中,Main
一开始创建一个Window
类的对象,这个类用来创建标准应用程序窗口。Title
属性是显示在窗口标题栏里的文字,而Show
方法会将窗口显示在屏幕上。
这里最重要的步骤是,调用 Application
对象的Run
方法。在传统Windows
编程的思维中,这么做的目的是要建立一个消息循环,让应用程序可以接收用户键盘或鼠标输入。
(默然说话:这个工程我是在
VS2008下面建立的,步骤如下
1.从“文件”菜单选择“新项目”。
2.在“新项目”对话框中,选取“Visual C#”,“控制台应用程序”。指定一个存放该项目的目录,将项目命名为part1,然后按下“确定”
3.在项目的“引用”栏中必须包含“PresentationCore”、“PresentationFramework”、“System”以及“WindowsBase”。如果没有,请右击“引用”栏,选择“添加引用…”,在对话框中选择“.NET”选项卡,然后选取以上提到的DLLS,然后按下“确定”。
4.鼠标右键选择项目,然后“添加”、“类…”,键入文件名SayHello.cs,最后按下“添加”
5.将前面的代码输入到SayHello.cs文件中。
6.按Ctrl+F5执行该程序
)
本书第一部分所示的大部分程序,都采用上面相同的步骤来建立项目,只有某些涉及多个源代码文件的项目做法不太一样。
在 SayHello
被运行时,你会发现一个Consol
窗口也在运行。这是源自编译选项的设定。在开发阶段其实Consol
相当有用。程序运行时可以利用它来显示一些文本信息,以便调试。如果程序出了问题,也可以在Consol
窗口中键入Ctrl+C
来轻易关闭程序。这些都是Consol
窗口听附带好处。
在一程序中,只能创建一个 Application
对象,对程序的其他地方来说,此Application
对象的作用就如同固定的锚一般。你在屏幕上是看不见Application
对象,但可以见到Window
对象。
你可以在创建 Window
对象之前,先创建Application
对象,但Run
方法的调用必须在最后。Run
一旦被调用,就不会返回(默然说话:意思即代码将不会执行到
Run方法后面,直到Run方法结束
)Run
方法返回后,Main
方法结束,Windows
操作系统会做一些清除工作。
我们也可以不调用 Window
对象的Show
方法,而是直接将Window
对象当作参数传给Run
方法:
app.Run(win);
应用程序在调用 Run
方法后才真正开始运行。也只有在调用Run
方法之后,Window
对象才能响应用户的输入。当用户关闭窗口时,Run
方法就会返回,程序也就结束。
在初始化之后,程序所做的事情,就是在响应各种事件。这些事件通常是关于键盘、鼠标、或手写笔的输入。 UIElement
定义了和键盘、鼠标、手写笔相关的事件;Window
类继承了所有的事件。
下面是一个范例,稍微将 Main
里的语句次序做了改变,同时也安装了一个事件处理器,专门处理MouseDown
事件:
//*********************************************************
//HandleAnEvent.cs 2010 15th July by mouyong
//*********************************************************
using System;
using System.Windows;
using System.Windows.Input;
namespace part1.ch01
{
class IneritTheApp : Application
{
[STAThread]
public static void Main()
{
IneritTheApp app = new IneritTheApp();
app.Run();
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window win = new Window();
win.Title = "继承App";
win.Show();
}
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
{
base.OnSessionEnding(e);
MessageBoxResult result = MessageBox.Show("你是否需要保存数据?",MainWindow.Title,MessageBoxButton.YesNoCancel,MessageBoxImage.Question,MessageBoxResult.Yes);
e.Cancel=(result==MessageBoxResult.Cancel);
}
}
}
MouseDown
事件所需要的事件处理器,必须符合MouseButtonEventHandle
委托,也就是说,第一个参数的类型是object
,第二个参数的类型是MouseButtonEventArgs
。这个类定义在System.Windows.Input
命名空间中,所以代码中使用using
编译指令来调用此命名空间。
当用户在窗口的客户区中按下鼠标, MouseDown
事件就会发生。其事件处理器的第一个参数是“此事件的来源”,在这里就是窗口。你可以将此对象强制转换为Window
类的对象,然后加以利用。
想要得到这个 Window
对象,做法不只一种。在Main
中所建立的Window
对象可以被储存在一个静态字段中,以便此事件处理器稍后使用。另一种做法,Application
具有一个静态属性,名为Current
,此属性会存放程序所创建的Application
对象。Application
还包含一个叫MainWindow
的实例属性(即只可通过对象进行调用的属性),可以得到Window
对象。代码如下:
Window win=Application.Current.MainWindow;
Application
类定义了很多有用的事件。在.NET
中的习惯是,大多数的事件都有对应的protected
方法,可以用来响应事件。Application
所定义的Startup
事件是利用protected OnStartup
方法来响应的。一旦调用Application
对象的Run
方法,OnStartup
方法就会被立刻调用。当Run
即将返回时,会调用OnExit
方法(并触发对应的Exit
事件)。你可以利用接收到这两个事件的时机来进行整个应用程序的初始化和清理工作。
OnSessionEnding
方法和SessionEndin
事件表示用户已经选择要注销Windows
操作系统,或者要关闭电脑。此事件附带一个SessionEndingCancelEventArgs
类型的参数,你可以将它的Cancel
属性设置为true
,就可以防止Windows
操作系统被关闭。
如果你的程序需要 Appliction
类的某些事件,你可以为这些事件委托事件处理器专门处理,另一个更方便的做法,就是定义一个继承Application
,例如下一个范例InheritTheApp
正是如此。
//*********************************************************
// IneritTheApp.cs 2010 15th July by mouyong
//*********************************************************
using System;
using System.Windows;
using System.Windows.Input;
namespace part1.ch01
{
class IneritTheApp : Application
{
[STAThread]
public static void Main()
{
IneritTheApp app = new IneritTheApp();
app.Run();
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window win = new Window();
win.Title = "继承App";
win.Show();
}
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
{
base.OnSessionEnding(e);
MessageBoxResult result = MessageBox.Show("你是否需要保存数据?",MainWindow.Title,MessageBoxButton.YesNoCancel,MessageBoxImage.Question,MessageBoxResult.Yes);
e.Cancel=(result==MessageBoxResult.Cancel);
}
}
}
(
默然说话:卡住。。。。上面的代码,OnStartup方法能正常运行,可是OnSessionEnding方法却是怎么也不会被调用,我试过了直接事件委托,也不行。。。。是不是因为我使用的.Net Framework是3.5版本,而Application作了什么巨大的变更呀?期望高手解惑。。。)
InheritTheApp
类继承自Application
,且重写(override
)两个Aplication
类定义的方法:OnStartup
和OnSessionEnding
。在此程序中,Main
方法并没有创建Application
类的对象,而是创建一个我们自己写的类的对象。
Onstartup
方法在Run
被调用后立即执行,我们利用它创建了一个Window
对象,并把它显示出来。
OnSessionEnding
中,我们弹出一个”
是,否,取消”
消息框。请注意此消息框的标题被设定为MainWindow.Title
。因为InheritTheApp
是继承自Application
的,所以只要直接使用MainWindow
,就可以得到此Application
实例的属性。你可以在MainWindow
的前面加上this
关键字,来更清楚地表示MainWindow
是Application
对象的属性。
消息框其实只有取消键会有效果。如果用户点击了取消键,它会将 SessionEndingCancelEventArgs
对象的Cancel
标志设为true
,从而阻止被打开的窗口被关闭。
不管是 OnStartup
还是OnSessionEnding
,一开始都是先调用蕨类的方法。此调用并非绝对必要,但如果没有什么特别的理由,还是调用的好。
你可以从命令提示窗口(默然说话:就是俗称的
cmd,也叫DOS窗口,还叫控制台。它是在开始按钮下面的运行对话框里输入cmd后按回车可以看到的一个窗口,通常是黑黑的,里面有光标闪烁
)执行你的程序,这样可以指定参数,Windows
程序也是一样的。想要取得命令行参数,Main
方法要有所不同:
public static void Main(string[] args)
命令行参数会以字符串数组形式传入 Main
中。在OnStartup
方法中,也可以利用StartupEventArgs
的Args
属性来取得此字符串数组。
Application
具有MainWindow
属性,这表示一个程序可以有多个窗口。一般来说,许多窗口都只是短暂出现的对话框,其实对话框也是Window
对象,只是有些小差异(对话框的显示方式,以及和用户的交互方式)。
下面的程序将几个窗口一起放到桌面上显示:
//*********************************************************
//ThrowWindowParty.cs 2010 17th July by mouyong
//*********************************************************
using System;
using System.Windows;
using System.Windows.Input;
namespace part1.ch01
{
class ThrowWindowParty:Application
{
[STAThread]
public static void Main()
{
ThrowWindowParty app = new ThrowWindowParty();
app.Run();
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Window winMain = new Window();
winMain.Title = "主窗体";
winMain.MouseDown += new MouseButtonEventHandler(winMain_MouseDown);
winMain.Show();
for (int i = 0; i < 2; i++)
{
Window win = new Window();
win.Title = "第个"+(i+1)+"多的窗体";
win.Show();
}
}
void winMain_MouseDown(object sender, MouseButtonEventArgs e)
{
Window win = new Window();
win.Title = "模式对话框";
win.ShowDialog();
}
}
}
与 InheritTheApp
类一样,ThrowWindowParty
类继承自Application
,且在override
版本的OnStartup
方法中,创建一个Window
对象。然后再创建两个Window
对象,也将它们显示出来
你会注意的第一件事,就是 OnStartup
所创建的三个窗口,在此应用程序中具有同等的地位。你可以点击任何窗口,该窗口都会出现在最上面。你可以用任何次序关闭这些窗口,只有在最后一个窗口被关闭后,程序才会结束。如果没有标题栏上文字的区别,你甚至都分不清哪个是第一个被打开的。
不过,你仍然会发现, Application
对象的MainWindow
属性会是第一个new
出的窗口,至少一开始是这样的。(默然说话:这里在暗示,
MainWindow可以使用赋值的方式来改变主窗体,例如,你可以在for循环中加上MainWindow=win的语句,那么此时的主窗体将是标题显示为“第2个多的窗体”,而不是原来那个标题为“主窗体”的窗体了
)
Application
类也包含了一个名为Windows
的属性,它是一个WindowCollection
。它实现 ICollection
和Ienumerable
接口,顾名思义,WindowCollection
用来存储多个Window
对象。这个类包括一个Count
属性和一个下标索引(默然说话:在C#
中,似乎只要是Collection
,都具备这两个特点)。在OnStartup
调用结束时,Windows.Count
的值会是3
。
此程序有一个不符常规的地方:三个窗口都出现在任务栏上。一个程序应该只占有一个任务栏按钮,而不应该是三个。如果你想不要多占用任务栏,你可以在 for
循环里加入下面的语句:
win.ShowInTaskbar = false;//让一个窗体对象不会显示在任务栏上
这样做了之后,在 for
循环中打开的两个窗口都不会显示在任务栏上了,任务栏上只会出现winMain
。但这样又会出现另一个不符常规的地方:当你先关闭标题名为“主窗体”的窗口时,会看到任务栏上的按钮消失了,但程序仍然在运行中,两个for
循环中打开的窗口仍然显示在屏幕上。
这是由于在默认情况下,程序结束必须等到 Run
方法返回,而Run
方法只有在你关闭所有窗体之后才会返回。如果想在关闭主窗体之后就结束程序,那就得更改Application
的ShutdownMode
属性。在调用Run
方法的语句前加上以下语句:
app.ShutdownMode=ShutdownMode.OnMainWindowClose;
现在,当主窗体关闭时,程序就会结束。
ShutdownMode
还有第三个选项,就是OnExplicitShudown
。这么一来,只有当程序调用了Application
的Shutdown
方法,程序才会结束。
刚才我们通过 ShutdownMode
和MainWindow
建立了多个窗口间的一个松散的关系。我们还可以通过另一个方式来建立多个窗口之间的父子关系,这是通过Window
类的Owner
属性来实现的。默认情况下,此属性是null
,表示该窗口没有父窗口。你可以给Owner
属性赋值一个其他的Window
对象。试着在for
循环中插入如下代码
win.Owner = winMain;//声明窗体的父亲(即拥有者)为主窗体
在加入以上代码之后你会发现,即使注释掉之前插入的代码,你的程序在任务栏上也只显示一个按钮,你的主窗体关闭之后,程序也会结束。并且,由于两个在 for
循环中打开的窗体成为了winMain
的子窗体了,所以它们永远显示在主窗体的前面,最小化winMain
时,两个子窗体跟着最小化,关闭winMain
时,两个子窗体也跟着关闭。实际上,这两个子窗体变成了无模式的对话框。
对话框有两种,无模式对话框是属于较不常见的一种(默然说话:最有名的无模式对话框就是查找
/替换对话框
),常见的是模式对话框。只要你用鼠标在ThrowWindowParty
客户区点一下,就可以看见模式对话框的实际范例。winMain_MouseDown
方法会创建另一个Window
对象,并设定好其Title
属性,但是并不是调用Show
,而是调用ShowDialog
来将它显示出来。ShowDialog
和Show
不一样,它不会立即返回,不会让你切换到同一个程序的其他窗口(当然,你可以切换到别的程序窗口)。只有在你关闭此对话框之后,ShowDialog
的调用才会返回。
前面的两个程序都定义了一个 Application
的子类。其实程序更经常的是定义Window
的子类。下面的项目中我们定义了三个类(每个类一个源码文件)。要想添加一个新类,你可以在VS2008
中直接右击项目,选择“添加—
类…
”,然后在对话框中输入类名,点添加即可。
下面的项目名称为 InheritAppAndWindow
,它也是一个类的名称,它只包含Main
方法。
//*********************************************************
//InheritAppAndWindow.cs 2010 17th July by mouyong
//*********************************************************
using System;
using System.Windows;
using System.Windows.Input;
namespace part1.ch01
{
class InheritAppAndWindow
{
[STAThread]
public static void Main()
{
MyApplication app = new MyApplication();
app.Run();
}
}
}
Main
创建一个类型为MyApplication
的对象,且调用此对象的Run
方法。MyApplication
类继承自Application
,它的定义如下:
//*********************************************************
//MyApplication.cs 2010 17th July by mouyong
//*********************************************************
using System;
using System.Windows;
using System.Windows.Input;
namespace part1.ch01
{
class MyApplication:Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MyWindow win = new MyWindow();
win.Show();
}
}
}
在重写的 OnStartup
方法中,此类创建了一个类型为MyWindow
的对象,这是该项目的第三个类,它继承自Window
:
//*********************************************************
//MyWindow.cs 2010 17th July by mouyong
//*********************************************************
using System;
using System.Windows;
using System.Windows.Input;
namespace part1.ch01
{
class MyWindow:Window
{
public MyWindow()
{
this.Title = "继承App与窗口";
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
string strMessage = string.Format("鼠标在位置{0}使用{1}键进行了点击",e.GetPosition(this),e.ChangedButton);
MessageBox.Show(strMessage,this.Title);
}
}
}
继承自 Window
的类,通常在构造函数中初始化自身,这里我们仅仅初始化了Title
属性。由于MyWindow
继承自Window
,所以可以使用this
关键字,当然,也可以不加。
我们并没有委托 MouseDown
事件,而是采用了和前面在Application
类中的做法:重写了OnMouseDown
方法。由于这个方法是一个实例方法,所以我们仍然可以使用this
关键字(默然说话:注意,
static方法中是不能使用this关键字的
)