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代码大致是这样的:

< ListBox  Name ="TagCloudList"
                 ScrollViewer.HorizontalScrollBarVisibility
="Disabled" >
            
< ListBox .ItemsPanel >
                
< ItemsPanelTemplate >
                    
< WrapPanel  IsItemsHost ="true" />
                
</ ItemsPanelTemplate >
            
</ ListBox.ItemsPanel >
</ ListBox >

这里还设置了ScrollViewer.HorizontalScrollBarVisibility="Disabled",这样可以保证其中的项不会无限制在同一行排列下去(这里的WrapPanel并没有设置宽度)。这样就解决了第一个技术问题——流布局,接下来我们来解决下一个问题——如何根据热门程度改变每个Tag的样式。

要解决这个问题,我们自然会想到使用DataTemplate,因为它是专门用来定制每个项的外观的(不仅仅是样式)。出于演示目的,这里的TagCloud仅支持显示,不支持超链接之类的交互功能,所以我们将用一个TextBlock来作为信息的载体。这个DataTemplate的代码如下:

         < DataTemplate  x:Key ="TagCloudTemplate" >
            
< 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的信息,定义如下:
     public   class  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)
     public   class  CountToFontSizeConverter : IValueConverter
    
{
        
IValueConverter Members#region IValueConverter Members

        
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        
{
            
const int minFontSize = 6;
            
const int maxFontSize = 38;
            
const int increment = 3;

            
int count = (int)value;

            
return ((minFontSize + count + increment) < maxFontSize) ? (minFontSize + count + increment) : maxFontSize;
        }


        
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        
{
            
throw new NotImplementedException("not implemented");
        }


        
#endregion

    }

这个转换类设置了一个最大上限maxFontSize,这样无论Count的值多大都不会导致字体过大。

为了对这个ListBox应用DataTemplate以及数据,最终的ListBox的XAML如下所示:
         < ListBox  Name ="TagCloudList"
                 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那样灵活,但这是一个好的开始。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值