你的位置:首页 > ASP.net教程

[ASP.net教程]silverlight简单数据绑定3


3种数据绑定模式  OneTime(一次绑定) OneWay(单项绑定) TwoWay(双向绑定)

OneTime:仅在数据绑定创建时使用数据源更新目标。

列子:

第一步,创建数据源对象让Person类实现INotifyPropertyChanged接口,该接口具有PropertyChanged事件,PropertyChanged事件在数据源发生变化时候通知绑定

.cs

namespace SilverlightApplication2{  public class Person:INotifyPropertyChanged  {    public event PropertyChangedEventHandler PropertyChanged;    private String _Name;    public String Name    {      get { return this._Name; }      set      {        this._Name = value;        NotifyPropertyChanged("Name");      }    }    private int _Age;    public int Age    {      get { return this._Age; }      set      {        this._Age = value;        NotifyPropertyChanged("Age");      }    }    private String _Address;    public String Address    {      get { return this._Address; }      set      {        this._Address = value;        NotifyPropertyChanged("Address");      }    }    public void NotifyPropertyChanged(String propertyName)    {      if(PropertyChanged!=null)      {        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));      }    }  }}

关于INotifyPropertyChanged  参见http://www.cnblogs.com/beginor/archive/2012/08/13/2636418.html

INotifyPropertyChanged
它的作用:向客户端发出某一属性值已更改的通知。
当属性改变时,它可以通知客户端,并进行界面数据更新.而我们不用写很多复杂的代码来更新界面数据,这样可以做到方法简洁而清晰,松耦合和让方法变得更通用.可用的地方太多了:例如上传进度,实时后台数据变更等地方.目前我发现winform和silverlight都支持,确实是一个强大的接口.
在构造函数中先绑定
public Class_Name()   
{   
    User user = new User();    
    user.Name = "your name";    
    user.Address = "your address";    
  
    textBox1.Text = user.Name;   
    textBox2.Text = user.Address;   
}  
编写一个简单的业务类

 
public class User : INotifyPropertyChanged   
{   
    public event PropertyChangedEventHandler PropertyChanged;    
  
    private string _name;    
    public string Name    
    {   
        get { return _name; }   
        set    
        {   
            _name = value;   
           if(PropertyChanged != null)   
            {   
                PropertyChanged(this, new PropertyChangedEventArgs("Name"));   
            }   
        }   
    }   
  
    private string _address;   
    public string Address   
    {   
        get { return _address; }   
        set    
        {   
            _address = value;   
            if (PropertyChanged != null)   
            {   
                PropertyChanged(this, new PropertyChangedEventArgs("Address"));   
            }   
        }   
    }   
}  
 
 

ObservableCollection
绑定到集合
数据绑定的数据源对象可以是一个含有数据的单一对象,也可以是一个对象的集合。之前,一直在讨论如何将目标对象与一个单一对象绑定。Silverlight中的数据绑定还能将目标对象与集合对象相绑定,这也是很常用的。比如显示文章的题目列表、显示一系列图片等。
如果要绑定到一个集合类型的数据源对象,绑定目标可以使用ItemsControl,如ListBox或DataGrid等。另外,通过定制ItemsControl的数据模板(DataTemplate),还可以控制集合对象中每一项的显示。
 使用ObservableCollection
数据源集合对象必须继承IEnumerable接口,为了让目标属性与数据源集合的更新(不但包括元素的修改,还包括元素的增加和删除)保持同步,数据源集合还必须实现INotifyPropertyChanged接口和INotifyCollectionChanged接口。
在Silverlight中创建数据源集合可以使用内建的ObservableCollection类,因为ObservableCollection类既实现了INotifyPropertyChanged接口,又实现了INotifyCollectionChanged接口。使用ObservableCollection类不但可以实现Add、Remove、Clear和Insert操作,还可以触发PropertyChanged事件。

 

关于 谈谈INotifyPropertyChanged 的实现

参见http://www.cnblogs.com/beginor/archive/2012/08/13/2636418.html

INotifyPropertyChanged 接口是 WPF/Silverlight 开发中非常重要的接口, 它构成了 ViewModel 的基础, 数据绑定基本上都需要这个接口。 所以, 对它的实现也显得非常重要, 下面接贴出我知道的几种实现方式, 希望能起到抛砖引玉的作用。

一般的实现方式

这是一种再普通不过的实现方式, 代码如下:

1
2
3
4
5
6
7
8
9
10
public class NotifyPropertyChanged : INotifyPropertyChanged {
    
   public event PropertyChangedEventHandler PropertyChanged;
 
   virtual internal protected void OnPropertyChanged(string propertyName) {
      if (this.PropertyChanged != null) {
         this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
   }
}



这种方式称之为一般的实现方式, 因为它确实是太普通不过了, 而且使用起来也让人感到厌恶, 因为必须指定手工指定属性名称:

1
2
3
4
5
6
7
8
9
10
11
12
public class MyViewModel : NotifyPropertyChanged {
 
   private int _myField;
 
   public int MyProperty {
      get { return _myField; }
      set {
         _myField = value;
         OnPropertyChanged("MyProperty");
      }
   }
}



lambda 表达式实现方式

对 lambda 表达式比较熟悉的同学可以考虑用 lambda 表达式实现属性名称传递, 在 NotifyPropertyChanged 类添加一个这样的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void SetProperty<T>(ref T propField, T value, Expression<Func<T>> expr) {
   var bodyExpr = expr.Body as System.Linq.Expressions.MemberExpression;
   if (bodyExpr == null) {
      throw new ArgumentException("Expression must be a MemberExpression!", "expr");
   }
   var propInfo = bodyExpr.Member as PropertyInfo;
   if (propInfo == null) {
      throw new ArgumentException("Expression must be a PropertyExpression!", "expr");
   }
   var propName = propInfo.Name;
   propField = value;
   this.OnPropertyChanged(propName);
}



有了这个方法, NotifyPropertyChanged 基类使用起来就令人舒服了很多:

1
2
3
4
5
6
7
8
9
10
11
public class MyViewModel : NotifyPropertyChanged {
 
   private int _myField;
 
   public int MyProperty {
      get { return _myField; }
      set {
         base.SetProperty(ref _myField, value, () => this.MyProperty);
          }
   }
}



这样一来, 把属性名称用字符串传递改成了用 lambda 表达式传递, 减少了硬编码, 确实方便了不少, 但是还是感觉略微麻烦了一些, 还是要写一个 lambda 表达式来传递属性名称。

拦截方式实现

如果对 Castal.DynamicProxy 有印象的话, 可以考虑使用 DynamicProxy 进行拦截实现, 我的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1. 先定义一个拦截器, 重写 PostProcess 方法, 当发现是调用以 set_ 开头的方法时,
//    一般就是设置属性了, 可以在这里触发相应的事件。
internal class NotifyPropertyChangedInterceptor : StandardInterceptor {
 
   protected override void PostProceed(IInvocation invocation) {
      base.PostProceed(invocation);
      var methodName = invocation.Method.Name;
      if (methodName.StartsWith("set_")) {
         var propertyName = methodName.Substring(4);
         var target = invocation.Proxy as NotifyPropertyChanged;
         if (target != null) {
            target.OnPropertyChanged(propertyName);
         }
      }
   }
}
 
// 2. 再定义一个帮助类, 提供一个工厂方法创建代理类。
public static class ViewModelHelper {
 
   private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator();
   private static readonly NotifyPropertyChangedInterceptor Interceptor
         = new NotifyPropertyChangedInterceptor();
 
   public static T CreateProxy<T>(T obj) where T : class, INotifyPropertyChanged {
      return ProxyGenerator.CreateClassProxyWithTarget(obj, Interceptor);
   }
}



使用起来也是很方便的, 只是创建 ViewModel 对象时必须用帮助类来创建实例, 代码如下:

1
2
3
4
5
6
7
8
9
10
public class MyViewModel : NotifyPropertyChanged {
 
   // 定义属性时不需要任何基类方法, 和普通属性没有什么两样。
   public int MyProperty {
      get; set;
   }
}
// 使用时需要这样创建实例:
var viewModel = ViewModelHelper.CreateProxy<MyViewModel>();
viewModel.MyProperty = 100;



不过这种实现的缺点就是所有的属性都会触发 PropertyChanged 事件, 而且只能触发一个事件, 而在实际开发中, 偶尔需要设置一个属性, 触发多个 PropertyChanged 事件。

未来 .Net 4.5 的实现方式

在即将发布的 .Net 4.5 中, 提供了 CallerMemberNameAttribute 标记, 利用这个属性, 可以将上面提供的 SetProperty 方法进行改造, 这样的实现才是最完美的:

1
2
3
4
5
6
protected void SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null) {
   if (object.Equals(storage, value)) return;
 
   storage = value;
   this.OnPropertyChanged(propertyName);
}



由于有了 CallerMemberName 标记助阵, 可以说使用起来是非常方便了:

1
2
3
4
5
6
7
8
9
10
11
public class MyViewModel : NotifyPropertyChanged {
 
   private int _myField;
 
   public int MyProperty {
      get { return _myField; }
      set {
         base.SetProperty(ref _myField, value);
      }
   }
}



这种方法虽然好,不过却只有在 .Net 4.5 中才有, 而且也许永远不会添加到 Silverlight 中。

 

第二步:用户界面绑定数据对象,指定绑定模式

.xaml

<Grid x:Name="LayoutRoot" Background="Wheat" Loaded="LayoutRoot_Loaded">    <StackPanel>      <TextBox Grid.Row="0" Grid.Column="0" Width="150" Height="30" HorizontalAlignment="Left" Text="{Binding Name,Mode=OneTime}"/>      <TextBox Grid.Row="1" Grid.Column="0" Width="150" Height="30" HorizontalAlignment="Left" Text="{Binding Age,Mode=OneTime}"/>      <TextBox Grid.Row="2" Grid.Column="0" Width="150" Height="30" HorizontalAlignment="Left" Text="{Binding Address,Mode=OneTime}"/>      <Button x:Name="btnUpdata" Width="150" Height="30" Content="更新" Click="btnUpdata_Click"/>    </StackPanel>  </Grid>

第三步:数据绑定

.xaml.cs

 Person person;    void LayoutRoot_Loaded(object sender,RoutedEventArgs e)    {      person = new Person()      {       Name="Terry",       Age=20,       Address="Beijing"      };      this.LayoutRoot.DataContext = person;    }    private void btnUpdata_Click(object sender, RoutedEventArgs e)    {      person.Name = "小哥";      person.Age = 23;      person.Address = "上海";    }

 

由于是OneTime数据绑定模式,可以看出在单机更新按钮时,尽管改变了数据对象的属性值,但是用户界面的数据值依然是在绑定创建时候的数据值。