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方法被调用,你可能开始于一个属性的改变,但是在客户端有机会改变其他的属性之前。

  • 相关阅读:
    PHPsession实现用户登陆功能
    asp.net core mvc基于Redis实现分布式锁,C# WebApi接口防止高并发重复请求,分布式锁的接口幂等性实现
    OpenSSL 下载和私钥证书、CERTIFICATE证书生成
    Java byte[] 转C# byte[]
    如何在Etherscan.io 部署ETH以太坊智能合约 如何在15分钟内创建你的加密货币
    JavaScript 关于金额、数字验证的正则表达式
    web api .net C# mvc API返回XML文档的解析并取值
    C#和PHP加密结果一致的DES加密解密算法。php实现和c#一致的DES加密解密
    C#常用的图片处理方法-图片剪切、图片压缩、多图合并代码
    BitMap 图片格式与Base64Image格式互转方法
  • 原文地址:https://www.cnblogs.com/Jax/p/1137296.html
Copyright © 2011-2022 走看看