前几天在做 .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 因为我需要绑定事件,接下来就说说如何为 ListView 用 C# 代码的方式绑定上数据模板。
Ⅰ
通过查看 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
|
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; }
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; }
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; }
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时间轴的控件