WPF程序设计读书笔记(1-1)

来源:互联网 发布:东华软件股份公司股票 编辑:程序博客网 时间:2024/04/27 20:37

 

第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]属性,否则会出现运行时错误。这个属性是用来申明该应用程序的初始线程模型为单线程,以便和COMComponent 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类定义的方法:OnStartupOnSessionEnding。在此程序中,Main方法并没有创建Application类的对象,而是创建一个我们自己写的类的对象。
Onstartup方法在Run被调用后立即执行,我们利用它创建了一个Window对象,并把它显示出来。
OnSessionEnding中,我们弹出一个是,否,取消消息框。请注意此消息框的标题被设定为MainWindow.Title。因为InheritTheApp是继承自Application的,所以只要直接使用MainWindow,就可以得到此Application实例的属性。你可以在MainWindow的前面加上this关键字,来更清楚地表示MainWindowApplication对象的属性。
消息框其实只有取消键会有效果。如果用户点击了取消键,它会将SessionEndingCancelEventArgs对象的Cancel标志设为true,从而阻止被打开的窗口被关闭。
不管是OnStartup还是OnSessionEnding,一开始都是先调用蕨类的方法。此调用并非绝对必要,但如果没有什么特别的理由,还是调用的好。
你可以从命令提示窗口(默然说话:就是俗称的cmd,也叫DOS窗口,还叫控制台。它是在开始按钮下面的运行对话框里输入cmd后按回车可以看到的一个窗口,通常是黑黑的,里面有光标闪烁)执行你的程序,这样可以指定参数,Windows程序也是一样的。想要取得命令行参数,Main方法要有所不同:
public static void Main(string[] args)
命令行参数会以字符串数组形式传入Main中。在OnStartup方法中,也可以利用StartupEventArgsArgs属性来取得此字符串数组。
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。它实现 ICollectionIenumerable接口,顾名思义,WindowCollection用来存储多个Window对象。这个类包括一个Count属性和一个下标索引(默然说话:在C#中,似乎只要是Collection,都具备这两个特点)。在OnStartup调用结束时,Windows.Count的值会是3
此程序有一个不符常规的地方:三个窗口都出现在任务栏上。一个程序应该只占有一个任务栏按钮,而不应该是三个。如果你想不要多占用任务栏,你可以在for循环里加入下面的语句:
win.ShowInTaskbar = false;//让一个窗体对象不会显示在任务栏上
这样做了之后,在for循环中打开的两个窗口都不会显示在任务栏上了,任务栏上只会出现winMain。但这样又会出现另一个不符常规的地方:当你先关闭标题名为“主窗体”的窗口时,会看到任务栏上的按钮消失了,但程序仍然在运行中,两个for循环中打开的窗口仍然显示在屏幕上。
这是由于在默认情况下,程序结束必须等到Run方法返回,而Run方法只有在你关闭所有窗体之后才会返回。如果想在关闭主窗体之后就结束程序,那就得更改ApplicationShutdownMode属性。在调用Run方法的语句前加上以下语句:
app.ShutdownMode=ShutdownMode.OnMainWindowClose;
现在,当主窗体关闭时,程序就会结束。
ShutdownMode还有第三个选项,就是OnExplicitShudown。这么一来,只有当程序调用了ApplicationShutdown方法,程序才会结束。
刚才我们通过ShutdownModeMainWindow建立了多个窗口间的一个松散的关系。我们还可以通过另一个方式来建立多个窗口之间的父子关系,这是通过Window类的Owner属性来实现的。默认情况下,此属性是null,表示该窗口没有父窗口。你可以给Owner属性赋值一个其他的Window对象。试着在for循环中插入如下代码
win.Owner = winMain;//声明窗体的父亲(即拥有者)为主窗体
在加入以上代码之后你会发现,即使注释掉之前插入的代码,你的程序在任务栏上也只显示一个按钮,你的主窗体关闭之后,程序也会结束。并且,由于两个在for循环中打开的窗体成为了winMain的子窗体了,所以它们永远显示在主窗体的前面,最小化winMain时,两个子窗体跟着最小化,关闭winMain时,两个子窗体也跟着关闭。实际上,这两个子窗体变成了无模式的对话框。
对话框有两种,无模式对话框是属于较不常见的一种(默然说话:最有名的无模式对话框就是查找/替换对话框),常见的是模式对话框。只要你用鼠标在ThrowWindowParty客户区点一下,就可以看见模式对话框的实际范例。winMain_MouseDown方法会创建另一个Window对象,并设定好其Title属性,但是并不是调用Show,而是调用ShowDialog来将它显示出来。ShowDialogShow不一样,它不会立即返回,不会让你切换到同一个程序的其他窗口(当然,你可以切换到别的程序窗口)。只有在你关闭此对话框之后,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关键字的