本文中着重讲述通过继承Control来实现自定义控件。
通过本文的学习,我们将学到以下知识点:
1). 如何自定义控件
2). 如何自定义DataTemplate
3). 如何自定义数据源
4). 代码中如何根据数据源以及DataTemplate创建Child控件,并且添加到自定义控件的Visual tree中。

自定义控件具体实现步骤如下:
1. 定义类,继承自Control,代码如下:
Code:
public class DevDivTemplatedCtrl : Control

2. 定义数据源
我们定义了CLR和DP两种,为了代码中可以直接赋值,同时支持绑定;代码如下:
Code:
        public static DependencyProperty DevDivItemsSourceProperty = DependencyProperty.Register("DevDivItemsSource",
                                                                    typeof(IEnumerable),
                                                                    typeof(DevDivTemplatedCtrl),
                                                                    new PropertyMetadata(DevDivItemsSourceChanged));

        public IEnumerable DevDivItemsSource
        {
            get { return (IEnumerable)GetValue(DevDivItemsSourceProperty); }
            set { SetValue(DevDivItemsSourceProperty, value); }
        }
代码并不复杂,我们就是想为类保存一个IEnumerable对象作为数据源
3. 自定义一个模板,这样控件使用者可以通过这个模板为数据源提供显示样式,代码如下:
Code:
        public DataTemplate DevDivItemTemplate
        { get; set; }
4. 继续看类实现前,我们先看看这个控件的默认ControlTemplate,代码在Themes/Generic.xaml中
Code:
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:DevDivTemplatedSample">
    
    <Style TargetType="local:DevDivTemplatedCtrl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:DevDivTemplatedCtrl">
                    <StackPanel x:Name="RootPanel" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
代码并不复杂,其中最重要的是这个自定义控件以x:Name为RootPanel的StackPanel作为数据源显示的Parent。即这个控件的使用者,是把子控件添加到RootPanel下的。
5. 我们返回控件类文件DevDivTemplatedCtrl.cs
我们会看到,它定义了一个private成员
Code:
        //  RootPanel
        private Panel _pnl;
_pnl指向的就是RootPanel,_pnl初始化代码在OnApplyTemplate中,代码如下:
Code:
_pnl = this.GetTemplateChild("RootPanel") as Panel;
6. 控件代码中最重要部分就是如何读取DevDivItemsSource以及如何利用用户提供的DevDivItemTemplate把Child控件加入到DevDivTemplatedCtrl这个自定义控件中。
Code:
       private void ReconstructItemTree(IEnumerable items)
        {
            if (_pnl == null)
                return;

            //  Iterate DevDivItemsSource
            //  And assign each item to the DataContext of FrameworkElement which is loaded from DevDivItemTemplate
            foreach (var item in items)
            {
                FrameworkElement obj = DevDivItemTemplate.LoadContent() as FrameworkElement;
                obj.DataContext = item;
                _pnl.Children.Add(obj);
            }
        }
上面代码中就是遍历DevDivItemsSource中每一个Item,然后把它赋值给模板控件的DataContext;并且把模板控件添加到RootPanel得Children中。
7. 接下来我们来看看如何使用这个自定义控件:
1). 在MainPage.xaml中添加如下代码:
Code:
       <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <TemplatedCtrl:DevDivTemplatedCtrl x:Name="TemplatedCtrl1">
                <TemplatedCtrl:DevDivTemplatedCtrl.DevDivItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Name}" />
                    </DataTemplate>
                </TemplatedCtrl:DevDivTemplatedCtrl.DevDivItemTemplate>
            </TemplatedCtrl:DevDivTemplatedCtrl>
        </Grid>
实际上我们就是给DevDivTemplatedCtrl设置了名字,并且设置了一下DevDivItemTemplate,
控件的名字为TemplatedCtrl1,DevDivItemTemplate中指定每一个item只有一个TextBlock,并且绑定到数据源的Name中。
2). MainPage.xaml.cs中设置数据源,数据源为一个City列表,City作为一个Item,City有一个Name属性;具体代码如下:
Code:
            ObservableCollection<City> cities = new ObservableCollection<City>();
            cities.Add(new City("Beijing"));
            cities.Add(new City("Shanghai"));

            TemplatedCtrl1.DevDivItemsSource = cities;
通过TemplatedCtrl1.DevDivItemsSource = cities;设置,自定义控件的DevDivItemsSource属性为cities,也就是第2节中提到的IEnumerable变成了cities(City列表)。
有了这个列表,第6小节中,就会遍历这个cities,取出每一个City,然后把City赋值给模板控件的DataContext,这样TextBlock就会赋值为City.Name,第6步最终把Text=City.Name的每一个TextBlock添加到RootPanel中。

Regards
Vincent
http://weibo.com/xueyw