WPF中自定义的DataTemplate中的控件,在Window_Loaded事件中加载机制初探

简介: 原文:WPF中自定义的DataTemplate中的控件,在Window_Loaded事件中加载机制初探         最近因为项目需要,开始学习如何使用WPF开发桌面程序。
原文: 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重新进行一次数据绑定。这个问题的原因我暂时没有找到。

目录
相关文章
|
4月前
|
开发框架 缓存 前端开发
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
|
4月前
|
C# 开发者 Windows
一款基于Fluent设计风格、现代化的WPF UI控件库
一款基于Fluent设计风格、现代化的WPF UI控件库
120 1
|
4月前
|
C# Windows
WPF中如何使用HandyCotrol控件库
WPF中如何使用HandyCotrol控件库
214 1
|
4月前
|
C# 微服务 Windows
模块化革命:揭秘WPF与微服务架构的完美融合——从单一职责原则到事件聚合器模式,构建高度解耦与可扩展的应用程序
【8月更文挑战第31天】本文探讨了如何在Windows Presentation Foundation(WPF)应用中借鉴微服务架构思想,实现模块化设计。通过将WPF应用分解为独立的功能模块,并利用事件聚合器实现模块间解耦通信,可以有效提升开发效率和系统可维护性。文中还提供了具体示例代码,展示了如何使用事件聚合器进行模块间通信,以及如何利用依赖注入进一步提高模块解耦程度。此方法不仅有助于简化复杂度,还能使应用更加灵活易扩展。
112 0
|
4月前
|
C# 前端开发 UED
WPF数据验证实战:内置控件与自定义规则,带你玩转前端数据验证,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,数据验证是确保输入正确性的关键环节。前端验证能及时发现错误,提升用户体验和程序可靠性。本文对比了几种常用的WPF数据验证方法,并通过示例展示了如何使用内置验证控件(如`TextBox`)及自定义验证规则实现有效验证。内置控件结合`Validation`类可快速实现简单验证;自定义规则则提供了更灵活的复杂逻辑支持。希望本文能帮助开发者更好地进行WPF数据验证。
157 0
|
4月前
|
C# UED 定位技术
WPF控件大全:初学者必读,掌握控件使用技巧,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,控件是实现用户界面交互的关键元素。WPF提供了丰富的控件库,包括基础控件(如`Button`、`TextBox`)、布局控件(如`StackPanel`、`Grid`)、数据绑定控件(如`ListBox`、`DataGrid`)等。本文将介绍这些控件的基本分类及使用技巧,并通过示例代码展示如何在项目中应用。合理选择控件并利用布局控件和数据绑定功能,可以提升用户体验和程序性能。
91 0
|
4月前
|
开发框架 前端开发 JavaScript
WPF应用开发之控件动态内容展示
WPF应用开发之控件动态内容展示
|
4月前
|
C#
WPF 自定义可拖动标题栏
WPF 自定义可拖动标题栏
57 0
|
4月前
|
前端开发 C#
wpfui:一个开源免费具有现代化设计趋势的WPF控件库
wpfui:一个开源免费具有现代化设计趋势的WPF控件库
172 0
|
4月前
|
开发框架 前端开发 C#
使用WPF开发自定义用户控件,以及实现相关自定义事件的处理
使用WPF开发自定义用户控件,以及实现相关自定义事件的处理