Проблема привязки данных WPF с использованием шаблона mvvm - PullRequest
1 голос
/ 10 октября 2009

Я создал пользовательский элемент управления «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();
    }
}

На что я нацеливаюсь: Когда я вхожу в текстовое поле UserId в SearchControl и нажимаю кнопку «Поиск», полученная в результате запись должна отображаться в текстовых полях для 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 с соответствующими текстовые поля ... Как я могу решить эту проблему ??

1 Ответ

8 голосов
/ 10 октября 2009

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, поскольку идентификатор уникален, поэтому для одного идентификатора не должно быть нескольких пользователей. SingleOrDefault (используется в приведенном выше фрагменте кода) выдает исключение, если найдено более одного результата, но возвращает ноль, если ничего не найдено.

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, чтобы предоставить идентификатор пользователя, который вы ищете, непосредственно с вашей командой.

<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) Не используйте строки, содержащие имена свойств. После того, как вы выполните рефакторинг, ваш код и свойства поменяют свои имена, чтобы у вас было время найти все имена свойств в строках и исправить их. Вместо этого используйте лямбда-выражения:

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));
    }
}

Я желаю вам всего наилучшего в решении вашей проблемы и надеюсь, что смогу вам немного помочь.

С наилучшими пожеланиями
Оливер Ханаппи

...