Assisticant

Thoughtful, intelligent data binding

Get Started

Your typical MVVM framework requires you to raise property changed events. Assisticant is more thoughtful. She figures out when the UI needs to be updated. You just write your code. She intelligently follows your logic and raises property changed and collection changed events exactly when she should.

Typical MVVM

public class PersonViewModel :
  ViewModelBase
{
  private string _first;
  private string _last;

  public string First
  {
    get => _first;
    set 
    {
      _first = value;
      RaisePropertyChanged(() => First);
      RaisePropertyChanged(() => Full);
    }
  }

  public string Last
  {
    get => _last;
    set 
    {
      _last = value;
      RaisePropertyChanged(() => Last);
      RaisePropertyChanged(() => Full);
    }
  }

  public string Full =>
    First + " " + Last;
}

Assisticant

public class PersonViewModel
{
  private Observable<string> _first =
    new Observable<string>();
  private Observable<string> _last =
    new Observable<string>();

  public string First
  {
    get => _first.Value;
    set => _first.Value = value;
  }

  public string Last
  {
    get => _last.Value;
    set => _last.Value = value;
  }

  public string Full =>
    First + " " + Last;
}

The Result

FullName property updates automatically.

Assisticant will even discover dependencies across object boundaries. You don't need to subscribe to events.

Typical MVVM

public class PersonModel : ObservableBase
{
  private string _first;
  private string _last;

  public string First
  {
    get => _first;
    set
    {
      _first = value;
      RaisePropertyChanged(() => First);
    }
  }

  public string Last
  {
    get => _last;
    set
    {
      _last = value;
      RaisePropertyChanged(() => Last);
    }
  }
}

Assisticant

public class PersonModel
{
  private Observable<string> _first =
    new Observable<string>();
  private Observable<string> _last =
    new Observable<string>();

  public string First
  {
    get => _first.Value;
    set => _first.Value = value;
  }

  public string Last
  {
    get => _last.Value;
    set => _last.Value = value;
  }
}
public class PersonViewModel : ViewModelBase
{
  private readonly PersonModel _person;

  public PersonViewModel(PersonModel person)
  {
    _person = person;
    _person.PropertyChanged += PersonPropertyChanged;
  }

  void PersonPropertyChanged(
    object sender, PropertyChangedEventArgs e)
  {
    if (e.PropertyName == "First")
    {
      RaisePropertyChanged(() => First);
      RaisePropertyChanged(() => Full);
    }
    else if (e.PropertyName == "Last")
    {
      RaisePropertyChanged(() => Last);
      RaisePropertyChanged(() => Full);
    }
  }

  public string First
  {
    get => _person.First;
    set => _person.First = value;
  }

  public string Last
  {
    get => _person.Last;
    set => _person.Last = value;
  }

  public string Full => _person.First + " " + _person.Last;
}
public class PersonViewModel
{
  private readonly PersonModel _person;

  public PersonViewModel(PersonModel person)
  {
    _person = person;
  }

  public string First
  {
    get => _person.First;
    set => _person.First = value;
  }

  public string Last
  {
    get => _person.Last;
    set => _person.Last = value;
  }

  public string Full => _person.First + " " + _person.Last;
}

Because Assisticant can track dependencies across objects, you don't need to use a message bus to keep view models in sync with one another. Just move the shared state to an object that both view models depend upon.

Typical MVVM

public class PersonSelectorViewModel : ViewModelBase
{
  private readonly Directory _directory;
  private readonly List<PersonViewModel> _people;
  private PersonViewModel _selectedPerson = null;

  public PersonSelectorViewModel(
    Directory directory)
  {
    _directory = directory;

    _people = _directory.GetPeople()
      .Select(person => new PersonViewModel
      {
        Id = person.Id,
        First = person.First,
        Last = person.Last
      })
      .ToList();
  }

  public IEnumerable<PersonViewModel> People => _people;

  public PersonViewModel SelectedPerson
  {
    get => _selectedPerson;
    set
    {
      if (_selectedPerson == value)
        return;

      _selectedPerson = value;
      RaisePropertyChanged(() => this.SelectedPerson);

      MessengerInstance.Send(new PersonSelected
      {
        PersonId = value.Id
      });
    }
  }
}

Assisticant

public class PersonSelectorViewModel
{
  private readonly Directory _directory;
  private readonly List<PersonViewModel> _people;
  private readonly PersonSelectionModel _personSelection;

  public PersonSelectorViewModel(
    Directory directory,
    PersonSelectionModel personSelection)
  {
    _directory = directory;
    _personSelection = personSelection;

    _people = _directory.GetPeople()
      .Select(person => new PersonViewModel(
        _personSelection.SelectedPerson))
      .ToList();
  }

  public IEnumerable<PersonViewModel> People => _people;

  public PersonViewModel SelectedPerson
  {
    get => _people.FirstOrDefault(p =>
      p.Person == _personSelection.SelectedPerson);
    set => _personSelection.SelectedPerson = value == null
      ? null
      : value.Person;
  }
}
public class PersonDetailViewModel : ViewModelBase
{
  private Directory _directory;
  private int _id;
  private string _first;
  private string _last;

  public PersonDetailViewModel()
  {
    _directory = new Directory();
    MessengerInstance.Register<PersonSelected>(this, message =>
    {
      var person = _directory.LoadSession(message.PersonId);
      _id = message.PersonId;
      First = person.First;
      Last = person.Last;
    });
  }

  public string First
  {
    get => _first;
    set
    {
      if (value == _first)
        return;

      _first = value;
      RaisePropertyChanged(() => this.First);

      MessengerInstance.Send(new PersonNameChanged
      {
        PersonId = _id,
        First = value,
        Last = Last
      });
    }
  }

  public string Last
  {
    get => _last;
    set
    {
      if (value == _last)
        return;

      _last = value;
      RaisePropertyChanged(() => this.Last);

      MessengerInstance.Send(new PersonNameChanged
      {
        PersonId = _id,
        First = First,
        Last = value
      });
    }
  }
}
public class PersonDetailViewModel
{
  private readonly PersonSelectionModel _personSelection;

  public PersonDetailViewModel(
    PersonSelectionModel personSelection)
  {
    _personSelection = personSelection;
  }

  public string First
  {
    get => _personSelection.SelectedPerson?.First;
    set => if (_personSelection.SelectedPerson != null)
      _personSelection.SelectedPerson.First = value;
  }

  public string Last
  {
    get => _personSelection.SelectedPerson?.Last;
    set => if (_personSelection.SelectedPerson != null)
      _personSelection.SelectedPerson.Last = value;
  }
}
public class PersonSelectionModel
{
  private Observable<PersonModel> _selectedPerson =
    new Observable<PersonModel>();

  public PersonModel SelectedPerson
  {
    get => _selectedPerson.Value;
    set => _selectedPerson.Value = value;
  }
}

Setup

To get started, create a project for the XAML platform of your choice:

Add a reference to the Assisticant.App NuGet package.

PM> Install-Package Assisticant.App

Create portable class libraries for all of your shared code. Add the Assisticant NuGet package to these projects.

PM> Install-Package Assisticant

To use the Visual Studio snippets, install the Assisticant.Snippets package. You only need to do this once per development machine.

PM> Install-Package Assisticant.Snippets