【问题标题】:WPF Data binding issue using mvvm pattern使用 mvvm 模式的 WPF 数据绑定问题
【发布时间】:2009-10-10 16:23:42
【问题描述】:

我创建了一个用户控件“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 并单击 Search 按钮时,检索到的结果记录应显示在 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);
   }
}

在这里,由于我将 UserViewModel 的两个单独实例用于 SearchControl 和 UserSearch,即使我在通过 UserId 搜索时检索了特定用户的记录,我也无法将属性 UserId、FullName 和 LastName 与各自的属性绑定文本框...我该如何解决这个问题??

【问题讨论】:

    标签: wpf data-binding mvvm


    【解决方案1】:

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

    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(在上面的代码 sn-p 中使用)如果找到多个结果则抛出异常,但如果没有找到则返回 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));
        }
    }
    

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

    最好的问候
    奥利弗·哈纳皮

    【讨论】:

    • 很好的回应奥利弗,来自另一个 wpf 菜鸟。我认为您在 (9) 中的表达式缺少 Body: -- var memberName = (expression.Body as MemberExpression).Member.Name; -- 谢谢你的好主意!
    • 是的,你是对的。我只从内存中编写了代码 sn-ps,所以我不确定 ;)
    • 喜欢你的 INotifyPropertyChanged 实现 Oliver。它不会经常发生,但是当您更改属性名称时,由于属性更改通知,要找到所有作为字符串引用的位置会很痛苦。您应该将其发送给这些人:wpf.codeplex.com/… 以获取他们的 IDE 模板工具包。
    • 感谢您的提示,我会考虑的。我在另一个你可能感兴趣的问题中发布了一个更强大的帮助类:stackoverflow.com/questions/1329138/…
    猜你喜欢
    • 1970-01-01
    • 2010-11-11
    • 1970-01-01
    • 1970-01-01
    • 2013-02-16
    • 2011-03-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多