WPF 代码生成绑定 DataTemplate

前几天在做 .NET WPF项目的时候,有这样一个需求,在 自定义控件样式文件 里面需要为一个 ListBox 绑定 DataTemplate 模板,而且在模板里面有一个Viewbox ,更变态的是,需要绑定一个鼠标左键单击事件,我们都知道自定义控件的 主题样式文件(视图XAML) 是无法访问到控件类的,这一下子就把我给难住了,后来一想,看来只能在控件类里面操作了。


1 . 获得样式文件的控件对象


怎么才可以在控件类中获得样式文件中的 控件对象 呢,这个还是很简单的,首先为控件创建一个类型相同的 UIElement对象 ,我这里是 ListBox

1
private ListBox _cameraListBox;       


然后设置一个 只读 的控件名称,用于绑定到样式文件中

1
private const string Parid_cameraListBox = nameof(Parid_cameraListBox);    


标识 模板化的已命名部件

1
[TemplatePart ( Name = Parid_cameraListBox ) ]


然后在 样式文件 中为控件的 x:Name 绑定上 II 步骤的名称:

1
2
3
4
5
<ListBox
x:Name="Parid_cameraListBox"
ScrollViewer.CanContentScroll="True"
Style="{StaticResource ListBoxStyCarmera}">
</ListBox>


重写控件类中的 OnApplyTemplate 方法

1
2
3
4
5
6
7
8
public override void OnApplyTemplate()
{
if ((_cameraListBox = GetTemplateChild(Parid_cameraListBox) as ListBox) != null)
{
Binding binding = new Binding(nameof(HistoryVideoSources)) { Source = this };
_cameraListBox.SetBinding(ListBox.ItemsSourceProperty, binding);
}
}

这样就可以在 控件类 中拿到 样式视图 里面的控件对象了

2 . 代码生成绑定DateTemplate

正如上面所说,为什么我要在后端生成绑定DataTemplate 因为我需要绑定事件,接下来就说说如何为 ListViewC# 代码的方式绑定上数据模板。


通过查看 DataTemplate 的从元数据,发现它是继承自 FrameworkTemplate,这个类有一个名为 VisualTree 类型为 FrameworkElementFactory 的属性,那这样就好很解决了,因为其实数据模板里面的每一个子控件 其实都是一个 FrameworkElementFactory 对象,废话不多说,开写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/// <summary>
/// 初始化下载数据模板
/// </summary>
private void Down_ListBox_Template ()
{
DataTemplate dataTemplate = new DataTemplate ();
FrameworkElementFactory topElement = CreateTopElement ();

FrameworkElementFactory downPath = CreatePathElement (GeometryDown, "下载历史");
FrameworkElementFactory downViewBox = CreateViewBoxElement (VideoAxisActionType.Dwon.ToString (), downPath);
topElement.AppendChild (downViewBox);

FrameworkElementFactory favoritePath = CreatePathElement (GeometryFavorite, "收藏历史");
FrameworkElementFactory favoriteViewBox = CreateViewBoxElement (VideoAxisActionType.Favorite.ToString (), favoritePath);
topElement.AppendChild (favoriteViewBox);

FrameworkElementFactory openPath = CreatePathElement (GeometryOpen, "打开视频");
FrameworkElementFactory openViewBox = CreateViewBoxElement (VideoAxisActionType.Open.ToString (), openPath);
topElement.AppendChild (openViewBox);

dataTemplate.VisualTree = topElement;
_downButtonListBox.ItemTemplate = dataTemplate;
}

/// <summary>
/// 创建顶层数据模板容器
/// </summary>
/// <returns></returns>
private FrameworkElementFactory CreateTopElement ()
{
FrameworkElementFactory frameworkElementFactory = new FrameworkElementFactory (typeof (StackPanel));
frameworkElementFactory.SetValue (StackPanel.HeightProperty, 16.00);
frameworkElementFactory.SetValue (StackPanel.MarginProperty, new Thickness (0, 3, 5, 1));
frameworkElementFactory.SetValue (StackPanel.OrientationProperty, Orientation.Horizontal);
return frameworkElementFactory;
}

/// <summary>
/// 创建Path元素模板
/// </summary>
/// <param name="GeometryPath"></param>
/// <param name="ToolTipStr"></param>
/// <returns></returns>
private FrameworkElementFactory CreatePathElement (string GeometryPath, string ToolTipStr)
{
FrameworkElementFactory path = new FrameworkElementFactory (typeof (Path));
path.SetValue (CursorProperty, Cursors.Hand);
path.SetValue (Path.DataProperty, Geometry.Parse (GeometryPath));
path.SetValue (Path.FillProperty, new SolidColorBrush ((Color) ColorConverter.ConvertFromString ("#c8c7c3")));
path.SetValue (ToolTipProperty, ToolTipStr);
return path;
}

/// <summary>
/// 创建ViewBox元素模板
/// </summary>
/// <param name="NameStr"></param>
/// <param name="thick"></param>
/// <param name="pathChild"></param>
/// <returns></returns>
private FrameworkElementFactory CreateViewBoxElement (string NameStr, FrameworkElementFactory pathChild)
{
FrameworkElementFactory viewBox = new FrameworkElementFactory (typeof (Viewbox));
viewBox.SetValue (HeightProperty, 14.00);
viewBox.SetValue (WidthProperty, 14.00);
viewBox.SetValue (MarginProperty, new Thickness (10, 0, 0, 2));
viewBox.SetValue (NameProperty, NameStr);
viewBox.AddHandler (MouseLeftButtonDownEvent, new MouseButtonEventHandler (viewBox_LeftMouseButtonDown));
viewBox.AppendChild (pathChild);
return viewBox;
}

上面我们声明了一个 DataTemplate 对象,然后开始创建各种 子控件 其实这里没有什么好说的,都是一些设置属性和层叠控件,需要多少层,就创建多少层,然后按照顺序把他们依次通过 AppendChild 方法添加进去,最后把 顶层FrameworkElementFactory 对象赋值给数据模板的 VisualTree 属性就可以了,最后一步就是就是:

1
_downButtonListBox.ItemTemplate = dataTemplate;

把我们声明的数据模板项给对应控件的ItemTemplate属性就可以了

3 . 事件绑定


上面我们介绍了,如何用代码去构建一个DataTemplate,那么怎么绑定事件呢,FrameworkElementFactory 类,有一个 AddHandler 重载方法:

1
2
public void AddHandler(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo);
public void AddHandler(RoutedEvent routedEvent, Delegate handler);
参 数 名 类 型 说 明
routedEvent RoutedEvent 路由事件的标识
handler Delegate 对处理程序实现的委托事件
handledEventsToo bool 标记为路由的事件处理, 默认值为 false

前两个参数不用说了,一个 路由事件标识 一个 具体的委托事件 ,重点说一下最后一个参数 handledEventsToo ,我们都知道 WPF路由事件 的路由策略如果设置为 Bubble 那么这个路由事件会一直沿着 可视树 向上传递,所以需要一个标识来表示这个事件是否已经被处理,那么 handledEventsToo 这个参数就担任了这个标识的作用。


其实我觉得到这里可以结束了,但是还是说一下具体如何绑定事件吧:

1
viewBox.AddHandler(MouseLeftButtonDownEvent, new MouseButtonEventHandler(viewBox_LeftMouseButtonDown));

这里的 viewBox 就是一个 FrameworkElementFactory 对象,通过AddHandler方法就可以绑定事件了。

如果还是有疑问可以看一下这个项目:WPF实现的类似Adobe AE时间轴的控件

评论