使用mvvm模式的WPF数据绑定问题

时间:2009-10-10 16:23:42

标签: wpf data-binding mvvm

我创建了一个用户控件“SearchControl”(它将在其他屏幕中进一步重复使用)。 SearchControl - >

<usercontrol name="SearchControl"......>
   <stackpanel orientation="horizontal"...>

       <TextBox Text"{Binding Path=UserId}"...>

       <Button Content="_Search" ....Command="{Binding Path=SearchCommand}"..>

   </stackpanel>
</usercontrol>

 public partial class SearchControl : UserControl
{
   public SearchControl()
   {
      InitializeComponent();
      DataContext=new UserViewModel();
   }
}

然后我在“UserSearch”窗口中使用此控件

<window name="UserSearch".............
  xmlns:Views="Namespace.....Views">
  <Grid>
      <Grid.RowDefinitions>
         <RowDefinition..../>
         <RowDefinition..../>
         <RowDefinition..../>
         <RowDefinition..../>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
         <ColumnDefinition..../>
         <ColumnDefinition..../>         
      </Grid.ColumnDefinitions>

      <Views:SearchControl Grid.Row="0" Grid.Colspan="2"/>
      <TextBlock Text="User Id" Grid.Row="1" Grid.Column="0"..../>
      <TextBox Text="{Binding Path=UserId}" Grid.Row="1" Grid.Column="1".../>

      <TextBlock Text="First Name" Grid.Row="2" Grid.Column="0"..../>
      <TextBox Text="{Binding Path=FirstName}" Grid.Row="2" Grid.Column="1".../>

       <TextBlock Text="Last Name" Grid.Row="3" Grid.Column="0"..../>
       <TextBox Text="{Binding Path=LastName}" Grid.Row="3" Grid.Column="1".../>
  </Grid>
</window>

public partial class UserSearch : Window
{
    public UserSearch()
    {
       InitializeComponent();
       DataContext=new UserViewModel();
    }
}

我的目标是:    当我在SearchControl的文本框中输入UserId并单击“搜索”按钮时,结果记录将被显示在UserId,FirstName,LastName的文本框中

class UserViewModel:INotifyPropertyChanged
{
   DBEntities _ent; //ADO.Net Entity set

   RelayCommand _searchCommand;

   public UserViewModel()
   {
      _ent = new DBEntities();
   }

   public string UserId {get; set;}
   public string FirstName {get; set;}
   public string LastName {get; set;}

   public ICommand SearchCommand
   {
      get
       {
           if(_searchCommand == null)
           {
               _searchCommand = new RelayCommand(param = > this.Search());
           }
           return _searchCommand;
       }
   }

   public void Search()
   {
       User usr = (from u in _ent
                  where u.UserId = UserId
                  select u).FirstOrDefault<User>();

       UserId = usr.UserId;
       FirstName = usr.FirstName;
       LastName = usr.LastName;

       OnPropertyChanged("UserId");
       OnPropertyChanged("FirstName");
       OnPropertyChanged("LastName");
   }

   public event PropertyChangedEventHandler PropertyChanged;
   protected void OnPropertyChanged(string propertyName)
   {
       if(PropertyChanged != null)
           PropertyChanged(this, new PropertChangedEventArgs(propertyName);
   }
}

这里我正在为SearchControl和UserSearch使用UserViewModel的两个独立实例,即使我在UserId上搜索特定用户的记录,我也无法将属性UserId,FullName,LastName与相应的绑定文本框...如何解决这个问题??

1 个答案:

答案 0 :(得分:8)

1)不要让View初始化表示模型,它应该是反过来的。表示模型是感兴趣的对象,而不是特定的视图。

public interface IView
{
    void SetModel(IPresentationModel model);
}

publiv class View : UserControl, IView
{
    public void SetModel(IPresentationModel model)
    {
        DataContext = model;
    }
}

public class PresentationModel : IPresentationModel
{
    public PresentationModel(IView view)
    {
        view.SetModel(this);
    }
}

2)不要在代码隐藏文件中设置子视图的数据上下文。通常,使用子视图的视图会在xaml文件中设置数据上下文。

3)通常每个视图都有自己的表示模型。演示模型应该有一种类型的视图。这意味着单个表示模型的不同视图在外观上可能不同但在功能上不同(在您的情况下,一个视图用于搜索,另一个用于显示和编辑数据)。所以,你已经实行了单一责任原则。

4)抽象你的数据访问层,否则你将无法对你的表示模型进行单元测试(因为它需要直接访问数据库)。定义存储库接口和实现:

public interface IUserRepository
{
    User GetById(int id);
}

public class EntityFrameworkUserRepository : IUserRepository
{
    private readonly DBEntities _entities;

    public EntityFrameworkUserRepository(DBEntities entities)
    {
        _entities = entities;
    }

    public User GetById(int id)
    {
        return _entities.SingleOrDefault(u => u.UserId == id);
    }
}

5)不要使用FirstOrDefault,因为ID是唯一的,因此一个id不能有多个用户。 SingleOrDefault(在上面的代码片段中使用)如果找到多个结果则抛出异常但如果没有找到则返回null。

6)直接绑定到您的实体:

public interface IPresentationModel
{
    User User { get; }
}

<StackPanel DataContext="{Binding Path=User}">
    <TextBox Text="{Binding Path=FirstName}" />
    <TextBox Text="{Binding Path=LastName}" />
</StackPanel>

7)使用CommandParameter直接提供您要搜索的用户ID。

<TextBox x:Name="UserIdTextBox">

<Button Content="Search" Command="{Binding Path=SearchCommand}"
        CommandParameter="{Binding ElementName=UserIdTextBox, Path=Text}" />

public class PresentationModel
{
    public ICommand SearchCommand
    {
        // DelegateCommand<> is implemented in some of Microsoft.BestPractices
        // assemblies, but you can easily implement it yourself.
        get { return new DelegateCommand<int>(Search); }
    }

    private void Search(int userId)
    {
        _userRepository.GetById(userId);
    }
}

8)如果只有数据绑定导致问题,请查看以下网站以获取如何调试wpf数据绑定的一些想法:http://beacosta.com/blog/?p=52

9)不要使用包含属性名称的字符串。一旦你重构你的代码和属性改变他们的名字,将有一个压力的时间找到字符串中的所有属性名称并修复它们。改为使用lambda表达式:

public class PresentationModel : INotifiyPropertyChanged
{
    private string _value;
    public string Value
    {
        get { return _value; }
        set
        {
            if (value == _value) return;

            _value = value;
            RaisePropertyChanged(x => x.Value);
        }
    }

    public PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged(Expression<Func<PresentationModel, object>> expression)
    {
        if (PropertyChanged == null) return;

        var memberName = ((MemberExpression)expression.Body).Member.Name;
        PropertyChanged(this, new PropertyChangedEventArgs(memberName));
    }
}

我希望你能最好地解决你的问题,我希望我能帮到你一点点。

最诚挚的问候 Oliver Hanappi