這篇將介紹使用DynamicResource實現動態的界面切換功能。熟悉WPF的園友應該已經猜到了實現方式,簡而言之就是動態替換DataTemplate,ControlTemplate,Style等等UI相關的屬性。
那么使用DynamicResource能讓UI動態到什么程度呢?可以說,心有多大,就可以做多大,只要你想得到,就可以做出來。
下面以展示層次數據結構為例,實現了運行時切換數據顯示界面結構的功能。先來看一下要顯示的數據,是一個XML文件。
<earth>
<country name="US">
<family name="Bill's">
<member name="Bill"/>
<member name="Mark"/>
</family>
<family name="Hugo's">
<member name="Hugo"/>
<member name="Sherry"/>
</family>
<family name="Li's">
<member name="Li"/>
<member name="Jay"/>
</family>
</country>
<country name="China">
<family name="陸家">
<member name="嘴"/>
<member name="臉"/>
</family>
<family name="徐家">
<member name="匯"/>
<member name="仁"/>
</family>
<family name="黃浦">
<member name="江"/>
<member name="邊"/>
</family>
</country>
</earth>
我們要用三種方式來展示這個數據,一種是最常見的TreeView,還可以用一組並列的ListBox,還有不太常見的嵌套式ItemsControl。如下圖所示。
圖1. TreeView
圖2. ListView

圖3. GroupView
要實現這些效果,可以使用DataTemplate。把界面中會變的部分獨立出來,有人說這個界面除了上面的菜單不變,整個都在變啊。沒錯,那就把整個主體部分獨立出來,放到DataTemplate里。而Window里就只有一個菜單和一個占位符了。如下所示。
<Window x:Class="Skinning.DemoWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="View Demo" Height="300" Width="300">
<DockPanel DataContext="{Binding Source={StaticResource XMLDataDataSource}}">
<Menu DockPanel.Dock="Top">
<MenuItem Header="List View" Click="OnListViewClick"/>
<MenuItem Header="Group View" Click="OnGroupViewClick"/>
<MenuItem Header="Tree View" Click="OnTreeViewClick"/>
</Menu>
<ContentPresenter Content="{Binding}" ContentTemplate="{DynamicResource EarthDataTemplate}"/>
</DockPanel>
</Window>
然后就是定義上面引用到的EarthDataTemplate。以ListView的DataTemplate為例,如下所示。
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate x:Key="NameTemplate">
<TextBlock Text="{Binding XPath=@name}"/>
</DataTemplate>
<DataTemplate x:Key="EarthDataTemplate">
<UniformGrid Rows="1" DataContext="{Binding XPath=earth/country}">
<ListBox ItemsSource="{Binding Mode=Default}" x:Name="countryList" ItemTemplate="{StaticResource NameTemplate}"/>
<ListBox DataContext="{Binding SelectedItem, ElementName=countryList}"
ItemsSource="{Binding Mode=OneWay, XPath=family}"
x:Name="familyList" ItemTemplate="{StaticResource NameTemplate}"/>
<ListBox DataContext="{Binding SelectedItem, ElementName=familyList}"
ItemsSource="{Binding Mode=OneWay, XPath=member}"
ItemTemplate="{StaticResource NameTemplate}"/>
</UniformGrid>
</DataTemplate>
</ResourceDictionary>
其它的DataTemplate就不一一例出了,完整的程序可以從這里下載。
雖然這個例子中只是展示了界面結構上的變化,只使用了DataTemplate,其它的小Case的形式的界面也完全不在話下。比如配色、控件樣式、控件位置,乃至所謂的換膚,可以分解為這些技巧的組合。當你把DynamicResource、Style、TemplateSelector、Converter、MarkupExtension等各種WPF技術都用上的時候就會發現WPF可以提供很強大的界面生成功能。
再來介紹一下這種方式的缺點。
為什么微軟的文檔和WPF的相關書籍中都沒有介紹這種方法呢?就像上一篇關於語言支持里列舉的現有方案一樣,都是要Build進DLL中呢? 我想其中一個原因就是安全上的考慮。把XAML文件這樣赤祼 祼地放在外面,對於了解WPF的人來說,完全可以利用這個文件“執行任意代碼”。這個很眼熟吧,常常出現在微軟的各個安全補丁的描述中。而且一般會是嚴重的漏洞。
這個問題雖然嚴重,但也是基本可以解決的。像Web中各種Editor一樣,可以對XAML里的內容,過濾一下,替換一下,限制一下等等。如果還不放心,可以把自己定義XML格式來定義界面,然后在內部用XSLT轉成XAML再加載。對自定義的XML加限制會更容易些。
這篇內容比較簡單,不過是為了下篇主要內容打個基礎。下篇將要展示一個自定義的Window的Style,把WPF的WinForm式的邊框去掉。