WPF实现Tag Cloud
作者:Tony Qu
首先,我来解释一下什么叫Tag Cloud。Tag Cloud最早出现在Web 2.0的网站中,主要是用来做Tag归类的,在这样的控件中会把访问量高的Tag或者较热门的Tag的字体设得很大,而访问量小或者冷门的Tag则字体较小,如下所示:
这是从MSDN Blog上获得的一个Tag Cloud,在这个Blog中,Windows Presentation Foundation和WPF都比较热门,因为它们的字体最大。要想在WPF中实现这样的效果,我们先得理解web中它是如何实现的。让我们去掉web页面中的css看看它的“原形”,如下图所示:
是不是觉得很面善阿,这就是标准的ul列表(即<ul><li></li><li></li>...</ul>),之所以经过css处理之后可以变成流布局方式,关键在于对每一个Tag都使用了display:inline这个样式,这表示不占据整行。另外,之所以不同的Tag会拥有不同的字体大小,是因为在做HTML呈现的时候,后台程序已经根据热门程度对不同的Tag项应用了不同的css class,例如Windows Presentation Foundation这一项使用的是Tag2,WPF这一项使用的是Tag1,Virtual Earth使用的是Tag6 (注意,这里的TagX中X值越大,字体越小)。
好了,说了这么多,我想你应该对web中的实现有所了解了。既然web中可以用列表控件来实现,那么WPF可不可以也用ListBox控件呢?当然可以!
首先我们需要想办法让ListBox中的项变成流布局方式。默认情况下,ListBox内部使用StackPanel作为布局控件,所以我们看到的项都是独占一行的。为了能够替换默认的布局面板,我们可以使用ListBox.ItemsPanel标签。同时我们选择WrapPanel,因为它可以产生类似于inline的效果,它默认会让里面的项以自己的实际宽度一个挨着一个排列,直到超出WrapPanel的宽度之后再换行继续排列。所以,我们的ListBox代码大致是这样的:
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="true"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
这里还设置了ScrollViewer.HorizontalScrollBarVisibility="Disabled",这样可以保证其中的项不会无限制在同一行排列下去(这里的WrapPanel并没有设置宽度)。这样就解决了第一个技术问题——流布局,接下来我们来解决下一个问题——如何根据热门程度改变每个Tag的样式。
要解决这个问题,我们自然会想到使用DataTemplate,因为它是专门用来定制每个项的外观的(不仅仅是样式)。出于演示目的,这里的TagCloud仅支持显示,不支持超链接之类的交互功能,所以我们将用一个TextBlock来作为信息的载体。这个DataTemplate的代码如下:
<TextBlock Padding="0,0,10,0"
FontSize="{Binding Path=Count,Converter={StaticResource CountToFontSizeConverter}}"
Name="TagName" Text="{Binding Path=Name, Mode=Default}"></TextBlock>
</DataTemplate>
在这个TextBlock中,我们设置了好几个属性,我将会一个一个解释:
1. Padding属性
这个属性是用来设置内部的信息到边界的空白距离的,学过css的朋友一定都明白是什么意思。这里有四个值,分别依次表示Left, Top, Right, Bottom,所以这里我们仅设置Right Padding为10,这样可以保证每个Tag之间会有一定的间距,否则会挤成一堆。
2. Text属性
这个属性其实只是把对象的Name属性绑定到Text属性上用于显示。在本文最后的例子中,我定义了一个Tag class和TagCollection,专门用来存储Tag的信息,定义如下:
{
string name;
int count;
public Tag(string name, int count)
{
this.count = count;
this.name = name;
}
public string Name
{
get { return name; }
set { name = value; }
}
public int Count
{
get { return count; }
set { count = value; }
}
}
public class TagCollection : ObservableCollection<Tag>{}
这个Tag类只有两个属性,Name属性表示要显示的东西,Count属性表示热门程度,值越大相对应的字体越大。至于TagCollection,这里用到了泛型类ObservableCollection<T>,这个类在实际开发中很有用,可以省去不少不必要的代码,我只需要告诉它我的项是什么类型的就可以了,通常不用重载其中的方法。在本例中,我会把TagCollection的一个实例赋给所在Window的DataContext,这样这个TagCollection就可以在整个Window中作为一个公共的数据源使用了。
DataTemplate的上下文环境对象通常就是集合的某一个项,这个例子中,DataTemplate对应一个Tag类实例,所以可以直接把Binding的Path设置为Name,即表示从Tag的Name属性获得数据。
3. FontSize属性
这里的FontSize是决定Tag项样式的关键,这里自然是要让FontSize随Tag.Count的变化而变化,所以我们需要一个把Integer转换为FontSize的Converter(WPF内部没有这样一个转换类)。这个Converter的代码如下:(这段代码来自于Family Show 2.0)
{
IValueConverter Members
}
这个转换类设置了一个最大上限maxFontSize,这样无论Count的值多大都不会导致字体过大。
为了对这个ListBox应用DataTemplate以及数据,最终的ListBox的XAML如下所示:
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemTemplate="{DynamicResource TagCloudTemplate}"
ItemsSource="{Binding}"
Grid.Row="0">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="true"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
这里的ItemsSource="{Binding}"表示让ListBox向上寻找DataContext,直到找到为止,这也是我们刚才把TagCollection实例赋给Window的DataContext属性的原因。
完整的示例代码可以从这里下载,最终的运行效果如下所示:
总结
在本例中,你会看到所有的UI代码都是在XAML中实现的,这有点像web中的css文件。推广WPF技术的一个目的就是要让UI和代码尽量分离,虽然目前的WPF还无法像css+html那样灵活,但这是一个好的开始。
版权声明:本文由作者Tony Qu原创, 未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。