WPF:构建应用程序

时间:2021-04-08 20:43:57

  WPF相关的项目内容包含在App.Xaml和Window1.xaml中,这些文件中包含了相当重要的Application对象和Window对象。

Window类

  Window是传统应用程序的主要元素,用来包含应用程序的内容。WPF Window其实只是一个包装过的Win32窗口。操作系统无法区分具有WPF内容的窗口和具有Win32内容的窗口间的差别,它会以相同的方式在非客户端区域进行渲染。

  Window提供了一种对Win32窗口的直接抽象,其中提供了许多简单的方法和属性。在完成Window初始化后,可以调用Show来显示它,调用Hide让它隐藏,调用Close来完全的关闭它。

  Window通过初始化一个继承自Window类的对象,再调用Show方法来创建任意数量的窗口。它也可以把这些Window指派为子窗口。子窗口和其他上级窗口一样,但它会随着父窗口的关闭而关闭,随着上级窗口的最小化而最小化,这样的Window叫作非模态对话框。

  把其他窗口变为自己的子窗口的Window,必须把它的子窗口的Owner属性设置为它的引用,但这必须在父窗口显示以后方可。它可通过OwnedWindow属性来枚举它的子窗口。

  当一个Window变为活动(active)或非活动(inactive)状态,相应的Activated及Deactived事件就会被触发。可通过调用Window的Activate方法来强行使一个Window变为活动状态。

  关闭窗口时会触发Closing事件。

Application类

  WPF应用程序里的线程必须跑在单线程单元里(STA)。许多API必须从当前线程调用,如果是从其他线程调用,WPF会抛出一个异常。

  为了避免使用Show方法后,窗口立即退出,需要让应用程序不停的从操作系统分配消息给窗口,直到它关闭为止。这些消息和Win32应用程序所基于的消息是一样的:WM_PAINT、WM_MOUSEMOVE等等。WPF内部必须处理这些信息才能在Windows上跑起来。在Win32里,你要写一个消息循环来处理进入的消息,并且把它们传给相应的窗口过程。在WPF里,最容易实现这个任务的办法就是使用System.Windows.Application类。

使用Application.Run

  Application定义了一个叫作Run的方法,用来保持应用程序一直运行,同时分配适当的消息。

[STAThread]
public static void Main()
{
Applicateion app = new application();
MainWindow window = new MainWindow();
window.Show();
app.Run(window);
}

  Application定义了StartUri属性,它提供了另一种显示应用程序的第一个窗口的方法,可这样使用:

[STAThread]
public static void Main()
{
Applicateion app = new Applicateion();
app.StartupUri = new Uri("MainWindow.xaml", UriKind.Relative);
app.Run();
}

WPF应用程序中的Main方法在哪里?

  在VS中创建一个WPF Window应用程序时,生成的工程中没有Main方法,但它还是可以运行。事实上,如果尝试添加Main方法,将会得到一个编译错误。

  从XAML编译应用程序是一个特殊的案例,因为VS赋予了XAML文件一个ApplicateionDefinition的Build Action,这会自动生成Main方法。对于Photo Gallery应用程序来说,这个入口点方法可在App.g.cs里找到:

[System.ATAThreadAttribute()]
public static void Main()
{
PhotoGallery.App app = new PhotoGallery.App();
app.InitializeComponent();
app.Run();
}

  VS隐藏了这个App.g.cs文件。

如何在WPF应用程序中获得命令行参数?

  通常情况下,WPF应用程序不允许实现Main方法,有两种方法可获得命令行参数:一种是预告定义一个继承自Application类的XAML,这样就可以手动定义一个带有字符串数组参数的Main方法。另一种方法是在程序的任何地方调用System.Environment.GetCommandLineArgs,它返回的字符串数组与Main方法中获得的字符串数组相同。

Applicateion类的其他用途

  Application类不只是一个简单的程序入口点及消息分发器。它包含一些用来管理常规应用程序级别的任务的事件、属性和方法。你可以重载继承自Application类(比如VS生成的App类)的OnEventName方法来处理这些事件,它们包含Startup和Exit、Activated和Deactivated,甚至还有SessionEnding事件,它是在用户注销或关机时触发的事件,并且可以被取消,你可以通过ReasonSessionEnding枚举值来了解详细信息。

  因为应用程序通常会有多个窗口,Application定义了一个只读的Windows集合,供你访问所有开关的Window。初始窗口可以通过MainWindow属性来访问,它是个可读写属性,你也可以在任何时候指定任何一个窗口为主窗口。

  默认情况下,所有Window被关闭时,Application就退出了(Run方法在这个时候才最终返回)。但这个行为是可修改的,可设置ShutdownMode属性为不同的值一实现。如:可以让Application在主窗口退出时退出,而不管其他Window当时的状态。即使所有的Window都已关闭,你同样可以让Application一直运行,直到显式的调用Shutdown方法才退出。这个行为对于需要在最小化时,把自己放到Windows通知区域的应用程序非常有用。

  Propertied集合是Application类中一个非常方便的属性。Properties很像应用程序状态或者ASP.NET里的会话状态(session state),它是一个用来存储数据(key/value)的字典,这些数据可以在Window和其他对象之间共享。你只需把数据存储在Properties集合中就可以了。

  应用程序级别的任务通常在Window的代码中执行,它需要应用程序中不同的Window来获得对于当前应用程序实例的引用。我们可以通过Application.-Current静态属性来访问这个实现。如:

  Application.Current.Properties["CurrentPhotoFilename"] = filename;

如何用WPF创建单实例应用程序

  传统的创建单实例应用程序的方法仍然适用于WPF程序,如使用一个已命名的互斥量(mutex)。代码如下:

bool mutexIsNew;
using (System.Threading.Mutex m = new System.Threading.Mutex(true, uniqueName, out mutexIsNew))
{
if(mutexIsNew)
//This is the first instance. Run the application
else
//There is already an instance running. Exit!
}

  请确认uniqueName不会被其他应用程序使用!通常,在开发时我们会创建一个GUID来作为标识。

不用Application对象也可以创建应用程序

  不使用Application对象来呈现应用程序是相当容易的,但你需要手动处理消息分发。

  可以使用Win32技术,但WPF同样也在System.Windows.Threading命名空间定义了一个底层的Dispatcher类,它不用救助于Win32 API就能分发消息。如,你的Main方法可以在呈现主窗口后调用Dispatcher.Run来代替Application.Run(事实上,Application.Run内部就是用Dispatcher.Run来实现消息分发功能的)。但这样的应用程序仍然缺少其他重要的应用程序管理功能。如,Dispatcher.Run从不返回,除非你在某处显式的调用Dispatcher.ExitAllFrames。

多线程应用程序

  标准WPF应用程序包含一个UI线程及一个渲染线程(渲染线程不直接提供给开发者,它在后台运行,并且处理像合成这样的底层任务)。可以产生单独的线程来执行后台工作,但不能从这些线程中直接与UI线程里任何继承自DispatcherObject的对象进行通信。WPF提供了简单的方法,以便于任何线程可以调度UI线程来运行它们的代码。DispatcherObject定义了Dispatcher属性(Dispatcher类型),它包含了一些Invoke(同步调用)及BeginInvoke(异步调用)的重载。这些方法会让你传入一个委托,并在dispatcher相应的UI线程上被调用。所有Invoke与BeginInvoke重载都需要一个DispatcherPriority枚举值。Dispatcher-Priority定义了10种活动优先级。

  你基至可以在任何产生的线程中通过调用Dispatcher.Run,来为你的应用程序提供多个UI线程。如果你的应用程序有多个顶层Window的话,可以使每个Window运行在一个独立的线程中。

创建并显示对话框

  Windows提供了一些常规对话框(模态子窗口)。

常规对话框

  WPF为常规对话框提供了一些内建的类,这些类通过一些属性和方法提供常规对话框的功能。注意,WPF本身不渲染这些对话框,它是通过内部调用Win32 API来呈现对话框并与它们通信的。

  使用内建对话框的步骤大约为:初始化对话框,调用它的ShowDialog方法,然后处理它的结果。

自定义对话框

  在WPF中,创建使用对话框与创建使用窗口基本相同。事实上,对话框就是窗口,但通常要额外处理返回的对话框结果。

  调用ShowDialog方法显示模态窗口,调用Show方法则显示非模态窗口。ShowDialog是阻塞式调用,并且它返回一个可以为null的Boolean(C#中的bool?类型)。

ShowDialog的另一种用法

  要在允许消息分发的同时获得对话框的阻塞行为,Window的ShowDialog方法会像Application.Run那样调用Dispatcher.Run。所以以下方法可以在不使用Application类的情况下启动WPF Window:

[STAThread]
public static void Main()
{
MainWindow window = new MainWindow();
window.ShowDialog();
}

维持并恢复应用程序状态

  一个标准的Windows应用程序可以拥有对当前计算机的所有访问权限,所以有许多不同的存储数据的方式,如果使用注册表或本地文件系统。但另一种充满魅力的方法是使用.NET Framework的分离存储技术。除了使用简单以外,它适用于所有能运行托管代码的环境。