WPF中自定义的DataTemplate中的控件,在Window_Loaded事件中加载机制初探
最近因为项目需要,开始学习如何使用WPF开发桌面程序。使用WPF一段时间之后,感觉WPF的开发思维和Winform还是有比较大的区别,包括页面布局、数据绑定、自定义模板等等。
整个项目中,有一个业务逻辑的实现方式,需要我在使用Listview控件中插入Combobox控件,效果如下图:
第一次尝试:为了实现这个效果,我在Xaml文件中定义的代码如下:
1.资源模板定义Xaml语句
<Window.Resources> <namespc:ListViewItemStyleSelector x:Key="mySelector"/> <DataTemplate x:Key="FirstCell" > <ComboBox Name="combobox" Width="80" /> </DataTemplate> </Window.Resources></span></span>
2.Listview定义Xaml语句:
<ListView Name="listview1" Margin="5" ItemContainerStyleSelector="{DynamicResource mySelector}" SelectionChanged="listview1_SelectionChanged" PreviewMouseDoubleClick="listview1_PreviewMouseDoubleClick"> <ListView.View> <GridView> <GridViewColumn Header="料品编码" DisplayMemberBinding="{Binding Path=II_Code}" ></GridViewColumn> <GridViewColumn Header="料品名称" DisplayMemberBinding="{Binding Path=II_Name}" ></GridViewColumn> <GridViewColumn Header="料品规格" DisplayMemberBinding="{Binding Path=II_Spec}" ></GridViewColumn> <GridViewColumn Header="料品型号" DisplayMemberBinding="{Binding Path=II_Version}" ></GridViewColumn> <GridViewColumn Header="料品计量单位" DisplayMemberBinding="{Binding Path=II_UnitName}" ></GridViewColumn> <GridViewColumn Header="工艺路线版本" CellTemplate="{StaticResource FirstCell}"></GridViewColumn> </GridView> </ListView.View> </ListView></span></span>
前台赋值语句这里就省略了,运行程序后发现所有的combobox控件数据源都是空的!
后台检查了数据源,数据源是有数据的,但是并没有成功绑定到相应的Combobox中!在检查了很多遍我的数据源列表之后,确认列表中的数据是存在,而且是符合Combobox的数据源要求的。现在问题很明显了:问题就出在数据绑定到相应Combobox这个过程中!
第二次尝试:数据绑定的问题可能存在两种情况,1.数据绑定语法错误,导致数据无法绑定、2.Combobox控件加载有问题,导致数据无法绑定。
针对第一种情况,我查阅资料,确认我的Xaml语法没有错误。既然Xaml语句无法为我需要的Combobox绑定数据源,那么,我能不能直接利用C#代码来显示地为每一个Combobox绑定数据源呢?
要显示地为Combobox赋值,首先需要利用C#代码获取到Combobox控件列表,代码如下:
private void Window_Loaded(object sender,RoutedEventArgs e) { List<Combobox> cbs = FindVisualChild<Combobox>(this); } </span><span style="font-size:14px;"> private List<T> FindVisualChild<T>(DependencyObject obj) where T : DependencyObject { try { List<T> TList = new List<T> { }; for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { DependencyObject child = VisualTreeHelper.GetChild(obj, i); if (child != null && child is T) { TList.Add((T)child); } else { List<T> childOfChildren = FindVisualChild<T>(child); if (childOfChildren != null) { TList.AddRange(childOfChildren); } } } return TList; } catch (Exception ee) { MessageBox.Show(ee.Message); return null; } }
运行程序之后,发现cbs.Count的值为0!这个发现让我产生了一个让自己觉得有点惊异的想法:难道说在Window_Loaded()事件中,控件并没有加载吗?
为了验证这个想法,我对Xaml代码中的Combobox的Datatemplate部分做出了修改,修改后代码如下:
<Window.Resources> <namespc:ListViewItemStyleSelector x:Key="mySelector"/> <DataTemplate x:Key="FirstCell" > <ComboBox Name="combobox" <em><u>Loaded="combobox_Loaded"</u></em> Width="80" /> </DataTemplate> </Window.Resources>
如上代码所示,我为每一个Combobox添加了Loaded()事件,只要在窗体加载的时候,监视combobox_Loaded()事件是否发生,就知道Combobox在Window_Loaded()事件中有没有加载了。
运行的结果让我略微感到意外:窗体加载的时候,combobox_Loaded()事件确实没有发生,也就是说Combobox在窗体加载的时候并没有加载;但是当我尝试移动listview的滚动条或者点击listviewitem的时候,程序命中了combobox_Loaded()事件。
对此,我猜测:根据Xaml代码的树形结构来看,本程序中定义的Combobox的直接parent并不是this(窗体)而是listview,所以当this加载的时候,并没有为Combobox加载,所以当Combobox的直接parent加载的时候,Combobox才加载。
而且,在研究combobox_Loaded()事件的过程中,我发现一个现象:
并不是所有在listview中的Combobox都一次性加载完毕,程序会优先加载在listview的显示区域内的listviewitem中的控件,处在显示区域之外的listviewitem只有当其进入显示区域之后才进行加载。
这就会导致这样一个问题:cbs列表中的Combobox很可能因为加载不全,而导致与我提供的数据源列表对应不上。为了解决这个问题,我决定采用“暴力”的笨办法,强制让所有的Combobox都加载。
第三次尝试:
强制加载所有Combobox的函数如下:
/// <summary> /// 滚动Listview1 /// 因为combobox是放在datatemplate中的,wpf的加载机制就是加载当前listview中显示区域的 /// datatemplate中的控件,显示区域之外的控件不加载。不加载的combobox在系统中并没有生成 /// 变量实例,因为无法为每一个combobox的数据源进行赋值。该方法需要放在window_loaded事件之外! /// 故此,采用将listview“从头滚动到底”的方式,强制加载所有的combobox。此方法应该有更好的替代方法,暂时没找到。 /// </summary> private bool ScrollListview( int index) { if (listview1.Items.Count >0) { int count = listview1.Items.Count; for (int i = 0; i < count; i ++) { listview1.ScrollIntoView(listview1.Items[i]); } if (index > -1) { listview1.ScrollIntoView(listview1.Items[index]); } } int x = FindVisualChild<ComboBox>(this).Count; int y = listview1.Items.Count; return x==y; }
完成这一步之后,只要ScrollView()函数返回true,就进入以下函数:
/// <summary> /// 加载所有的combobox /// </summary> private void LoadAllCombobox() { int count = listview1.Items.Count; List<ComboBox> cbl = FindVisualChild<ComboBox>(listview1); for (int i = 0; i < count; i++) { //这段代码将combobox和listviewitem内容一一对应起来。 ItemInfoLists item = listview1.Items[i] as ItemInfoLists; cbl[i].ItemsSource = item.TechVersionList; cbl[i].DisplayMemberPath = "TRV_Version"; cbl[i].SelectedValuePath = "TR_VersionID"; cbl[i].SelectedIndex = 0; } }
完成以上步骤之后,运行程序,所有Combobox都已经正确的加载并且绑定数据源,如下图所示:
总结:因为combobox放在datatamplate中,WPF的控件加载机制决定了在listview显示区域之外的listviewitem中的控件暂不加载,待到所属listviewitem显示的时候再行加载, 其加载顺序和listview的itemssource的遍历顺序存在错位情况,无法控制。 datatemplate中的combobox数据源需要在C# code中显式加载。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
附:解决Combobox数据源加载的问题之后,我又发现了另外一个问题。那就是,当我利用鼠标滚轮滑动listview的时候,Combobox的数据源会存在丢失的情况,需要对Combobox重新进行一次数据绑定。这个问题的原因我暂时没有找到。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
WPF实现选项卡效果(2)——动态添加AvalonDock选项卡
原文: WPF实现选项卡效果(2)——动态添加AvalonDock选项卡 简介 在前面一篇文章里面,我们使用AvalonDock实现了类似于VS的选项卡(或者浏览器的选项卡)效果。但是我们是通过xaml代码实现。 现在我们尝试通过C#代码实现选项卡的动态添加。 完整系列 ● 第一部分 ● 第二部分 ● 第三部分 在Git中下载工程源码 修改Xaml代码 在前面一篇文章的Xaml代码里面,添加如下代码: <Grid.RowDefinitions> <RowDefinition Height="25"></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Menu Name="Menu1" Grid.Row="0"> <MenuItem Name="item_AddNew" Header="添加新选项卡" Click="item_AddNew_Click"></MenuItem> </M...
- 下一篇
C# WPF 中用代码模拟鼠标和键盘的操作
原文: C# WPF 中用代码模拟鼠标和键盘的操作 原文地址 C#开发者都知道,在Winform开发中,SendKeys类提供的方法是很实用的。但是可惜的是,在WPF中不能使用这个方法了。 我们知道,在WPF中非UI线程刷新UI线程,需要使用Dispatcher.Invoke((Action)delegate { /* Your code is put here */ });方法。这里调用System.Windows.Forms.SendKeys.Send()方法会报错。 下面这个代码文件做了一个很好的包装,可以下载后参考: Simulation.zip 如何使用呢? 很简单, 要敲一个键, 比如回车: Keyboard.Press(Key.Enter); Keyboard.Release(Key.Enter); 要敲一个组合键:比如Alt+F4: Keyboard.Press(Key.LeftAlt); Keyboard.Press(Key.F4); Keyboard.Release(Key.LeftAlt); Keyboard.Release(Key.F4); 要敲一段文字:...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS关闭SELinux安全模块
- 设置Eclipse缩进为4个空格,增强代码规范
- SpringBoot2整合Redis,开启缓存,提高访问速度