构建WPF用户界面:从基础到实践
在当今的应用程序开发中,用户界面(UI)的设计和实现至关重要。它不仅影响着用户体验,还与业务逻辑和数据访问紧密相连。本文将深入探讨如何使用Windows Presentation Foundation(WPF)构建强大而灵活的UI,借助CSLA .NET框架,实现业务层与UI层的高效分离。
1. WPF简介
WPF是微软在.NET 3.0中引入的下一代Windows应用程序UI技术。它巧妙地融合了Windows和Web开发的诸多优点,既具备Windows Forms丰富的交互性和事件驱动特性,又采用了基于XML的可样式化标记语言来描述界面,与Web开发类似。不过,WPF与Windows Forms和Web Forms虽有相似之处,但本质上又有所不同,这就要求开发者克服一定的学习曲线。
在数据绑定方面,WPF提供了强大的支持,类似于Windows Forms使用事件驱动模型实现UI控件与业务对象的交互。同时,它也采用了数据控制模型(数据提供程序控件)来加载数据到表单中,就像Web Forms一样。然而,WPF的ObjectDataProvider控件在处理CSLA .NET风格的业务对象时存在局限性,为此,CSLA .NET专门为WPF提供了CslaDataProvider控件。
2. 自定义身份验证问题
在WPF中,自定义身份验证是一个需要特别关注的问题。由于WPF不是线程安全的,所有UI工作必须在单个UI线程上完成,但它在幕后会使用后台线程。而.NET的安全基础设施依赖于与特定线程关联的当前主体对象,这就可能导致代码运行在没有正确主体的线程上。
这个问题仅在使用自定义身份验证时出现。如果使用Windows身份验证,所有线程都会使用登录到工作站的用户的WindowsPrincipal,不会有问题。但在自定义身份验证场景下,可能会出现一个或多个线程最终使用了错误的主体的情况。这会给CSLA .NET的授权和数据门户子系统带来困扰,因为它们依赖于.NET主体对象来完成工作。
微软推荐的解决方案是在AppDomain上设置默认主体值:
AppDomain.CurrentDomain.SetThreadPrincipal(principal)
但这个方法存在严重的局限性。一旦执行这行代码,从那之后创建的所有线程都会使用新的默认主体。这意味着必须在创建任何后台线程(包括线程池中的线程)之前执行这行代码,而且登录过程不能使用任何异步行为。此外,这行代码在应用程序的生命周期内只能调用一次。
CSLA .NET的ApplicationContext对象提供了一个部分解决方案,它能解决所有CSLA .NET代码以及使用ApplicationContext.User访问当前主体的代码的问题。但这只是部分解决方案,因为CSLA .NET无法确保每个线程上的实际CurrentPrincipal始终正确,所以一些.NET功能(如代码访问安全,CAS)可能并非始终正常工作。
3. 界面设计
在ProjectTracker解决方案中,有一个名为PTWpf的项目,它的界面由一个主窗体构成,左侧是导航链接,右侧是内容区域。主窗体通过动态加载用户控件到内容区域,向用户展示不同的功能。
以下是构成界面的主要表单和控件:
| 表单/控件 | 类型 | 描述 |
| — | — | — |
| MainForm | Window | 应用程序的主窗体 |
| EditForm | UserControl | 用于创建用户控件的自定义基类 |
| ListTemplateConverter | IValueConverter | 将布尔值转换为DataTemplate值 |
| Login | Window | 用于收集用户凭据的登录对话框 |
| RolesEdit | EditForm | 允许用户编辑角色列表 |
| ProjectSelect | Window | 提示用户从项目列表中选择项目的对话框 |
| ProjectList | EditForm | 显示项目列表 |
| ProjectEdit | EditForm | 允许用户查看、添加或编辑项目 |
| ResourceSelect | Window | 提示用户从资源列表中选择资源的对话框 |
| ResourceList | EditForm | 显示资源列表 |
| ResourceEdit | EditForm | 允许用户查看、添加或编辑资源 |
| VisibilityConverter | IValueConverter | 将布尔值转换为Visibility值 |
这种基于用户控件的设计方法提供了极大的灵活性。用户控件可以在MDI界面的子窗体中托管,也可以在多窗格界面的窗格中托管,适用于多种不同类型的UI设计。
4. 用户控件框架
动态加载用户控件的过程并不复杂,主要遵循以下步骤:
1. 创建EditForm控件。
2. 从视图中移除任何现有的EditForm控件。
3. 将新的EditForm添加到Children集合中。
4. 为新的EditForm挂钩TitleChanged事件。
为了实现标准化的用户体验,创建了EditForm类,它是UserControl的子类,为MainForm托管的每个控件提供了一些标准行为。
4.1 EditForm类的行为
| 行为 | 描述 |
|---|---|
| 主体更改 | 当当前.NET主体更改时,MainForm可以通知活动的EditForm重新应用授权规则 |
| 提供Title属性 | 通过添加Title属性扩展UserControl,该属性可以通过XAML或代码设置,并包含一个事件,以便MainForm知道Title已更改,从而更新主窗体标题 |
| 错误处理 | 提供一种标准方式,用于任何数据提供程序显示数据访问期间发生的异常 |
4.2 主体更改处理
当当前.NET主体更改时,意味着用户已登录或注销应用程序,因此需要为当前显示的EditForm重新应用授权规则。EditForm基类通过调用可重写的ApplyAuthorization()方法来通知子类。
Private Sub EditForm_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs)
ApplyAuthorization()
End Sub
Private Sub Refresh() Implements IRefresh.Refresh
ApplyAuthorization()
End Sub
Protected Overridable Sub ApplyAuthorization()
End Sub
每个子类可以重写ApplyAuthorization()方法,根据新的.NET主体重新应用授权规则。
4.3 Title属性
在应用程序中,希望每个编辑表单都能更改顶级窗口标题。在WPF中,控件的数据可绑定属性通常实现为依赖属性。以下是Title属性的实现:
Public Event TitleChanged As EventHandler
Public Shared ReadOnly TitleProperty As DependencyProperty = _
DependencyProperty.Register("Title", GetType(String), GetType(EditForm), Nothing)
Public Property Title() As String
Get
Return DirectCast(GetValue(TitleProperty), String)
End Get
Set(ByVal value As String)
SetValue(TitleProperty, value)
RaiseEvent TitleChanged(Me, EventArgs.Empty)
End Set
End Property
TitleChanged事件是一个标准事件,MainForm会处理这个事件,以便在值更改时得到通知。属性声明本身是一个依赖属性,因此可以通过XAML设置其值。
4.4 错误处理
使用WPF数据提供程序进行数据访问时,处理异常有点棘手,因为数据访问是由控件触发的,无法使用Try…Catch块来处理异常。标准技术是处理数据提供程序控件的DataChanged事件,并检查其Error属性是否不为空。
EditForm基类提供了一个事件处理程序来处理所有此类异常:
Protected Overridable Sub DataChanged(ByVal sender As Object, ByVal e As EventArgs)
Dim dp = TryCast(sender, System.Windows.Data.DataSourceProvider)
If dp.Error IsNot Nothing Then
MessageBox.Show(dp.Error.ToString(), "Data error", _
MessageBoxButton.OK, MessageBoxImage.Exclamation)
End If
End Sub
每个编辑表单负责将其数据提供程序的DataChanged事件挂钩到这个处理程序。
5. MainForm窗口
MainForm是应用程序的核心,它提供导航功能并托管用户控件。其内容区域是一个DockPanel控件,通过ShowControl()方法实现用户控件的切换。
Public Shared Sub ShowControl(ByVal control As UserControl)
_mainForm.ShowUserControl(control)
End Sub
Private Sub ShowUserControl(ByVal control As UserControl)
UnhookTitleEvent(_currentControl)
contentArea.Children.Clear()
If control IsNot Nothing Then
contentArea.Children.Add(control)
End If
_currentControl = control
HookTitleEvent(_currentControl)
End Sub
ShowControl()方法是一个公共的共享方法,方便项目中的所有代码调用。它会委托给MainForm实例的ShowUserControl()方法。ShowUserControl()方法会先取消现有控件的TitleChanged事件挂钩,清空内容区域,然后添加新的用户控件,并挂钩新控件的TitleChanged事件。
6. 值转换器
WPF允许通过XAML直接完成很多工作,减少每个窗口或用户控件背后的代码。实现值转换器控件是一种将代码转移到XAML的强大技术,它可以将一种类型的值(如布尔值)转换为另一种类型或值。
6.1 VisibilityConverter
VisibilityConverter类实现了IValueConverter接口,用于将布尔值转换为Visibility值。当将UI的一部分绑定到授权属性(如CanEditObject)时,它非常有用。
Public Class VisibilityConverter
Implements System.Windows.Data.IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, _
ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) _
As Object Implements IValueConverter.Convert
If CBool(value) Then
Return System.Windows.Visibility.Visible
Else
Return System.Windows.Visibility.Collapsed
End If
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, _
ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) _
As Object Implements IValueConverter.ConvertBack
Return False
End Function
End Class
在这个类中,只有Convert()方法被完全实现,因为它用于从ObjectStatus控件绑定到UI控件,UI控件的更改不会绑定回ObjectStatus。
6.2 ListTemplateConverter
ListTemplateConverter类稍微复杂一些,它不仅实现了IValueConverter接口,还定义了两个属性。
Public Class ListTemplateConverter
Implements System.Windows.Data.IValueConverter
Private _trueTemplate As System.Windows.DataTemplate
Public Property TrueTemplate() As System.Windows.DataTemplate
Get
Return _trueTemplate
End Get
Set(ByVal value As System.Windows.DataTemplate)
_trueTemplate = value
End Set
End Property
Private _falseTemplate As System.Windows.DataTemplate
Public Property FalseTemplate() As System.Windows.DataTemplate
Get
Return _falseTemplate
End Get
Set(ByVal value As System.Windows.DataTemplate)
_falseTemplate = value
End Set
End Property
#Region "IValueConverter Members"
Public Function Convert(ByVal value As Object, ByVal targetType As Type, _
ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) _
As Object Implements IValueConverter.Convert
If CBool(value) Then
Return TrueTemplate
Else
Return FalseTemplate
End If
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, _
ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) _
As Object Implements IValueConverter.ConvertBack
Return value
End Function
#End Region
End Class
同样,只有Convert()方法被完全实现。TrueTemplate和FalseTemplate属性用于根据输入的布尔值定义要返回的DataTemplate值。在XAML中,可以将这个转换器定义为资源:
<local:ListTemplateConverter x:Key="ListTemplateConverter"
TrueTemplate="{StaticResource editableTemplate}"
FalseTemplate="{StaticResource readonlyTemplate}" />
7. 应用程序配置
应用程序需要通过配置文件提供一些基本的配置信息。在客户端应用程序配置文件中,可以提供连接字符串,使应用程序直接与数据库交互,也可以配置数据门户与远程应用服务器通信。
7.1 身份验证配置
CSLA .NET通过配置文件控制身份验证:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="CslaAuthentication" value="Csla" />
<add key="CslaPropertyChangedMode" value="Xaml" />
</appSettings>
</configuration>
CslaAuthentication键指定使用自定义身份验证。如果要使用Windows身份验证,需要将配置更改为:
<add key="CslaAuthentication" value="Windows" />
同时,需要从ProjectTracker.Library中移除PTPrincipal和PTIdentity类,并删除UI项目中的登录/注销功能。
CslaPropertyChangedMode键使CSLA .NET使用XAML模型从所有业务对象引发PropertyChanged事件。这对于WPF应用程序非常重要,否则数据绑定将无法根据业务对象的底层更改正确更新UI。
7.2 本地数据门户配置
要使客户端应用程序直接与数据库交互,可以使用以下配置:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="CslaAuthentication" value="Csla" />
<add key="CslaPropertyChangedMode" value="Xaml" />
</appSettings>
<connectionStrings>
<add name="PTracker" connectionString="your connection string"
providerName="System.Data.SqlClient" />
<add name="Security" connectionString="your connection string"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
由于LocalProxy是数据门户的CslaDataPortalProxy设置的默认值,因此不需要实际的数据门户配置,配置文件中只需控制身份验证和提供数据库连接字符串。
7.3 远程数据门户配置
要使数据门户使用应用服务器并通过WCF通道进行通信,可以使用以下配置:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="CslaAuthentication" value="Csla" />
<add key="CslaPropertyChangedMode" value="Xaml" />
<add key="CslaDataPortalProxy"
value="Csla.DataPortalClient.WcfProxy, Csla"/>
</appSettings>
<connectionStrings>
</connectionStrings>
<system.serviceModel>
<client>
<endpoint name="WcfDataPortal"
address="http://localhost:4147/WcfHost/WcfPortal.svc"
binding="wsHttpBinding"
contract="Csla.Server.Hosts.IWcfPortal" />
</client>
</system.serviceModel>
</configuration>
需要将localhost:4147更改为安装数据门户主机的应用服务器的名称,并将WcfHost替换为该服务器上的虚拟根名称。在使用此配置之前,必须创建并配置WCF主机虚拟根。
重要的是要认识到,通过更改应用程序配置文件,可以在不更改任何UI或业务对象代码的情况下,将数据门户从本地模式切换到远程模式(使用任何网络通道)。
8. 数据门户服务器配置
当使用远程数据门户配置时,客户端需要与应用服务器通信。如果客户端数据门户配置为使用WCF,则必须提供一个配置正确的WCF应用服务器。
WcfHost网站是一个在IIS中托管的WCF应用服务器示例。任何包含以下关键元素的网站都可以作为WCF数据门户主机:
| 元素 | 描述 |
| — | — |
| WcfPortal.svc | WCF服务端点文件,引用Csla.Server.Hosts.WcfPortal类 |
| bin | 标准Web bin文件夹,包含Csla.dll、ProjectTracker.Library.dll以及业务库所需的任何其他程序集 |
| web.config | 标准web.config文件,但包含一个system.serviceModel元素,用于配置数据门户的端点 |
WCF服务由地址、绑定和契约定义。对于在IIS中托管的WCF服务,地址由IIS和svc文件的名称定义,因此在web.config文件中不需要指定。在CSLA .NET数据门户端点的情况下,契约是固定的,必须是Csla.Server.Hosts.IWcfPortal。绑定可以是任何同步的WCF绑定。
应用服务器的web.config文件必须定义数据访问代码所需的连接字符串,并且CslaAuthentication值必须与客户端相同。
9. PTWpf项目设置
在ProjectTracker解决方案中,PTWpf项目是UI应用程序,它引用了ProjectTracker.Library项目和Csla.dll。在使用CSLA .NET框架构建应用程序时,建议对框架程序集建立文件引用,而在UI和业务程序集之间使用项目引用,这样可以更方便地进行调试。
10. MainForm窗口
MainForm是应用程序的核心,它提供导航功能并托管用户控件。其导航区域由一系列Expander和Hyperlink控件组成,每个Hyperlink控件都有一个Click事件处理程序,负责实现用户所需的行为。
例如,NewProject()事件处理程序用于创建新项目:
Private Sub NewProject(ByVal sender As Object, ByVal e As EventArgs)
Try
Dim frm As New ProjectEdit(Guid.Empty)
ShowControl(frm)
Catch ex As System.Security.SecurityException
MessageBox.Show(ex.ToString())
End Try
End Sub
CloseProject()处理程序用于关闭项目:
Private Sub CloseProject(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim frm As New ProjectSelect()
Dim result As Boolean = CBool(frm.ShowDialog())
If result Then
Dim id As Guid = frm.ProjectId
ProjectCloser.CloseProject(id)
MessageBox.Show("Project closed", "Close project", _
MessageBoxButton.OK, MessageBoxImage.Information)
End If
End Sub
11. 登录和注销功能
MainForm还实现了用户登录和注销功能。用户可以在应用程序启动时或点击菜单中的登录按钮触发登录过程。LogInOut()方法处理实际的登录/注销行为:
Private Sub LogInOut(ByVal sender As Object, ByVal e As EventArgs)
If Csla.ApplicationContext.User.Identity.IsAuthenticated Then
ProjectTracker.Library.Security.PTPrincipal.Logout()
CurrentUser.Text = "Not logged in"
LoginButtonText.Text = "Log in"
Else
Dim frm As New Login()
frm.ShowDialog()
If frm.Result Then
Dim username As String = frm.UsernameTextBox.Text
Dim password As String = frm.PasswordTextBox.Password
ProjectTracker.Library.Security.PTPrincipal.Login(username, password)
End If
If Not Csla.ApplicationContext.User.Identity.IsAuthenticated Then
ProjectTracker.Library.Security.PTPrincipal.Logout()
CurrentUser.Text = "Not logged in"
LoginButtonText.Text = "Log in"
Else
CurrentUser.Text = String.Format("Logged in as {0}", _
Csla.ApplicationContext.User.Identity.Name)
LoginButtonText.Text = "Log out"
End If
End If
ApplyAuthorization()
Dim p As IRefresh = TryCast(_currentControl, IRefresh)
If p IsNot Nothing Then
p.Refresh()
End If
End Sub
在用户登录或注销后,会调用ApplyAuthorization()方法更新MainForm的显示,并通知当前用户控件主体已更改。
12. 登录窗口
Login窗口用于收集和验证用户的凭据。它定义了一个Result属性,用于指示用户是否点击了登录按钮。
Private _result As Boolean
Public Property Result() As Boolean
Get
Return _result
End Get
Set(ByVal value As Boolean)
_result = value
End Set
End Property
Private Sub LoginButton(ByVal sender As Object, ByVal e As EventArgs)
_result = True
Me.Close()
End Sub
Private Sub CancelButton(ByVal sender As Object, ByVal e As EventArgs)
_result = False
Me.Close()
End Sub
13. 实际表单实现
接下来将详细讨论几个实际的表单实现,包括RolesEdit、ResourceList、ProjectList和ProjectEdit表单。
13.1 RolesEdit表单
RolesEdit用户控件允许授权用户编辑资源在分配到项目时可以担任的角色。大部分工作都在XAML中完成。
表单声明使用了EditForm作为基类,并引入了所需的命名空间:
<local:EditForm x:Class="PTWpf.RolesEdit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PTWpf"
xmlns:csla="clr-namespace:Csla.Wpf;assembly=Csla"
xmlns:ptracker="clr-namespace:ProjectTracker.Library.Admin;assembly=ProjectTracker.Library">
表单资源部分定义了一些全局资源,包括VisibilityConverter、IdentityConverter、CslaDataProvider和ObjectStatus:
<local:EditForm.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter" />
<csla:IdentityConverter x:Key="IdentityConverter" />
<csla:CslaDataProvider x:Key="RoleList"
ObjectType="{x:Type ptracker:Roles}"
FactoryMethod="GetRoles"
DataChanged="DataChanged"
ManageObjectLifetime="True">
</csla:CslaDataProvider>
<csla:ObjectStatus x:Key="RoleListStatus"
DataContext="{StaticResource RoleList}" />
</local:EditForm.Resources>
CslaDataProvider控件是关键,它不仅可以创建或检索业务对象,还可以取消对对象的编辑并保存对象,所有这些操作都可以完全通过XAML完成。
表单内容包括一个TextBlock标签、一个ListBox和一些按钮控件:
<StackPanel>
<TextBlock>Roles:</TextBlock>
<ListBox Name="RolesListBox"
ItemsSource="{Binding}"
ItemTemplate="{Binding Source={StaticResource RoleListStatus},
Path=CanEditObject,
Converter={StaticResource ListTemplateConverter}}" />
<StackPanel Orientation="Horizontal"
Visibility="{Binding Source={StaticResource RoleListStatus},
Path=CanEditObject,
Converter={StaticResource VisibilityConverter}}">
<Button
Command="ApplicationCommands.Save"
CommandTarget="{Binding Source={StaticResource RoleList},
Path=CommandManager, BindsDirectlyToSource=True}"
HorizontalAlignment="Left" IsDefault="True">Save</Button>
<Button
Command="ApplicationCommands.Undo"
CommandTarget="{Binding Source={StaticResource RoleList},
Path=CommandManager, BindsDirectlyToSource=True}"
HorizontalAlignment="Left" IsCancel="True">Cancel</Button>
<Button Name="AddItemButton"
Command="ApplicationCommands.New"
CommandTarget="{Binding Source={StaticResource RoleList},
Path=CommandManager, BindsDirectlyToSource=True}"
HorizontalAlignment="Left" IsCancel="True">Add role</Button>
</StackPanel>
</StackPanel>
ListBox的ItemTemplate根据用户的权限使用ListTemplateConverter在只读模板和读写模板之间切换。按钮控件的可见性根据用户是否有权编辑数据进行控制,并且它们使用WPF命令与CslaDataProvider控件交互。
在代码背后,需要重写ApplyAuthorization()方法:
Protected Overrides Sub ApplyAuthorization()
Dim dp = DirectCast(Me.FindResource("RoleList"), Csla.Wpf.CslaDataProvider)
dp.Rebind()
If Not Csla.Security.AuthorizationRules.CanEditObject(dp.ObjectType) Then
dp.Cancel()
End If
End Sub
如果新用户没有权限编辑业务对象,会调用Cancel()方法,使对象恢复到未更改状态。
13.2 ResourceList表单
ResourceList表单用于显示数据库中的资源列表,允许用户选择一个项目进行编辑。它定义了一个异步的数据提供程序资源:
<csla:CslaDataProvider x:Key="ResourceList"
ObjectType="{x:Type PTracker:ResourceList}"
FactoryMethod="GetResourceList"
IsAsynchronous="True"/>
在表单内容中,使用BusyAnimation控件给用户提供后台任务执行的视觉提示:
<csla:BusyAnimation Height="20" Width="20" Margin="5"
IsRunning="{Binding Source={StaticResource ResourceList},
Path=IsBusy, BindsDirectlyToSource=True}" />
ListBox的MouseDoubleClick事件处理程序用于打开ResourceEdit表单:
Private Sub ShowResource(ByVal sender As Object, ByVal e As EventArgs)
Dim item As ResourceInfo = DirectCast(Me.listBox1.SelectedItem, ResourceInfo)
If item IsNot Nothing Then
Dim frm As New ResourceEdit(item.Id)
MainForm.ShowControl(frm)
End If
End Sub
13.3 ProjectList表单
ProjectList表单与ResourceList表单类似,但允许用户在TextBox控件中输入过滤条件,该值将作为GetProjectList()工厂方法的参数。
<csla:CslaDataProvider x:Key="ProjectList"
ObjectType="{x:Type PTracker:ProjectList}"
FactoryMethod="GetProjectList"
IsAsynchronous="True"
IsInitialLoadEnabled="False">
<csla:CslaDataProvider.FactoryParameters>
<system:String><enter name></system:String>
</csla:CslaDataProvider.FactoryParameters>
</csla:CslaDataProvider>
TextBox控件的Text属性绑定到数据提供程序的参数:
<TextBox Name="NameTextBox" AutoWordSelection="True">
<TextBox.Text>
<Binding Source="{StaticResource ProjectList}"
Path="FactoryParameters[0]"
BindsDirectlyToSource="true"
UpdateSourceTrigger="PropertyChanged">
</Binding>
</TextBox.Text>
当用户双击列表中的项目时,会打开ProjectEdit表单。
13.4 ProjectEdit表单
ProjectEdit表单允许用户编辑项目的详细信息和分配给项目的资源列表。它使用两个数据提供程序控件:一个用于填充资源列表中的ComboBox控件,另一个用于获取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"/>
在表单的Loaded事件处理程序中,根据传入的Id值调用相应的工厂方法检索对象:
Private _projectId As Guid
Private _dp As Csla.Wpf.CslaDataProvider
Public Sub New()
InitializeComponent()
AddHandler Me.Loaded, AddressOf ProjectEdit_Loaded
_dp = TryCast(Me.FindResource("Project"), Csla.Wpf.CslaDataProvider)
End Sub
Public Sub New(ByVal id As Guid)
Me.New()
_projectId = id
End Sub
Private Sub ProjectEdit_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
Using _dp.DeferRefresh()
_dp.FactoryParameters.Clear()
If _projectId.Equals(Guid.Empty) Then
_dp.FactoryMethod = "NewProject"
Else
_dp.FactoryMethod = "GetProject"
_dp.FactoryParameters.Add(_projectId)
End If
End Using
If _dp.Data IsNot Nothing Then
SetTitle(DirectCast(_dp.Data, Project))
Else
MainForm.ShowControl(Nothing)
End If
End Sub
表单使用数据绑定和ObjectStatus、PropertyStatus控件为用户提供视觉提示,并使用命令实现按钮功能。在资源列表中,每个项目的FullName属性绑定到一个按钮,用户点击该按钮时会发送一个Open命令,携带ResourceId属性作为参数。
<Button Style="{StaticResource LinkButton}"
Margin="0" Width="200"
Command="ApplicationCommands.Open"
CommandParameter="{Binding Path=ResourceId}"
Content="{Binding Path=FullName}" Foreground="Blue" />
表单通过CommandBinding处理该命令:
<UserControl.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open"
Executed="OpenCmdExecuted"
CanExecute="OpenCmdCanExecute" />
</UserControl.CommandBindings>
在代码背后实现相应的方法:
Private Sub OpenCmdExecuted(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
If e.Parameter IsNot Nothing Then
Dim frm As New ResourceEdit(Convert.ToInt32(e.Parameter))
MainForm.ShowControl(frm)
End If
End Sub
Private Sub OpenCmdCanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
e.CanExecute = True
End Sub
总结
通过以上的介绍,我们可以看到使用WPF和CSLA .NET框架构建UI的强大之处。借助数据绑定和CSLA .NET提供的各种控件,我们可以实现业务层与UI层的高度分离,使UI开发者能够专注于用户交互和应用程序的外观和感觉,而无需担心验证、授权规则、数据访问等复杂问题。同时,通过数据门户机制,应用程序可以在客户端工作站和应用服务器上合理运行业务逻辑,并且可以通过简单更改应用程序配置文件来满足不同的性能、可扩展性、可靠性和安全要求。
在实际开发中,我们可以根据具体需求灵活运用这些技术,不断优化和完善应用程序的UI设计和实现。希望本文能为你在WPF UI开发方面提供有价值的参考和指导。
构建WPF用户界面:从基础到实践
14. 技术优势与应用场景分析
使用WPF和CSLA .NET构建用户界面具有显著的技术优势,并且适用于多种应用场景。
14.1 技术优势
- 高度分离的业务与UI层 :业务对象封装了验证、授权规则和数据访问逻辑,UI开发者只需关注用户交互和界面设计,提高了开发效率和代码的可维护性。
- 数据绑定的强大功能 :通过数据绑定,UI控件可以自动与业务对象同步,减少了手动编写代码的工作量,提高了代码的稳定性。
- 灵活的配置与部署 :数据门户机制允许应用程序在本地和远程模式之间轻松切换,通过更改配置文件即可满足不同的性能、可扩展性和安全需求。
14.2 应用场景
- 企业级应用 :对于需要处理大量数据和复杂业务逻辑的企业级应用,WPF和CSLA .NET可以提供高效、稳定的用户界面解决方案。
- 桌面应用 :在桌面应用开发中,WPF的丰富交互性和美观的界面设计能力可以提升用户体验,而CSLA .NET的业务逻辑处理能力可以确保应用程序的可靠性。
- 移动应用的桌面端补充 :对于移动应用,WPF和CSLA .NET可以用于开发桌面端的管理工具,实现数据的集中管理和处理。
15. 性能优化建议
为了提高应用程序的性能,在使用WPF和CSLA .NET时可以采取以下优化建议:
15.1 数据加载优化
- 异步加载 :对于大量数据的加载,使用异步加载方式可以避免阻塞UI线程,提高用户体验。例如,在ResourceList表单中,将IsAsynchronous属性设置为True,使数据在后台线程中加载。
<csla:CslaDataProvider x:Key="ResourceList"
ObjectType="{x:Type PTracker:ResourceList}"
FactoryMethod="GetResourceList"
IsAsynchronous="True"/>
- 分页加载 :对于数据量较大的列表,可以采用分页加载的方式,只加载当前页面的数据,减少内存占用。
15.2 UI渲染优化
- 减少不必要的绑定 :避免在UI控件中使用过多的数据绑定,尤其是在频繁更新的场景下,过多的绑定会影响性能。
- 使用虚拟化技术 :对于列表控件,如ListBox和ListView,可以使用虚拟化技术,只渲染当前可见区域的项目,提高渲染效率。
15.3 资源管理优化
- 及时释放资源 :在使用完资源后,及时释放资源,避免内存泄漏。例如,在使用完数据提供程序后,调用其Dispose()方法。
- 缓存数据 :对于一些频繁使用的数据,可以进行缓存,减少重复查询的开销。
16. 常见问题与解决方案
在使用WPF和CSLA .NET开发过程中,可能会遇到一些常见问题,以下是一些解决方案:
16.1 数据绑定问题
- 数据更新不及时 :确保在业务对象中正确触发PropertyChanged事件,并且在配置文件中设置CslaPropertyChangedMode为Xaml。
<add key="CslaPropertyChangedMode" value="Xaml" />
- 绑定错误 :检查绑定表达式是否正确,确保绑定的属性和对象类型匹配。
16.2 身份验证问题
- 自定义身份验证异常 :使用CSLA .NET的ApplicationContext对象提供的部分解决方案,但要注意一些.NET功能可能并非始终正常工作。
- 身份验证配置错误 :确保客户端和服务器端的CslaAuthentication值一致,并且在使用Windows身份验证时,正确配置WCF主机网站。
16.3 性能问题
- 界面卡顿 :检查是否存在大量的同步操作,尽量使用异步加载和分页加载方式。
- 内存泄漏 :检查是否存在未释放的资源,及时调用Dispose()方法释放资源。
17. 未来发展趋势与展望
随着技术的不断发展,WPF和CSLA .NET也将不断演进和完善。以下是一些未来可能的发展趋势:
17.1 与新技术的融合
- 与云计算的结合 :将应用程序的业务逻辑和数据存储迁移到云端,实现更强大的可扩展性和灵活性。
- 与人工智能的结合 :利用人工智能技术实现智能推荐、自动化处理等功能,提升用户体验。
17.2 跨平台支持
- 支持更多操作系统 :未来可能会支持在更多操作系统上运行,如Linux和macOS,扩大应用程序的使用范围。
- 跨平台开发框架的集成 :与跨平台开发框架集成,实现一次开发,多平台部署。
17.3 开发工具的改进
- 更强大的开发工具 :开发工具可能会提供更多的可视化设计和调试功能,提高开发效率。
- 自动化测试工具的支持 :提供更完善的自动化测试工具,确保应用程序的质量和稳定性。
18. 总结与回顾
本文全面介绍了使用WPF和CSLA .NET构建用户界面的相关技术和方法。从WPF的基本概念和特点,到CSLA .NET的身份验证、数据访问和业务逻辑处理,再到具体的界面设计和表单实现,我们详细阐述了每个环节的关键技术和操作步骤。
通过使用WPF和CSLA .NET,我们可以实现业务层与UI层的高度分离,提高开发效率和代码的可维护性。同时,数据绑定和数据门户机制为应用程序带来了强大的功能和灵活性,使其能够适应不同的应用场景和需求。
在实际开发中,我们需要根据具体情况灵活运用这些技术,不断优化和完善应用程序的性能和用户体验。希望本文能为你在WPF UI开发方面提供有价值的参考和指导,帮助你构建出更加优秀的用户界面应用程序。
19. 流程图展示
以下是MainForm中用户控件加载的流程图:
graph TD;
A[开始] --> B[创建EditForm控件];
B --> C[移除现有EditForm控件];
C --> D[添加新EditForm到Children集合];
D --> E[挂钩TitleChanged事件];
E --> F[结束];
20. 关键技术点总结表格
| 技术点 | 描述 | 代码示例 |
|---|---|---|
| 自定义身份验证 | 通过设置AppDomain的默认主体值实现,但有局限性,CSLA .NET提供部分解决方案 |
AppDomain.CurrentDomain.SetThreadPrincipal(principal)
|
| 数据绑定 | 使用数据绑定实现UI控件与业务对象的同步 |
<TextBox Text="{Binding Name}" />
|
| 值转换器 | 将一种类型的值转换为另一种类型或值,如VisibilityConverter和ListTemplateConverter |
vb Public Class VisibilityConverter Implements System.Windows.Data.IValueConverter ... End Class
|
| 数据提供程序 | 如CslaDataProvider,用于数据绑定和业务对象的操作 |
xml <csla:CslaDataProvider x:Key="RoleList" ObjectType="{x:Type ptracker:Roles}" FactoryMethod="GetRoles" ... />
|
| 命令绑定 | 通过命令绑定实现按钮与业务逻辑的交互 |
xml <CommandBinding Command="ApplicationCommands.Open" Executed="OpenCmdExecuted" CanExecute="OpenCmdCanExecute" />
|
超级会员免费看
5万+

被折叠的 条评论
为什么被折叠?



