WPF Template模版之尋找失落的控件【三】


“井水不犯河水”常用來形容兩個組織之間界限分明、互不相干,LogicTree與控件內部這顆小樹之間就保持着這種關系。換句話說,如果UI元素樹上有個X:Name=“TextBox1”的控件,某個控件內部也是由Template生成的x:Name="TextBox1"的控件,它們並不沖突,LogicTree不會看到控件內部的細節,控件內部元素也不會去理會控件外面是什么值。你可能會想:“這樣一來,萬一我想從控件外部訪問內部的控件,獲取它的屬性值,豈不是做不到了。”放心,WPF為我們准備了訪問控件內部小世界的入口,現在我們就開始出發尋找那些失落的控件。

    由ControlTemplate和DataTemplate生成的控件都是“由Template生成的控件”。ControlTemplate和DataTemplate兩個類均派生自FrameWorkTemplate類,這個類有個名為FindName的方法供我們檢索其內部控件。也就是說,只要我們能拿到Template,找到其內部控件就不成問題。對於ControlTemplate對象,訪問其目標控件的Template屬性就可以拿到,但想拿到DataTemplate就要費一番周折了。千萬不要以為ListBoxItem或者ComBoxItem容器就是DataTemplate的目標控件哦!因為控件的Template和ContentTemplate完全是兩碼事。

 

    我們先來尋找由ControlTemplate生成的控件。首先設計一個ControlTemplate並把它應用在一個UserControl控件上。界面上還有一個Button,在它的Click事件處理器中我們檢索ControlTemplate生成的代碼。

程序的XAML代碼如下:

 

[html]  view plain  copy
 
 print?
  1. <Window x:Class="WpfApplication11.wnd11431"  
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.         Title="wnd11431" Height="174" Width="300">  
  5.     <Window.Resources>  
  6.         <ControlTemplate x:Key="cTemp">  
  7.             <StackPanel>  
  8.                 <TextBox x:Name="txtBox1" BorderBrush="Black" Margin="5"></TextBox>  
  9.                 <TextBox x:Name="txtBox2" BorderBrush="Black" Margin="5"></TextBox>  
  10.                 <TextBox x:Name="txtBox3" BorderBrush="Black" Margin="5"></TextBox>  
  11.             </StackPanel>  
  12.         </ControlTemplate>  
  13.     </Window.Resources>  
  14.     <StackPanel>  
  15.         <UserControl x:Name="uc" Template="{StaticResource cTemp}" Margin="5"></UserControl>  
  16.         <Button Content="Find By Name" Width="200" Click="Button_Click"></Button>  
  17.     </StackPanel>  
  18. </Window>  

 

Button的事件處理器代碼如下:

 

[csharp]  view plain  copy
 
 print?
  1. private void Button_Click(object sender, RoutedEventArgs e)  
  2. {  
  3.     TextBox tb = uc.Template.FindName("txtBox1", uc) as TextBox;  
  4.     tb.Text = "TextBox1";  
  5.   
  6.     StackPanel sp = tb.Parent as StackPanel;  
  7.     (sp.Children[1] as TextBox).Text = "TextBox2";  
  8.     (sp.Children[2] as TextBox).Text = "TextBox3";  
  9. }  


 

    接下來我們來尋找由DataTemplate生成的控件。不過在正式尋找之前,我們先思考一個問題:尋找到一個由DataTemplate生成的控件之后,我們想從中獲取哪些數據,如果想單純獲取與用戶界面相關的數據(比如控件的高度、寬度等),這么做是正確的。但是如果是想獲取與業務邏輯相關的數據,那就要考慮是不是程序的設計出了問題------因為WPF采用的是數據驅動UI邏輯,獲取業務邏輯數據在底層就能做到,一般不會跑到表層來找。

    DataTemplate最常用的地方就是GridViewColumn的CellTemplate屬性。把GridViewColumn放置在一個GridView控件里就可以生成表格了。GridViewColumn的默認CellTemplate是使用TextBlock只讀屬性顯示數據,如果我們想讓用戶能修改數據或者使用CheckBox顯示bool類型的數據的話就需要自定義DataTemplate了。

先定義Student的類:

 

[csharp]  view plain  copy
 
 print?
  1. /// <summary>  
  2. /// 數據結構  
  3. /// </summary>  
  4. public class Student  
  5. {  
  6.     public int Id { get; set; }  
  7.     public string Name { get; set; }  
  8.     public string Skill { get; set; }  
  9.     public bool HasJob { get; set; }  
  10. }  
准備數據集合,呈現數據的工作全部由XAML代碼來完成,為顯示姓名的TextBox添加GetFocus事件處理器:
[html]  view plain  copy
 
 print?
  1. <Window x:Class="WpfApplication11.wnd11432"  
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.         xmlns:local ="clr-namespace:WpfApplication11"  
  5.         xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"  
  6.         Title="wnd11432" Height="250" Width="350">  
  7.     <Window.Resources>  
  8.         <!--數據集合-->  
  9.         <c:ArrayList x:Key="stuList">  
  10.             <local:Student Id="1" Name="Jack" Skill="C++" HasJob="true"></local:Student>  
  11.             <local:Student Id="2" Name="Tom" Skill="C#/Java" HasJob="true"></local:Student>  
  12.             <local:Student Id="3" Name="Super" Skill="SQL" HasJob="false"></local:Student>  
  13.             <local:Student Id="4" Name="Mac" Skill="WPF" HasJob="false"></local:Student>  
  14.             <local:Student Id="5" Name="Thinker" Skill="Writing" HasJob="true"></local:Student>  
  15.             <local:Student Id="6" Name="Pad" Skill="ASP.NET" HasJob="true"></local:Student>  
  16.         </c:ArrayList>  
  17.         <!--數據模版-->  
  18.         <DataTemplate x:Key="nameDT">  
  19.             <TextBox x:Name="txtBoxName" Width="100" Text="{Binding Name}" GotFocus="txtBoxName_GotFocus"></TextBox>  
  20.         </DataTemplate>  
  21.         <DataTemplate x:Key="skillDT">  
  22.             <TextBox x:Name="txtBoxSkill" Width="100" Text="{Binding Skill}"></TextBox>  
  23.         </DataTemplate>  
  24.         <DataTemplate x:Key="hasJobDT">  
  25.             <CheckBox x:Name="chBoxhasJob" Width="30" IsChecked="{Binding HasJob}"></CheckBox>  
  26.         </DataTemplate>  
  27.     </Window.Resources>  
  28.     <StackPanel Margin="5" Background="LightSlateGray">  
  29.         <ListView x:Name="listView" ItemsSource="{StaticResource stuList}">  
  30.             <ListView.View>  
  31.                 <GridView>  
  32.                     <GridView.Columns>  
  33.                         <GridViewColumn Header="Id" Width="30" DisplayMemberBinding="{Binding Id}"></GridViewColumn>  
  34.                         <GridViewColumn Header="Name"  CellTemplate="{StaticResource nameDT}"></GridViewColumn>  
  35.                         <GridViewColumn Header="Skill" CellTemplate="{StaticResource skillDT}"></GridViewColumn>  
  36.                         <GridViewColumn Header="HasJob" CellTemplate="{StaticResource hasJobDT}"></GridViewColumn>  
  37.                     </GridView.Columns>  
  38.                 </GridView>  
  39.             </ListView.View>  
  40.         </ListView>  
  41.     </StackPanel>  
  42. </Window>  
    因為我們是在DataTemplate里面添加了事件處理器,所以界面上任何一個由此DataTemplate生成的TextBox都會在獲得焦點的時候調用txtBoxName_GotFocus這個事件處理器。txtBoxName_GotFocus的代碼如下:

 

 

[csharp]  view plain  copy
 
 print?
  1. private void txtBoxName_GotFocus(object sender, RoutedEventArgs e)  
  2. {  
  3.     // 業務邏輯數據  
  4.     TextBox tb = e.OriginalSource as TextBox;  
  5.     ContentPresenter cp = tb.TemplatedParent as ContentPresenter; // 數據模版目標控件  
  6.     Student stu = cp.Content as Student;  
  7.     listView.SelectedItem = stu; // 設置選中項  
  8.   
  9.     // 訪問界面元素  
  10.     ListViewItem lvi = listView.ItemContainerGenerator.ContainerFromItem(stu) as ListViewItem; // 條目數據包裝  
  11.     CheckBox chb = FindVisualChild<CheckBox>(lvi);  
  12.     if(chb != null)  
  13.     {  
  14.         MessageBox.Show(string.Format("ChectBox IsChecked: {0}", chb.IsChecked.ToString()));  
  15.     }  
  16. }  
  17.   
  18. private ChildType FindVisualChild<ChildType>(DependencyObject obj)  
  19.         where ChildType:DependencyObject  
  20. {  
  21.     for(int i=0; i<VisualTreeHelper.GetChildrenCount(obj); i++)  
  22.     {  
  23.         DependencyObject child = VisualTreeHelper.GetChild(obj, i);  
  24.         if(child != null && child is ChildType)  
  25.         {  
  26.             return (child as ChildType);  
  27.         }  
  28.         else  
  29.         {  
  30.             ChildType childOfChild = FindVisualChild<ChildType>(child);  
  31.             if (childOfChild != null)  
  32.                 return (childOfChild);  
  33.         }  
  34.     }  
  35.   
  36.     return (null);  
  37. }  
    當使用GridView作為ListView的View屬性時,如果某一列使用TextBox作為CellTemplate,那么即使這列中的TextBox被鼠標單擊並獲得了焦點ListView也不會把此項做為自己的SelectedItem。所以,txtBoxName_GotFocus的前半部分是獲得數據的源頭(TextBox),然后沿UI元素樹上朔到DataTemplate目標控件(ContentPresenter)並獲取它的內容,它的內容一定是一個Student實例。
    txtBoxName_GotFocus的后半部分則借助 VisualTreeHelper類檢索由DataTemplate生成的控件。前面說過,每個ItemsControl的派生類(如ListBox,ComBox,ListView)都具有自己獨特的條目容器,本例中是一個包裝着Student對象的ListViewItem(注意,此ListViewItem對象的Content也是Student對象)。可以把這個ListViewItem控件視為一顆樹的根,使用VisualTreeHelper類就可以遍歷它的各個節點。本例中是吧遍歷算法分裝在了FindVisualChild泛型方法里。
運行程序,並單擊某個顯示姓名的TextBox,效果如下圖所示:

 



    由本例可以看出,無論是從事件源頭“自下而上”的找,還是使用ItemContainerGenerator.ContainerFromItem方法找到條目容器再“自上而下”的找,總之,找到業務邏輯數據(Student實例)並不難,而工作中大多是操作業務邏輯數據。如果真的想找由DataTemplate生成的控件,對於結構簡單的控件,可以使用DataTemplate對象的FindName方法;對於結構復雜的控件,則需要借助VisualTreeHelper來實現。

可以使用WPF Inspector查看VisualTree或LogicTree細節:

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM