代码改变世界

Expert C# 2008 Business Objects 第19章 WPF用户界面 NO.4

2009-07-24 13:05  sapajou  阅读(861)  评论(0编辑  收藏  举报

2.8 ResourcesList窗体

构建显示数据的窗体-很可能是允许用户从一个列表中选择一项-是很普遍的。通过使用数据绑定,构建这样的窗体非常容易。ResourceList窗体显示了来自数据库的资源列表,允许用户从中选择一项进行编辑。像大部分WPF窗体一样,大部分的工作都在XAML中完成。

窗体定义了一个数据提供者资源。

<csla:CslaDataProvider x:Key="ResourceList"

ObjectType="{x:Type PTracker:ResourceList}"

FactoryMethod="GetResourceList"

IsAsynchronous="True"/>

这与RolesEdit中使用的那个不同,因为IsAsynchronous被设为True了,并且IsInitialLoadEnabled没有被设置(默认值是True)。结果就是窗体一被加载数据获取就开始了,但是数据获取发生在一个后台线程中。对用户来说,窗体几乎是立即显示了,而数据是在获取完成时才显示。

窗体的内容中,一个BusyAnimation控件用来给用户一个可视化的提示,告诉用户某个后台任务正在执行。

<csla:BusyAnimation Height="20" Width="20" Margin="5"

IsRunning="{Binding Source={StaticResource ResourceList},

Path=IsBusy, BindsDirectlyToSource=True}" />

IsRunning属性绑定到CslaDataProvider控件的IsBusy属性,所以像图19-7显示的那样,动画基于数据提供者是否有一个活动的查询来开启或关闭。

clip_image002

图19-7 数据正在加载时忙碌动画运行

你可以在Resources:标签文本的右侧看到动画的一部分。

窗体的内容被显示在一个ListBox控件中,该控件有一个简单的DataTemplate来显示每一行。ListBox控件的MouseDoubleClick事件被路由到一个事件处理器,该处理器是窗体后台代码中的一个方法。

<ListBox Name="listBox1"

ItemsSource="{Binding}"

MouseDoubleClick="ShowResource"/>

这里是ShowResource()方法的代码:

void ShowResource(object sender, EventArgs e)

{

ResourceInfo item =

(ResourceInfo)this.listBox1.SelectedItem;

if (item != null)

{

ResourceEdit frm = new ResourceEdit(item.Id);

MainForm.ShowControl(frm);

}

}

这是窗体后台仅有的代码。代码从ListBox控件中获取选中项(如果有选中项),将选中项的Id属性传递给一个ResourceEdit窗体的新实例。然后使用MainForm中的ShowControl()方法将用户的显示修改为新的窗体,允许用户查看或者编辑选中项。

2.9 ProjectList窗体

ProjectList窗体几乎与ResourceList窗体一样,仅有一个微小的改变。ProjectList窗体允许用户输入一些筛选条件到一个TextBox控件中。这个值被用作ProjectList业务对象的GetProjectList()工厂方法的参数。

这值讨论一下,因为我正在使用数据绑定连接TextBox控件和CslaDataProvider控件的参数值。换句话说,不需要窗体的后台代码来接收用户的输入并用作数据提供者的参数。

这里是数据提供者的声明,参数被高亮显示了:

<csla:CslaDataProvider x:Key="ProjectList"

ObjectType="{x:Type PTracker:ProjectList}"

FactoryMethod="GetProjectList"

IsAsynchronous="True"

IsInitialLoadEnabled="False">

<csla:CslaDataProvider.FactoryParameters>

<system:String>&lt;enter name&gt;</system:String>

</csla:CslaDataProvider.FactoryParameters>

</csla:CslaDataProvider>

图19-8显示了在Visual Studion设计器中的窗体。

clip_image004

图19-8 设计器中的ProjectList窗体

IsInitialLoadEnabled属性是False,确保数据提供者不会试图使用<enter name>作为筛选参数从数据库中加载数据。这也意味着用户必须输入一些筛选值来获取最终结果。

有趣的部分是TextBox控件的声明,因为是TextBox控件将它的输入值绑定到了数据提供者的参数。

<TextBox Name="NameTextBox" AutoWordSelection="True">

<TextBox.Text>

<Binding Source="{StaticResource ProjectList}"

Path="FactoryParameters[0]"

BindsDirectlyToSource="true"

UpdateSourceTrigger="PropertyChanged">

</Binding>

</TextBox.Text>

我将Binding元素高亮显示,因为在这个例子中它是与众不同的。Text属性有一个到ProjectList资源(数据提供者)的绑定,Path被设置为FactoryParameters[0],这是参数列表中的第一个参数。

对任何与数据提供者控件交互的绑定表达式,BindsDirectlyToSource属性都为True。UpdateSourceTrigger属性被设置为PropertyChanged,意味着用户不需要离开文本域就可以让更新发生。图19-9显示了筛选查询的结果。

clip_image006

图19-9 ProjectList窗体显示筛选结果

当用户双击列表的一项时,将被导航到ProjectEdit窗体,这使用与ResourceList窗体一样的代码。

2.10 ProjectEdit窗体

本章我要讨论的最后一个窗体是ProjectEdit窗体,这个窗体允许用户编辑一个项目的详细信息和分配给项目的资源列表。这个窗体为讨论明细编辑窗体和主从关系提供了机会。

图19-10显示了当用户编辑一个项目时窗体的样子。

这个窗体的构成与你在本章中看过的窗体非常相似,它使用数据提供者控件和数据绑定来启用数据的查看和编辑,使用ObjectStatus和PropertyStatus 控件来为用户启用可视化提示,使用命令来实现按钮。

clip_image008
图19-10 ProjectEdit窗体

2.10.1数据提供者控件和数据获取

这个窗体使用了两个数据提供者控件:一个用来填充资源列表中的Combox控件,一个用来获取Project业务对象。

<csla:CslaDataProvider x:Key="RoleList"

ObjectType="{x:Type PTracker:RoleList}"

FactoryMethod="GetList"

IsAsynchronous="True" />

<csla:CslaDataProvider x:Key="Project"

ObjectType="{x:Type PTracker:Project}"

FactoryMethod="GetProject"

IsAsynchronous="False"

IsInitialLoadEnabled="False"

DataChanged="DataChanged"

ManageObjectLifetime="True"/>

Project数据提供者被使用的方式与你迄今为止看到的是不同的。在这个例子中,业务对象的Id值在窗体创建时被传递到窗体,这个值被存储在一个字段中,然后在窗体的Loaded事件处理器中被用来获取对象。这里是代码,相关部分被高亮显示:

private Guid _projectId;

private Csla.Wpf.CslaDataProvider _dp;

public ProjectEdit()

{

InitializeComponent();

this.Loaded += new RoutedEventHandler(ProjectEdit_Loaded);

_dp = this.FindResource("Project") as Csla.Wpf.CslaDataProvider;

}

public ProjectEdit(Guid id)

: this()

{

_projectId = id;

}

void ProjectEdit_Loaded(object sender, RoutedEventArgs e)

{

using (_dp.DeferRefresh())

{

_dp.FactoryParameters.Clear();

if (_projectId.Equals(Guid.Empty))

{

_dp.FactoryMethod = "NewProject";

}

else

{

_dp.FactoryMethod = "GetProject";

_dp.FactoryParameters.Add(_projectId);

}

}

if (_dp.Data != null)

SetTitle((Project)_dp.Data);

else

MainForm.ShowControl(null);

}

构造器将id参数放在一个字段中,这个字段随后在Loaded事件处理器中被用作数据提供者的参数。

所有与数据提供者控件进行交互的代码都被包装在一个使用DeferRefresh()方法的using语句块中的事实是很重要的。默认情况下,每次数据提供者的属性发生变动,数据提供者立即从数据库中刷新数据。如果你需要像本例中那样立刻设置数个属性,你仅仅想让控件与数据库交互一次。

使用DeferRefresh()方法的using语句块允许你设置控件的多个属性,仅仅在using语句块的结尾控件才会尝试去查询数据库。

2.10.2设置数据绑定

与你看到过的其他窗体一样,这个窗体也通过设置一个对所有窗体内容有效的DataContext值来使用数据绑定。这在包含所有其他内容的顶层Grid控件中实现。

<Grid Name="MainGrid"

DataContext="{Binding Source={StaticResource Project}}"

Margin="0 0 20 0">

不过,这是一个主从窗体。明细控件被绑定到DataContext(Project对象)上,资源列表中的项被绑定到Project对象的ProjectResources集合上。

换句话说,明细控件是像这样来绑定的:

<TextBox Name="NameTextBox" Grid.Column="0"

Text="{Binding Name,

Converter={StaticResource IdentityConverter}}" />

这种绑定也被用在本章的其他窗体上。不过,显示ProjectResources集合的ListBox控件是这样绑定的:

<ListBox ItemsSource="{Binding Resources}"

ItemTemplate="{Binding Source={StaticResource ProjectStatus},

Path=CanEditObject,

Converter={StaticResource ListTemplateConverter}}"/>

在Project类中,ProjectResources集合是通过Resources属性暴露出来的。既然Project对象有一个Resources属性,你就可以使用它作为一个绑定表达式的目标,让ListBox控件绑定到这个属性上。

ListBox控件中的每一行数据都是ProjectResources集合中的一项:一个ProjectResource对象,窗体中定义了一个DataTemplate来为只读的情况显示数据,另一个模板用在读写的场景。这与本章前面的窗体没什么不同。

DataTemplate中唯一有趣的事情是ComboBox控件的使用。记得这个窗体里有两个数据提供者控件吧,其中一个返回RoleList业务对象中的角色列表。这被用来填充ComboBox控件。

<ComboBox

ItemsSource="{Binding Source={StaticResource RoleList}}"

DisplayMemberPath="Value"

SelectedValuePath="Key"

SelectedValue="{Binding Path=Role}"

Width="150" />

与在Windows Forms和Web Forms中的一样,ComboBox控件实际上可以同时绑定到两个数据源。一个数据源通过设置ItemsSource、DisplayMemberPath和SelectedValuePath属性被用来填充列表中的项。控件通过设置SelectedValue属性也被绑定到当前DataContext对象的一个单一属性上-在本例中,被绑定到当前ProjectResource业务对象的Role属性上。

2.10.3通过命令导航

资源列表中的每一项都能被点击,允许用户直接导航到那个资源的ResourceEdit窗体。你可以像我在ResourceList和ProjectList窗体中做的那样使用简单的事件处理来完成导航,然而在本例中,我使用命令来演示你可以选择的另一种不同的技术。

业务对象的FullName属性被绑定到一个Button控件,该控件以超链接的样式来展现使得用户知道可以点击它。当用户点击控件时,它发送一个Open命令并以ResourceId作为参数。

<Button Style="{StaticResource LinkButton}"

Margin="0" Width="200"

Command="ApplicationCommands.Open"

CommandParameter="{Binding Path=ResourceId}"

Content="{Binding Path=FullName}" Foreground="Blue" />

ProjectEdit窗体自身处理这个命令,因为窗体已经为这个命令定义了一个CommandBinding。

<UserControl.CommandBindings>

<CommandBinding Command="ApplicationCommands.Open"

Executed="OpenCmdExecuted"

CanExecute="OpenCmdCanExecute" />

</UserControl.CommandBindings>

CommandBinding允许你通过XAML来表示一个命令将被路由到一个指定的方法上。你还需要提供一个CanExecute()方法,该方法通过它的CanExecuteRoutedEventArgs参数返回一个Boolean值,表明命令在任何时候是否能被执行。如果返回false,相关的Button控件自动被禁用。

在窗体的后台代码中,实现了两个方法。

private void OpenCmdExecuted(object sender, ExecutedRoutedEventArgs e)

{

if (e.Parameter != null)

{

ResourceEdit frm = new ResourceEdit(Convert.ToInt32(e.Parameter));

MainForm.ShowControl(frm);

}

}

private void OpenCmdCanExecute(

object sender, CanExecuteRoutedEventArgs e)

{ e.CanExecute = true; }

OpenCmdExecuted()方法中CommandParameter的值可以通过e.Parameter得到。在XAML中,ResourceId值作为参数传递,因此方法中可以获取该值。该值被用来创建一个ResourceEdit窗体的新实例,通过调用ShowControl()方法该窗体随后被显示给用户。

这种方式与直接处理Click事件比并不节省代码,但它提供了更好的灵活性。有关WPF命令的完整讨论超出了来书的范围,但你应该知道命令可以被路由到不同的位置,要比紧耦合的事件处理更具灵活性。

组合使用数据绑定和Csla.Wpf命名空间中控件,你就可以在XAML中实现构建一个数据显示或是数据实体窗体所需要的几乎所有常见行为。在许多情况下,窗体后台只需很少的代码,这改善了应用程序的可维护性。

3 小结

本章贯穿了使用来自第17章和第18章的业务对象创建一个基本的WPF用户界面的全过程。很明显,有许多种方式使用WPF创建用户界面,因此本章的目标是强调你如何使用数据绑定不费力地创建窗体来查看和编辑对象数据。

本章的关键是当你使用业务对象创建你的业务层的时候,用户界面开发人员不需要关心验证或授权规则、数据访问或是大多数其他复杂的问题。用户界面开发人员可以集中精力在用户交互、应用程序观感等等方面。结果就是在UI层和业务层之间的高度分离。

同时,因为对象使用数据门户机制来获取或者更新数据,应用程序能利用移动对象的能力:酌情在客户端工作部和应用服务器上运行业务逻辑。更好的是,你能简单的修改应用程序配置文件来在各种不同的物理n层配置间进行切换以满足不同的性能、可扩展性、可靠性和安全方面的需求。

第20章将涵盖基于相同业务对象集合的Web Forms用户界面的实现。尽管在WPF和Web Forms环境间有着明显的不同,当从一种界面类型转移到另一种界面类型时我们仍将获得业务逻辑和数据访问代码的完全利用。