zoukankan      html  css  js  c++  java
  • 《Programming WPF》翻译 第4章 4.数据源

    目前为止,我们已经简单的处理了对象。然而,这并不是数据的唯一来源;XML和突然想到的相关数据库,都是流行的选择。更进一步地,由于XML

    相关数据库并不能存储数据为.NET对象,某些转换可能需要支持数据绑定,正如你会想到的,需要数据源对象上的.NET属性。而且即使我们可以直接在xaml中声明对象,仍然希望有一个层间接地从其他源中拉数据,甚至于将这个工作交给一个工作线程,如果说取回是一个呆板的操作。

    简而言之,为了对象的转换和加载,我们希望间接的而不是直接的声明方式。对于这个间接方式,我们必须致力于IDataSource接口的实现,其中一种就是数据对象源。

    4.4.1数据对象源

    一种对IDataSource接口的实现是,为所有的操作提供一个间接的层,这些操作用于生成要绑定到的对象。例如,如果我们想要在Web上加载一组Person对象,我们需增强一些代码中的逻辑,如示例4-34

    示例4-34

    namespace PersonBinding {
      
    public class Person : INotifyPropertyChanged {}
      
    public class People : ObservableCollection<Person> {}

      
    public class RemotePeopleLoader : People {
        
    public RemotePeopleLoader(  ) {
          
    // Load people from afar
          
        }

      }

    }

    在示例4-34中,RemotePeopleLoader类从People集合类中派生,在构造器中检索数据,因为对象数据源希望它创建的对象是一个集合,正如示例4-35

    示例4-35

    <Window.Resources>
      
      
    <ObjectDataSource
        x:Key
    ="Family"
        TypeName
    ="PersonBinding.RemotePeopleLoader"
        Asynchronous
    ="True" />
    </Window.Resources>
    <Grid DataContext="{StaticResource Family}">
      
      
    <ListBox ItemsSource="{Binding}" >
    </Grid>

    ObjectDataSource元素通常位于资源块中,按名称在xaml的其他位置中使用。TypeName属性引用了集合类的完整的限定名称。

    WPF中的大部分具有type参数的类,如DataTemplate元素的DataType属性,在设置中带上type扩展标记,这包括类,命名空间和使用mapping语法的编译集信息。

    <!-- set up DataTemplate for Bar.Quux in assembly foo -->
    <?Mapping
      XmlNamespace="local"
      ClrNamespace="Bar"  Assembly="foo" /><Window  xmlns="local">
      <Window.Resources>
        <DataTemplate
          DataType="{x:Type local:Quux}"></DataTemplate>
      </Window.Resources>
      
    </Window>

    然而,ObjectDataSource以自己的方式设置type的信息。

    <!-- set up ObjectDataSource for Bar.Quux in assembly foo -->
    <ObjectDataSource x:Key="foo" TypeName="Bar.Quux, foo" />

    愿望是美好的,现实是残酷的。在RTM版本之前,两种技术都是合理的。

    伴随着对象数据源担当数据和绑定之间的中介者,我们需要更新代码,当我们遍历People集合时(现在是一个基本类RemotePeopleLoader,但是仍然是Person对象的容器),正如示例4-36所示。

    示例4-36

    public partial class Window1 : Window {
      
      ICollectionView GetFamilyView(  ) 
    {
        IDataSource 
     ds 
    = (IDataSource)this.FindResource("Family");
        People people 
    = (People)ds.Data;
        
    return BindingOperations.GetDefaultView(people);
      }


      
    void birthdayButton_Click(object sender, RoutedEventArgs e) {
        ICollectionView view 
    = GetFamilyView(  );
        Person person 
    = (Person)view.CurrentItem;

        
    ++person.Age;
        MessageBox.Show();
      }


      
    void addButton_Click(object sender, RoutedEventArgs e) {
        IDataSource ds 
    = (IDataSource)this.FindResource("Family");
        People people 
    = (People)ds.Data;
        people.Add(
    new Person("Chris"35));
      }

    }

    由于Family资源现在是一个ObjectDataSource,本身就是IDataSource接口的实现,在示例4-26中,当我们需要People集合的时候,我们将Family中的资源转换为IdataSource,并从Data属性中拉出这个集合。

    即使数据源对象通过Data属性暴露他的数据,这并不意味着你必须绑定它。如果你注意到示例4-35,我们仍然像从前一样绑定了列表框。

    <!--do not bind to Path=Data -->
    <ListBox ItemSource=”{Binding}” >

    这样做的原因是WPFIDataSource提供内建的支持,因此没有必要间接地这样做。

    4.4.1.1异步数据遍历

    在示例4-35中,我们应用了Asynchronous属性,这是最有趣的一块功能:数据源对象提供给我们所欠缺的——当我们直接在xaml中声明对象图的时候。

    Asynchronous属性设置为true时(默认为false),通过TypeName创建详细对象的任务就交给工作线程处理,当遍历过数据,仅仅在UI线程表现绑定。这与绑定到数据并不一样——数据是在网络流中遍历到的,但是这总比当一个长时间的遍历发生时阻塞了UI线程要好。

    4.4.1.2传递参数

    Asynchronous属性外,数据源对象还提供了Parameters属性,这是一个逗号分隔的字符串列表,作为一个字符串传递到由数据源对象创建的类型中。例如,如果我们要传递一组URL参数,用来尝试遍历其中的数据,我们可以使用Parameters参数如示例4-37

    示例4-37

    <ObjectDataSource
      
    x:Key="Family"
      TypeName
    ="PersonBinding.RemotePeopleLoader"
      Asynchronous
    ="True"
    Parameters
    ="http://sellsbrothers.com/sons.dat, http://sellssisters.com/daughters.dat" />

    在示例4-37中,我们已经添加了一个包含两个URL的列表,这将被转换为调用RemotePeopleLoader有两个参数的构造函数,如示例4-38

    示例4-38

    namespace PersonBinding {
      
    public class RemotePeopleLoader : People {
        
    public RemotePeopleLoader(string url1, string url2) {
          
    // Load People from afar using two URLs
          
        }

    }

    不幸的是,如果我们把其他的数据类型放入由数据源对象的Property属性支持的参数列表,如整型,这将不会被转换,即使构造函数拥有适当的类型是有效的;数据源对象只支持创建带有无参或有参构造函数的对象。如果每一个数据都必须转换,你就不得不这么做了。

    4.4.2 XMLDataSource

    正如我提及的,对象是仅由数据绑定支持的,但数据究竟不仅仅存为对象。实际上,大部分数据并不存储为对象。一种日益流行的方法是把数据存储到XML。例如,示例4-39显示了我们的家庭数据,表示以XML的形式。

    示例4-39

    <!-- family.xml -->
    <Family xmlns="">
      
    <Person Name="Tom" Age="9" />
      
    <Person Name="John" Age="11" />
      
    <Person Name="Melissa" Age="36" />
    </Family>

    这个文件作为可执行应用程序,在同样的文件夹中是有效的,我们能够使用XmlDataSource绑定到它,正如示例4-40所示。

    示例4-40

    <!-- Window1.xaml -->
    <Window >
      
    <Window.Resources>
        
        
    <XmlDataSource
          
    x:Key="Family"
          Source
    ="family.xml"
          XPath
    ="/Family/Person" />
      
    </Window.Resources>
      
    <Grid DataContext="{StaticResource Family}">
        
        
    <ListBox  ItemsSource="{Binding}">
          
    <ListBox.ItemTemplate>
            
    <DataTemplate>
              
    <StackPanel Orientation="Horizontal">
                
    <TextBlock TextContent="{Binding XPath=@Name}" />
                
    <TextBlock TextContent=" (age: " />
                
    <TextBlock TextContent="{Binding XPath=@Age}"  />
                
    <TextBlock TextContent=")" />
              
    </StackPanel>
            
    </DataTemplate>
          
    </ListBox.ItemTemplate>
        
    </ListBox>

        
    <TextBlock >Name:</TextBlock>
        
    <TextBox Text="{Binding XPath=@Name}" />
        
    <TextBlock >Age:</TextBlock>
        
    <TextBox Text="{Binding XPath=@Age}"  />
        
      
    </Grid>
    </Window>

    注意到,XmlDataSource的使用,带着一个相对的URL指向family.xml文件,这个Xpath表达式在Family根元素下推出Person元素。在XAML文件中唯一改变的是,使用ObjectDataSource绑定NameAgeTextBox控件,而我们使用Xpath表达式代替了Path表达式

        *Xpath语法的说明草果了本书的范围,一个好的参考书目是,Essential XML Quick Reference by Aaron Skonnard and Martin Gudgin (Addison Wesley)

    4.4.2.1 XML数据岛

    如果你恰好在编译期知道你的数据,XML数据源也以同样的方式支持“数据岛”:由XAML直接创建对象,如示例4-41所示。

    示例4-41

    <XmlDataSource x:Key="Family" XPath="/Family/Person">
      
    <Family xmlns="">
        
    <Person Name="Tom" Age="9" />
        
    <Person Name="John" Age="11" />
        
    <Person Name="Melissa" Age="36" />
      
    </Family>
    </XmlDataSource>

    在示例4-41中,我们将XmlDataSource元素下的内容复制到了family.xml中,去掉Source属性而保留了Xpath表达式。

    尽管如此,既然我们使用XML替代了对象数据,我们示例中的操作需要改动,如访问和改变当前项(正如我们对Birthday按钮的实现),添加新项,排序和过滤。简而言之,任何我们使用Person对象集合的地方,都需要改动。另一方面,在数据项之间移动的一系列方法ICollectionView.MovingCurrentToXxx()继续工作的很好,我们的AgeToForegroundValueConverter也是这样。

    IValueConverter.Convert的实现可以继续工作,因为我们对对象的字符串值进行语法解析,而不是直接将其转换为Int32。在Person对象的情形中使用转换是首选的,因为AgeInt32类型的,对其进行语法分析是不必要的。尽管如此,在XML以及我们的应用程序缺少XSD的情形,Age是一个String类型,因此解析它就是必要的了。

    4.4.2.2 XML数据源和访问数据项

    为了访问和操作XML数据源,取代你的自定义类型实例,你可以使用位于System.Xml命名空间的XMLElement实例,正如示例4-42所示。

    示例4-42

    // Window1.xaml.cs

    namespace PersonBinding {
      
    public partial class Window1 : Window {
        

        ICollectionView GetFamilyView(  ) 
    {
          IDataSource ds 
    = (IDataSource)this.FindResource("Family");
          IEnumerable people 
    = (IEnumerable)ds.Data;
          
    return BindingOperations.GetDefaultView(people);
        }


        
    void birthdayButton_Click(object sender, RoutedEventArgs e) {
          ICollectionView view 
    = GetFamilyView(  );

          XmlElement person 
    = (XmlElement)view.CurrentItem;
          person.SetAttribute(
    "Age",
            (
    int.Parse(person.Attributes["Age"].Value) + 1).ToString(  ));
          MessageBox.Show(
            
    string.Format(
              
    "Happy Birthday, {0}, age {1}!",
              person.Attributes[
    "Name"].Value,
              person.Attributes[
    "Age"].Value),
            
    "Birthday");
        }

        
      }

    }

    在示例4-42中,首先要注意的是GetFamilyView的实现,我们不再直接寻找People集合,而是实现由Xml数据源提供的IEnumerable接口。IEnumerable.NET中你能拥有的最简单接口,仍然有一个集合——是GetdefaultView方法所需要的。

    还要注意示例4-42中集合视图的CurrentItem属性,是一个XmlElement实例。为了增加age,我们访问元素的Age属性,取出它的值,将其解析为一个整型,增加它的值,再将这个整型转换为String类型,设置为当前元素的新的Age属性值。显示每一个属性不过是对成对属性的访问。

    4.4.2.3XML数据源以及添加数据项

    当添加(或移除)一个数据项时,最好访问XmlDataSource自身,从而可以访问Document属性来创建和添加新元素,正如示例4-43

    示例4-43

    void addButton_Click(object sender, RoutedEventArgs e) {
      XmlDataSource xds 
    = (XmlDataSource)this.FindResource("Family");
      XmlElement person 
    = xds.Document.CreateElement("Person");
      person.SetAttribute(
    "Name""Chris");
      person.SetAttribute(
    "Age""35");
      xds.Document.ChildNodes[
    0].AppendChild(person);
    }

    这里,我们使用了XmlDataSource来获取XmlDocument,以及使用XmlDodument来创建一个叫做Person的新元素(使之符合其余Person元素),设置NameAge属性,以及在Family根元素下添加这个元素(在顶级Document对象上ChildNodes[0]是有效的)。

    4.4.2.4 XML数据源以及排序

    Xml数据源的条目进行排序,大概会想起我们要使用XmlElements进行处理,正如示例4-44

    示例4-44

    class PersonSorter : IComparer {
      
    public int Compare(object x, object y) {
        XmlElement lhs 
    = (XmlElement)x;
        XmlElement rhs 
    = (XmlElement)y;

        
    // Sort Name ascending and Age descending
        int nameCompare =
          lhs.Attributes[
    "Name"].Value.CompareTo(
            rhs.Attributes[
    "Name"].Value);

        
    if( nameCompare != 0 ) {
          
    return nameCompare;
        }


        
    return int.Parse(rhs.Attributes["Age"].Value) -
               
    int.Parse(lhs.Attributes["Age"].Value);
      }

    }


    void sortButton_Click(object sender, RoutedEventArgs e) {
      ListCollectionView view 
    = (ListCollectionView)GetFamilyView(  );

      
    // Managing the view.Sort collection would work, too
      if( view.CustomSort == null ) {
        view.CustomSort 
    = new PersonSorter(  );
      }

      
    else {
        view.CustomSort 
    = null;
      }

    }

    在示例4-44中,我们进行了排序,正如先前一样,但是我们从NameAge属性中拉出数据并适当的进行转换。

    4.4.2.5 XML数据源以及过滤

    XML的过滤机制非常像对象的过滤,只是我们使用XmlElements进行处理,正如示例4-45

    示例4-45

    void filterButton_Click(object sender, RoutedEventArgs e) {
      ICollectionView view 
    = GetFamilyView(  );

      
    if( view.Filter == null ) {
        view.Filter 
    = delegate(object item) {
          
    return
            
    int.Parse(((XmlElement)item).Attributes["Age"].Value) >= 18;
        }
    ;
      }

      
    else {
        view.Filter 
    = null;
      }

    }

    这里我们的过滤器使用了匿名委托,将每一个数据项转换为一个XmlElement元素来进行过滤。

    4.4.3相关数据源

    目前的版本,WPF没有直接支持绑定到相关的数据库,而且间接的支持范围并不是很广。作为WPF一个关于当前状态的绑定到相关数据的示例,我建议WinFX SDK示例提名为“Binding with Data in an ADO DataSet Sample

    4.4.4自定义数据源

    如果你愿意利用为遍历对象提供的间接数据源,但是没有一个内嵌数据源会使你满意,一个自定义的IDataSource实现应该会获得成功。例如,代替创建RemotePersonLoader集合来加载或移除家庭数据(在集合的构造函数中添加集合项,无论如何都有点做作),我们将要创建一个自定义的IDataSource实现,来达到这一点,如示例4-46

    示例4-46

    namespace PersonBinding {
      
    public class Person : INotifyPropertyChanged {}
      
    public class People : ObservableCollection<Person> {}

      
    public class RemotePeopleSource : IDataSource {
        People people 
    = null;

        
    public RemotePeopleSource(  ) {
          
    // Load People from afar
          

          
    // Let data binding know we've got data
          if( DataChanged != null ) {
            DataChanged(
    this, EventArgs.Empty);
          }

        }


        
    // IDataSource Members

        
    // Gets the underlying data object
        public object Data {
          
    get return people; }
        }


        
    // Occurs when a new data object becomes available
        
    // Especially handy for async object retrieval
        public event EventHandler DataChanged;

        
    // Refreshes the data source object using the most current
        
    // values for the object's configuration properties
        public void Refresh(  ) {
          
    // Not needed in our case
        }

      }

    }

    在示例4-46中,通过创建一个People集合的实例,我们已经实现了IDataSource接口,而且,在构造函数中,在一个神秘的数据遍历过程之后,我们激发了一个事件,让数据绑定知道我们已经得到了数据,还有再次检查Data属性。这个协议特别有用——一旦你进行异步的数据遍历(像对象数据源那样)。

    如果你的数据源通过自定义属性,像Asynchronous,一个或更多属性可以在运行期被改变。如果你已经得到了多个影响数据遍历的属性,你可能不想开始搜索新数据直到Refresh方法被调用,你可能开始于一个属性的改变,但是在客户端有机会改变其他的属性之前。

  • 相关阅读:
    【leetcode】1630. Arithmetic Subarrays
    【leetcode】1629. Slowest Key
    【leetcode】1624. Largest Substring Between Two Equal Characters
    【leetcode】1620. Coordinate With Maximum Network Quality
    【leetcode】1619. Mean of Array After Removing Some Elements
    【leetcode】1609. Even Odd Tree
    【leetcode】1608. Special Array With X Elements Greater Than or Equal X
    【leetcode】1603. Design Parking System
    【leetcode】1598. Crawler Log Folder
    Java基础加强总结(三)——代理(Proxy)Java实现Ip代理池
  • 原文地址:https://www.cnblogs.com/Jax/p/1137296.html
Copyright © 2011-2022 走看看