none
Bind Binding's path... RRS feed

  • 问题

  •  问题又来了.

    如何将 一个 Binding 的 Path 再次 绑定(Bind) 到其他的对象的一个属性值, 这些对象及其属性是运行时得到的, 并非一个常量字串, 而这个 Binding 包含在 DataTemplate 中, 在设计时就写入 xaml 档案中.

    例如一个 ListView,
    运行时, 它的 Items 由查询数据库后的 DataView 提供. (ItemsSource = DataView).
    ListView 的每一行是一个 DataViewRow.   每一个 Cell 是 DataViewRow 的 一个 Item.
    得到此 Cell 的内容的索引应该是 数值或者数据表的字段名.
    在给 ItemsSource 置值前, 由 自定的 GenerateColumnHeader() 产生一些 GridViewColumn 作为 ListView 的 View 的 Columns 的 项.
    当 这个 GridViewColumn 不指定 DisplayMemberBinding 而指定 CellTemplate 时, 这个 CellTemplate 的一个 TextBlock 如何显示 DataRowView.Item(GridViewColumn.Header.ToString()) 的值:

    在设计时就写好的 DataTemplate 作为 CellTemplate 属性值:
    <DataTemplate x:Key="myCellTemplateForType1">
        <ProgressBar />
        <TextBlock Text="{Binding Path=???}"/>
    </DataTemplate>


    简单的描述:
      GridViewColumn.CellTemplate = App.Current.Resources("myCellTemplateForType1")
         当 GridViewColumn.Header = "mfc" 时                            (运行时)
            TextBlock 的 Text 的 Binding 是 {Binding Path=mfc}  (设计时)
        当 GridViewColumn.Header = "kfc" 时                             (运行时)
            TextBlock 的 Text 的 Binding 是 {Binding Path=kfc}   (设计时)
    因为 Path 是不固定的, 所以应该如何写上面的 xaml??

    { Binding Path={Binding Path=GridViewColumn.Header} }

    Hello, everyone
    2009年2月4日 20:17

答案

  • 不好意思,我原先的回复没有注意看,是的,WPF对于GridViewColumn使用Binding的优先级从高到底分别是:
    1. DisplayMemberBinding
    2. CellTemplate
    3. CellTemplateSelector

    你的问题我试过了,我没有找到通过XAML解决的办法,因为GridViewColumn和ListView里面的ListViewItem没有层次关系,所以没有办法在ListViewItem里面找到当前GridViewColumn的Header。

    我暂时能想到的方法是使用代码的方式创建DataTemplate::(

    C#代码

        /// <summary>  
        /// Interaction logic for Window1.xaml  
        /// </summary>  
        public partial class Window1 : Window  
        {  
            public Window1()  
            {  
                InitializeComponent();  
     
                Loaded += new RoutedEventHandler(Window1_Loaded);  
            }  
     
            void Window1_Loaded(object sender, RoutedEventArgs e)  
            {  
                People people = new People();  
                people.Add(new Person("Person 1"falsenew DateTime(1980, 1, 30)));  
                people.Add(new Person("Person 2"falsenew DateTime(1980, 1, 30)));  
                people.Add(new Person("Person 3"truenew DateTime(1982, 1, 30)));  
                people.Add(new Person("Person 4"truenew DateTime(1980, 4, 30)));  
                people.Add(new Person("Person 5"truenew DateTime(1980, 1, 3)));  
                people.Add(new Person("Person 6"falsenew DateTime(1977, 11, 10)));  
                people.Add(new Person("Person 7"truenew DateTime(1987, 12, 30)));  
                people.Add(new Person("Person 8"falsenew DateTime(1980, 1, 1)));  
                people.Add(new Person("Person 9"falsenew DateTime(1981, 2, 28)));  
                people.Add(new Person("Person 10"falsenew DateTime(1983, 10, 30)));  
     
                TestDataGrid.ItemsSource = people;  
     
                GridViewColumn column1 = ((GridView)TestDataGrid.View).Columns[0];  
                GridViewColumn column2 = ((GridView)TestDataGrid.View).Columns[1];  
                GridViewColumn column3 = ((GridView)TestDataGrid.View).Columns[2];  
                column1.CellTemplate = CreateTemplate("Name");  
                column2.CellTemplate = CreateTemplate("Gender");  
                column3.CellTemplate = CreateTemplate("Birthday");  
            }  
     
            private DataTemplate CreateTemplate(string column)  
            {  
                DataTemplate template = new DataTemplate();  
     
                FrameworkElementFactory factoryText =  
                                    new FrameworkElementFactory(typeof(TextBox));  
                factoryText.SetValue(TextBox.TextProperty, new Binding(column));  
                template.VisualTree = factoryText;  
     
                return template;  
            }  
        }  
     
        public class Person  
        {  
            public string Name { getset; }  
     
            public bool Gender { getset; }  
     
            public DateTime Birthday { getset; }  
     
            public Person(string name, bool gender, DateTime birthday)  
            {  
                Name = name;  
                Gender = gender;  
                Birthday = birthday;  
            }  
        }  
     
        public class People : ObservableCollection<Person>  
        {  
        } 

    XAML代码:
            <ListView x:Name="TestDataGrid">  
                <ListView.View> 
                    <GridView> 
                        <GridViewColumn Header="Name" /> 
                        <GridViewColumn Header="Gender" /> 
                        <GridViewColumn Header="Birthday"/>  
                    </GridView> 
                </ListView.View> 
            </ListView> 

    如果你需要给DataTemplate里面的控件设置Style或者Template的话,可以通过
    factoryText.SetValue(..., ....);  
    来实现
    • 已标记为答案 Yeshirow 2009年2月8日 13:04
    2009年2月7日 15:49

全部回复

  • 你需要使用Binding类的RelativeSource来达到你的目的。

     Xaml Code:
    <Window x:Class="Test.Window1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="Window1" Height="300" Width="300" 
        Loaded="Window_Loaded">  
        <Window.Resources> 
            <DataTemplate x:Key="ForumQuestion">  
                <TextBox Width="200" Text="{Binding Path=Header, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumn}}}" /> 
            </DataTemplate> 
        </Window.Resources> 
          
        <StackPanel Orientation="Vertical">  
            <ListView x:Name="TestDataGrid">  
                <ListView.View> 
                    <GridView> 
                        <GridViewColumn Header="Name"   
                                        DisplayMemberBinding="{Binding Path=Name}" 
                                        CellTemplate="{StaticResource ForumQuestion}"/>  
                        <GridViewColumn Header="Gender"                                
                                        DisplayMemberBinding="{Binding Path=Gender}" 
                                        CellTemplate="{StaticResource ForumQuestion}"/>  
                        <GridViewColumn Header="Birthday"                              
                                        DisplayMemberBinding="{Binding Path=Birthday}" 
                                        CellTemplate="{StaticResource ForumQuestion}"/>  
                    </GridView> 
                </ListView.View> 
            </ListView> 
        </StackPanel> 
    </Window> 

    C# Code:
            private void Window_Loaded(object sender, RoutedEventArgs e)     
            {     
                People people = new People();     
                people.Add(new Person("Person 1"falsenew DateTime(1980, 1, 30)));     
                people.Add(new Person("Person 2"falsenew DateTime(1980, 1, 30)));     
                people.Add(new Person("Person 3"truenew DateTime(1982, 1, 30)));     
                people.Add(new Person("Person 4"truenew DateTime(1980, 4, 30)));     
                people.Add(new Person("Person 5"truenew DateTime(1980, 1, 3)));     
                people.Add(new Person("Person 6"falsenew DateTime(1977, 11, 10)));     
                people.Add(new Person("Person 7"truenew DateTime(1987, 12, 30)));     
                people.Add(new Person("Person 8"falsenew DateTime(1980, 1, 1)));     
                people.Add(new Person("Person 9"falsenew DateTime(1981, 2, 28)));     
                people.Add(new Person("Person 10"falsenew DateTime(1983, 10, 30)));     
        
                TestDataGrid.ItemsSource = people;     
            }     
        
        
        public class Person     
        {     
            public string Name { getset; }     
        
            public bool Gender { getset; }     
        
            public DateTime Birthday { getset; }     
        
            public Person(string name, bool gender, DateTime birthday)     
            {     
                Name = name;     
                Gender = gender;     
                Birthday = birthday;     
            }     
        }     
        
        public class People : ObservableCollection<Person>    
        {     
        }    
     

    另外,你可以尝试一下WPF的DataGrid控件,WPF DataGrid在08年10月份左右发布,到下面这个地方下载:
    http://www.codeplex.com/Project/Download/FileDownload.aspx?ProjectName=wpf&DownloadId=56783
    • 已标记为答案 Yeshirow 2009年2月6日 16:19
    • 取消答案标记 Yeshirow 2009年2月6日 20:17
    2009年2月6日 9:04
  • DataGrid 已经使用过了, 但它的 性能 好像不如 ListView (尤其是 当 滚动显示 时 的操作, 或许我的设置不正确), 即使其内容是 ReadOnly 的.

    不过它提供的 各种 Column 类型的确不错, 很容易操作.

    ' FindAncestor, AncestorType


    sorry, 照搬了 DataTemplate, 所以产生了错觉.
    事实上, 设置了 DisplayMemberBinding 则 Cell 的内容会优先应用 DisplayMemberBinding 而忽略了 CellTemplate 属性.
    你给出的应用, 并不应用到 DataTemplate, 我已经新建一个项目, 并且将你的示例进行了测试.
    它的确显示了正确的数据, 但承载数据的并非 TextBox, 我稍作修改后(仅加了一个 Foreground 属性):
    ...
    <
    Window.Resources> 
        <DataTemplate x:Key="ForumQuestion">  
        <TextBox Foreground="Magenta" Width="200" Text="{Binding Path=Header, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumn}}}" /> 
        </DataTemplate>
    ...
    可以看出, 它忽略了 ForumQuestion 这个 DataTemplate, 因为显示的字体的颜色并没有发生改变.


    Hello, everyone
    • 已编辑 Yeshirow 2009年2月6日 20:33
    2009年2月6日 16:19
  • 若删除 DisplayMemberBinding 属性, 则可以显示 TextBox, 但是 TextBox 的 Text 没有任何值.
    Hello, everyone
    2009年2月6日 20:31
  •   或许问题描述得不够准确, 我再详细的说明一下:

    目标:
    使用 ListView  的 GridView 显示一张动态得到的 2D 数据表.

    要求:
    如果表 (Table1) 结构如下面的:
          Field1_ID         Field2_String          Field3_Number
                       1                          ab                              50
                       2                          ac                            40.5
                      ...                           ...                             .....
    则 ListView 显示下面的样式(模拟):
    Field1_ID          Field2_String        Field3_Number
         1                          ab                          50           
         2                          ac                         40.5
              

    后面一列为 Grid, Grid 承载一个 ProgressBar 和 一个 TextBlock.

    TextBlock 的 Text 为 Table1 对应行的 对应列 的值.
    问题:
    如果 表的字段总是 固定的, 则 DataTemplate 可以是如下的:
        <DataTemplate x:Key="myField3Template">
            <Grid MinWidth="100">
              <ProgressBar Maximum="100" Value="{Binding Path=Field3_Number, Converter=xxx}" />
              <TextBlock Text="{Binding Path=Field3_Number}" />
           </Grid>
        </DataTemplate>
    但是 Field3_Number 并非固定的, 因为 2D 数据表并非只有一张, 并且字段不是固定的, 当呈现表的内容时, 如果某列的类型为数值, 就要应用此 Template 作为 GridViewColumn 的 CellTemplate.
    所以如何针对不同的表, 有条件的(条件是数值类型) 应用此 DataTemplate?
    上面的 Path=Field3_Number, 应该写成通用的.
    Hello, everyone
    2009年2月7日 11:30
  • 不好意思,我原先的回复没有注意看,是的,WPF对于GridViewColumn使用Binding的优先级从高到底分别是:
    1. DisplayMemberBinding
    2. CellTemplate
    3. CellTemplateSelector

    你的问题我试过了,我没有找到通过XAML解决的办法,因为GridViewColumn和ListView里面的ListViewItem没有层次关系,所以没有办法在ListViewItem里面找到当前GridViewColumn的Header。

    我暂时能想到的方法是使用代码的方式创建DataTemplate::(

    C#代码

        /// <summary>  
        /// Interaction logic for Window1.xaml  
        /// </summary>  
        public partial class Window1 : Window  
        {  
            public Window1()  
            {  
                InitializeComponent();  
     
                Loaded += new RoutedEventHandler(Window1_Loaded);  
            }  
     
            void Window1_Loaded(object sender, RoutedEventArgs e)  
            {  
                People people = new People();  
                people.Add(new Person("Person 1"falsenew DateTime(1980, 1, 30)));  
                people.Add(new Person("Person 2"falsenew DateTime(1980, 1, 30)));  
                people.Add(new Person("Person 3"truenew DateTime(1982, 1, 30)));  
                people.Add(new Person("Person 4"truenew DateTime(1980, 4, 30)));  
                people.Add(new Person("Person 5"truenew DateTime(1980, 1, 3)));  
                people.Add(new Person("Person 6"falsenew DateTime(1977, 11, 10)));  
                people.Add(new Person("Person 7"truenew DateTime(1987, 12, 30)));  
                people.Add(new Person("Person 8"falsenew DateTime(1980, 1, 1)));  
                people.Add(new Person("Person 9"falsenew DateTime(1981, 2, 28)));  
                people.Add(new Person("Person 10"falsenew DateTime(1983, 10, 30)));  
     
                TestDataGrid.ItemsSource = people;  
     
                GridViewColumn column1 = ((GridView)TestDataGrid.View).Columns[0];  
                GridViewColumn column2 = ((GridView)TestDataGrid.View).Columns[1];  
                GridViewColumn column3 = ((GridView)TestDataGrid.View).Columns[2];  
                column1.CellTemplate = CreateTemplate("Name");  
                column2.CellTemplate = CreateTemplate("Gender");  
                column3.CellTemplate = CreateTemplate("Birthday");  
            }  
     
            private DataTemplate CreateTemplate(string column)  
            {  
                DataTemplate template = new DataTemplate();  
     
                FrameworkElementFactory factoryText =  
                                    new FrameworkElementFactory(typeof(TextBox));  
                factoryText.SetValue(TextBox.TextProperty, new Binding(column));  
                template.VisualTree = factoryText;  
     
                return template;  
            }  
        }  
     
        public class Person  
        {  
            public string Name { getset; }  
     
            public bool Gender { getset; }  
     
            public DateTime Birthday { getset; }  
     
            public Person(string name, bool gender, DateTime birthday)  
            {  
                Name = name;  
                Gender = gender;  
                Birthday = birthday;  
            }  
        }  
     
        public class People : ObservableCollection<Person>  
        {  
        } 

    XAML代码:
            <ListView x:Name="TestDataGrid">  
                <ListView.View> 
                    <GridView> 
                        <GridViewColumn Header="Name" /> 
                        <GridViewColumn Header="Gender" /> 
                        <GridViewColumn Header="Birthday"/>  
                    </GridView> 
                </ListView.View> 
            </ListView> 

    如果你需要给DataTemplate里面的控件设置Style或者Template的话,可以通过
    factoryText.SetValue(..., ....);  
    来实现
    • 已标记为答案 Yeshirow 2009年2月8日 13:04
    2009年2月7日 15:49
  •   不好意思,我原先的回复没有注意看,是的,WPF对于GridViewColumn使用Binding的优先级从高到底分别是:
    1. DisplayMemberBinding
    2. CellTemplate
    3. CellTemplateSelector

    你的问题我试过了,我没有找到通过XAML解决的办法,因为GridViewColumn和ListView里面的ListViewItem没有层次关系,所以没有办法在ListViewItem里面找到当前GridViewColumn的Header。

    我暂时能想到的方法是使用代码的方式创建DataTemplate::(

    C#代码

        /// <summary>  
        /// Interaction logic for Window1.xaml  
        /// </summary>  
        public partial class Window1 : Window  
        {  
            public Window1()  
            {  
                InitializeComponent();  
     
                Loaded += new RoutedEventHandler(Window1_Loaded);  
            }  
     
            void Window1_Loaded(object sender, RoutedEventArgs e)  
            {  
                People people = new People();  
                people.Add(new Person("Person 1"falsenew DateTime(1980, 1, 30)));  
                people.Add(new Person("Person 2"falsenew DateTime(1980, 1, 30)));  
                people.Add(new Person("Person 3"truenew DateTime(1982, 1, 30)));  
                people.Add(new Person("Person 4"truenew DateTime(1980, 4, 30)));  
                people.Add(new Person("Person 5"truenew DateTime(1980, 1, 3)));  
                people.Add(new Person("Person 6"falsenew DateTime(1977, 11, 10)));  
                people.Add(new Person("Person 7"truenew DateTime(1987, 12, 30)));  
                people.Add(new Person("Person 8"falsenew DateTime(1980, 1, 1)));  
                people.Add(new Person("Person 9"falsenew DateTime(1981, 2, 28)));  
                people.Add(new Person("Person 10"falsenew DateTime(1983, 10, 30)));  
     
                TestDataGrid.ItemsSource = people;  
     
                GridViewColumn column1 = ((GridView)TestDataGrid.View).Columns[0];  
                GridViewColumn column2 = ((GridView)TestDataGrid.View).Columns[1];  
                GridViewColumn column3 = ((GridView)TestDataGrid.View).Columns[2];  
                column1.CellTemplate = CreateTemplate("Name");  
                column2.CellTemplate = CreateTemplate("Gender");  
                column3.CellTemplate = CreateTemplate("Birthday");  
            }  
     
            private DataTemplate CreateTemplate(string column)  
            {  
                DataTemplate template = new DataTemplate();  
     
                FrameworkElementFactory factoryText =  
                                    new FrameworkElementFactory(typeof(TextBox));  
                factoryText.SetValue(TextBox.TextProperty, new Binding(column));  
                template.VisualTree = factoryText;  
     
                return template;  
            }  
        }  
     
        public class Person  
        {  
            public string Name { getset; }  
     
            public bool Gender { getset; }  
     
            public DateTime Birthday { getset; }  
     
            public Person(string name, bool gender, DateTime birthday)  
            {  
                Name = name;  
                Gender = gender;  
                Birthday = birthday;  
            }  
        }  
     
        public class People : ObservableCollection<Person>  
        {  
        } 

    XAML代码:
            <ListView x:Name="TestDataGrid">  
                <ListView.View> 
                    <GridView> 
                        <GridViewColumn Header="Name" /> 
                        <GridViewColumn Header="Gender" /> 
                        <GridViewColumn Header="Birthday"/>  
                    </GridView> 
                </ListView.View> 
            </ListView> 

    如果你需要给DataTemplate里面的控件设置Style或者Template的话,可以通过
    factoryText.SetValue(..., ....);  
    来实现
    2009年2月7日 15:51
  • (http://msdn.microsoft.com/en-us/library/system.windows.frameworkelementfactory.aspx)

    因为在 MSDN 中提到, 在代码中构造 DataTemplate 的方式已被否决 (Remark 部分). 所以 在标记为可用答案之前, 我想再问一些问题. 就是, 如果使用 DisplayMemberBinding 时, Cell 中 承载数据的 的元素 是什么?

    是 ContentPresenter?

    如果是, 这个 ContentPresenter 是如何得到它的 Content 的 (该 ContentPresenter 和 DisplayMemberBinding 及 GridViewColumn 之间的关系与联系)?

    在我上面的问题中, 如果是使用 DisplayMemberBinding 来显示 DataView 表, 则:

    对于 ListView 的每一行 (Row),

      它是 GridViewRowPresenter? GridViewRowPresenter 的 DataContext 是 DataRowView?

    对于 ListView 的每个列 ( Cell),

      它是 ContentPresenter? 它内部是如何对 Content 的进行 Binding 的呢?

     

    ???????????????????????????????????????????????????????????????????

    ListView       ------------------------------ Items: DataView

      GridView

            Columns(GridViewColumn)...

            GridViewRowPresenter 1  ---------- DataRowView (DataView.Rows(0))

                    ContentPresenter1

                            Content                     ---------- Item (DataView.Row(0).Item(0))

                    ContentPresenter2

                            Content                     ---------- Item (DataView.Rows(0).Item(1))

                    ...

            GridViewRowPresenter 2  ----------- DataRowView (DataView.Rows(1))

     ...

    ??????????????????????????????????????????????????????????????????

    它呈现数据是不是这个样子的呢? 它们的层次关系好像有点复杂. (Content 的索引器是怎么知道的?)

     


    Hello, everyone
    2009年2月7日 19:03
  • 对于ListView内部的构造,我的建议是你使用XamlPad自己看一下,例如下面的代码在XamlPad里面看ListView的结构时,Cell里面承载元素的实际上是TextBlock,而ListView的每一行是ListViewItem(里面通过GridViewRowPresenter)来显示每一行。GridViewRowPresenter.DataContext是XmlElement,对于ListView的每一列(Cell),因为承载数据的是TextBlock控件,所以只要搬定XmlElement到TextBlock.Text上面,通过指定XPath就可以解决了。

    <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > 
      <Page.Resources> 
     
        <!--Define data for ListView--> 
        <XmlDataProvider x:Key="MyData" XPath="/Info"
          <x:XData> 
            <Info xmlns=""
              <Song Name="Song 1" Time="3:54" Artist="Artist 1" Level="3"  
                    Disk="Disk 2"/> 
              <Song Name="Song 2" Time="4:31" Artist="Artist 2" Level="4"  
                    Disk="Disk 1" /> 
              <Song Name="Song 3" Time="4:31" Artist="Artist 1" Level="2"  
                    Disk="Disk 1" /> 
     
            </Info> 
          </x:XData> 
        </XmlDataProvider> 
     
        <DataTemplate x:Key="ForumQuestion"
           <TextBox Width="200" Text="{Binding Path=Name, Mode=OneWay}" /> 
        </DataTemplate> 
      </Page.Resources> 
     
      <StackPanel> 
            <ListView x:Name="TestDataGrid" ItemsSource="{Binding Source={StaticResource MyData}, XPath=Song}"
                <ListView.View> 
                    <GridView> 
                        <GridViewColumn Header="Name"     
                                        DisplayMemberBinding="{Binding XPath=@Name}" /> 
                                        <!-- CellTemplate="{StaticResource ForumQuestion}"/>  --> 
                        <GridViewColumn Header="Time"                  
                                        DisplayMemberBinding="{Binding XPath=@Time}" />          
                                        <!-- CellTemplate="{StaticResource ForumQuestion}"/>  --> 
                        <GridViewColumn Header="Artist"                
                                        DisplayMemberBinding="{Binding XPath=@Artist}" />       
                                        <!-- CellTemplate="{StaticResource ForumQuestion}"/>  --> 
                    </GridView> 
                </ListView.View> 
            </ListView> 
      </StackPanel> 
     
    </Page> 


    对于你原先的问题,之所以我没有找到Xaml的解决方案,是因为我没有找到办法将Binding.Path设置成另外一个Binding,即下面的代码在XamlParser里面是非法的:

    <TextBlock Text="{Binding Path={Binding Path=DataContext.ColumnName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewRowPresenter}}}}" />

    :(

    另外,在调试DataBinding的错误的时候,你可以在Visual Studio 2008的Output窗口察看DataBinding输出的错误调试信息。
    2009年2月8日 11:41
  • 看来除了在代码中产生 DataTemplate 外, 目前已经没有更好的办法了.

    ListView 呈现数据的元素, 现在还有点搞不清它们之间的关系, 会慢慢研究下.

    感谢 Killmyday.    :)
    Hello, everyone
    2009年2月8日 13:07