WPF Data binding issue using mvvm pattern

data-bindingmvvmwpf

I have created a user control "SearchControl"(which will be reused further in other screens as well.
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();
   }
}

I then use this control in a window "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();
    }
}

What I am aimimg for:
When I enter UserId inthe textbox in SearchControl and click on Search button, the resulting record which is retieved should be displayed in the textboxes for 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);
   }
}

Here as I am using two separate instances of the UserViewModel for the SearchControl and UserSearch, even though I retieve the record for the particular user on searching by UserId, I am unable to bind the properties UserId, FullName , LastName with the respective textboxes…How do I fix this problem??

Best Answer

1) Don't let the View initialize the presentation model, it should be the other way round. The presentation model is the object of interest, not the particular 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) Don't set the data context of the subview in the code behind file. Usually, the view that uses the subview sets the data context in the xaml file.

3) Usually each view has its own presentation model. The presentation model should have one type of view. That means that different views of a single presentation model may differ in appearance but not in functionality (in your case one view is used to search, the other one is used to display and edit data). So, you have vialoted the Single Responsibilty Principle.

4) Abstract your data access layer, otherwise you won't be able to unit test your presentation model (because it needs access to the data base directly). Define an repository interface and implementation:

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) Don't use FirstOrDefault because an ID is unique, so there must not be several users for one id. SingleOrDefault (used in the code snippet above) throws an exception if more than one result is found but returns null if none is found.

6) Bind directly to your entity:

public interface IPresentationModel
{
    User User { get; }
}

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

7) Use the CommandParameter to provide the user id you are searching for directly with your command.

<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) If only data binding causes issues, look at the following website to get some ideas how to debug wpf data bindings: http://beacosta.com/blog/?p=52

9) Don't use strings that contain property names. Once you refactor your code and properties change their names, to will have a stressful time finding all property names in strings and fixing them. Use lambda expressions instead:

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

I wish you the best to solve your problem and I hope that I could help you a little bit.

Best Regards
Oliver Hanappi