比较事件驱动型 UI 和数据绑定型 UI
事件驱动型用户界面 (UI) 是围绕控件公开的事件设计的。 这些事件可能与触发事件时调用的事件处理程序代码关联。 例如,假设你有一个按钮,该按钮在被单击后会执行长时间运行的操作。 分配给 Clicked
事件的事件处理程序可以启动该操作,然后将按钮的 IsEnabled
属性设置为 false
,以防止在操作运行时再次单击该按钮。
数据绑定 UI 使用数据绑定来呈现数据并与数据交互。 控件的属性绑定到数据对象的属性,并且这些绑定可以检测属性的更改。 使用上一示例时,考虑执行长时间运行操作的按钮。 将 IsEnabled
属性绑定到数据对象的 IsBusy
属性,而不是在代码隐藏中禁用按钮。 每当数据对象变得“忙”时,按钮的启用状态就会相应地自动进行更改。
使用事件和代码隐藏的优缺点
使用控件的带代码隐藏的事件处理程序是为 UI 设计应用逻辑的一种快速便捷的方法。 可以使用代码来调用服务以获取数据、对该数据执行操作以及与页面上的控件交互。 该代码用于让 UI 和数据保持同步。
考虑气象服务应用的示例。 以下 XAML 片段包含一个简单的 UI 按钮,用户选择该按钮即可获取最新数据并使用湿度来更新 UI。
<VerticalStackLayout Margin="10">
<HorizontalStackLayout Spacing="20">
<Label Text="Postal Code:" VerticalOptions="Center" />
<Entry x:Name="PostalCode" WidthRequest="100" />
<Button x:Name="RefreshWeatherButton" Text="Refresh" WidthRequest="200" Clicked="RefreshWeatherButton_Clicked" />
</HorizontalStackLayout>
<Label x:Name="Humidity" Text="Humidity: ?" />
</VerticalStackLayout>
此示例中有三个命名控件:
- 名为 PostalCode 的
Entry
控件。 - 名为 RefreshWeatherButton 的
Button
控件。 - 名为“湿度”的
Label
控件。
RefreshWeatherButton
有一个为 Clicked
事件声明的事件处理程序。 单击该按钮后,事件处理程序将使用 PostalCode
条目控件中输入的数据向天气服务查询最新的天气预报,并将 Humidity
标签的文本设置为当前湿度。
private void RefreshWeatherButton_Clicked(object sender, EventArgs e)
{
WeatherService.Location = PostalCode.Text;
WeatherService.Refresh();
Humidity.Text = $"Humidity: {WeatherService.Humidity}";
}
在这一事件处理程序中,三个控件通过代码隐藏彼此紧密耦合并与数据紧密耦合。
这种设计非常适合小型 UI,但一旦 UI 变得复杂,维护紧密耦合的代码隐藏可能就会变得很麻烦。 如果删除或更改控件,则必须清理使用这些 UI 控件的所有代码,其中可能包括事件处理程序。 如果决定重新设计 UI,则还需重构大量代码。 当支持数据结构发生变化时,必须深入研究每个 UI 的代码才能保持同步。
数据绑定很有用
数据绑定可以在 XAML 或代码中实现,但在 XAML 中要常见得多,有助于减小代码隐藏文件。 通过使用声明性代码或标记替换事件处理程序中的过程代码,可简化应用,使其清晰明了。 由于绑定不需要代码隐藏,因此你可以轻松地创建、更改或重新设计 UI,使之与你所希望的数据呈现方式相适应。
让我们采用上一部分的示例,但将其更新为使用数据绑定:
<VerticalStackLayout Margin="10">
<HorizontalStackLayout Spacing="20">
<Label Text="Postal Code:" VerticalOptions="Center" />
<Entry Text="{Binding Location, Mode=OneWayToSource}" WidthRequest="100" />
<Button Text="Refresh" Command="{Binding RefreshWeather}" WidthRequest="200" />
</HorizontalStackLayout>
<Label Text="{Binding Humidity}" />
</VerticalStackLayout>
可以发现进行了数据绑定的属性,它们使用 XAML 扩展语法 {Binding ...}
作为属性的值。 暂时不用担心具体细节,本模块稍后会对此进行介绍。
在 XAML 中声明了同样的三个控件,但它们都没有命名,因为不需要名称:
Entry
控件:此控件的
Text
属性绑定到名为Location
的属性。Button
控件:按钮的
Command
属性绑定到名为RefreshWeather
的属性。Command
是按钮上的一个属性,会在按下按钮时调用代码。 它是数据绑定中使用的Clicked
事件的替代项。Label
控件:这个
Text
属性绑定到名为Humidity
的属性。
在这个简单的 UI 中,所有代码隐藏都被消除了。 删除所有代码隐藏并不是数据绑定的重点,尽管这通常是可能的。 代码隐藏仍然占有一席之地。 你实施多少数据绑定取决于你自己。
现在,UI 与数据对象松散耦合。 为什么是松散耦合而不是紧密耦合? 原因在于绑定的评估方式。 每个控件都有一个 BindingContext
属性。 如果未设置上下文,则使用父控件的上下文,依此类推,直到评估 XAML 的根。 评估绑定时,将检查上下文的对象实例是否具有所需的属性,例如标签控件的 Text
绑定(绑定到上下文的 Humidity
属性)。 如果上下文中不存在 Humidity
,则什么也不会发生。
由于 UI 是松散耦合的,因此你可以重新设计 UI,而不必担心破坏代码。 但是,可能会破坏功能。 例如,你可以删除该按钮,应用仍然可以编译并运行,但你没有办法刷新天气。 另一方面,你可以将 Entry
和 Button
控件替换为单个 SearchBar
控件。 该控件允许你输入文本并调用命令。
<SearchBar Text="{Binding Location, Mode=OneWayToSource}" SearchCommand="{Binding RefreshWeather}" />
如你所见,在 UI 设计中使用数据绑定有助于发展和更改 UI,无需做太多工作。 它自动让 UI 与数据保持同步,而应用逻辑与 UI 则保持分离状态。