Go to content

Bulletcode.NET

View Models

Bulletcode.NET includes three classes which can be used as base classes for view models in both desktop and mobile applications:

  • ObservableObject — implements INotifyPropertyChanged; can be used for view models containing observable properties.
  • ObservableValidator — inherits ObservableObject and implements INotifyDataErrorInfo; can be used for view models containing properties with validation attributes (see Validation for more information).
  • BaseViewModel — inherits ObservableValidator, implements IDisposable and provides support for creating commands which wrap the view model’s methods.

In Bulletcode.UI, you can also use the BasePageViewModel and BaseDialogViewModel classes which inherit BaseViewModel. See Pages and dialogs for more information.

Observable properties

To create an observable property, its constructor should call the SetProperty() method with a reference to the field storing the value of the property, for example:

public class MyViewModel : BaseViewModel
{
    private string _name;

    public string Name
    {
        get => _name;
        set => SetProperty( ref _name, value );
    }
}

This method automatically triggers the PropertyChanged event if the new value is different than the current one.

If the value of the property is not stored directly as the view model’s field — for example, when it’s stored as a property of another object — a getter and setter callback can be used instead:

public class MyViewModel : BaseViewModel
{
    private Model _model;

    public string Name
    {
        get => _model.Name;
        set => SetProperty( () => _model.Name, value => _model.Name = value, value );
    }
}

Validation

If the view model inherits ObservableValidator, another overload of SetProperty() method is available which has an additional parameter. When set to true, validators are automatically executed after updating the value of the property, and the ErrorsChanged event is triggered if necessary:

public class MyViewModel : BaseViewModel
{
    private string _name;

    [Required]
    [Display( Name = "Name" )]
    public string Name
    {
        get => _name;
        set => SetProperty( ref _name, value, true );
    }
}

See Validation for more information.

Dependencies

The OnPropertyChanged() method can be called to trigger the PropertyChanged event for the specified property, for example:

public class MyViewModel : BaseViewModel
{
    private decimal _side;

    public decimal Side
    {
        get => _side;
        set
        {
            if ( SetProperty( ref _side, value ) )
                OnPropertyChanged( nameof( Area ) );
        }
    }

    public decimal Area => _side * _side;
}

The SetProperty() method returns true if the property has been updated, i.e. if the new value is different than the current one.

Another solution is to use the DependsOn attribute:

public class MyViewModel : BaseViewModel
{
    private decimal _side;

    public decimal Side
    {
        get => _side;
        set => SetProperty( ref _side, value );
    }

    [DependsOn( nameof( Side ) )]
    public decimal Area => _side * _side;
}

In this case, when the Side property is updated, the PropertyChanged event is also automatically triggered for the Area property.

When using attributes, dependencies can be nested; for example, property A can depend on B, and B can depend on C. In this case, updating C would trigger the PropertyChanged event for both A and B.

Commands

If the view model inherits BaseViewModel, the GetCommand() method can be used to create commands which can be bound to UI elements, for example buttons.

The GetCommand() method accepts a callback, which is usually a method of the view model:

public class MyViewModel : BaseViewModel
{
    public ICommand SubmitCommand => GetCommand( Submit );

    private void Submit()
    {
        // TODO
    }
}

The command can then be bound to a UI element, for example:

<Button Command="{Binding SubmitCommand}">OK</Button>

The command can also take a parameter:

public class MyViewModel : BaseViewModel
{
    public ICommand SubmitCommand => GetCommand( Submit );

    private void Submit( string sender )
    {
        // TODO
    }
}

The parameter can be passed from the UI using the CommandParameter attribute. For example:

<Button Command="{Binding SubmitCommand}" CommandParameter="{Binding Sender}">OK</Button>

The GetCommand() method can take a second parameter, which is a function indicating whether the command is enabled:

public class MyViewModel : BaseViewModel
{
    public ICommand SubmitCommand => GetCommand( Submit, () => CanSubmit );

    public bool CanSubmit => _name = null;

    private void Submit()
    {
        // TODO
    }
}

The NotifyCanExecuteChanged() method should be called when the enabled state of a command is changed:

public class MyViewModel : BaseViewModel
{
    private string _name;

    public string Name
    {
        get => _name;
        set
        {
            if ( SetProperty( ref _name, value ) )
                NotifyCanExecuteChanged( nameof( SubmitCommand ) );
        }
    }
}

This method can also be called without a parameter to update the state of all commands.

Lifetime

View models should be registered as scoped services. This can be done, for example, by using the Scoped attribute (see Dependency Injection for more information):

[Scoped]
public class MyViewModel : BaseViewModel
{
}

When pages and dialogs are created using the Shell, they have their own associated service provider scopes, which means that the view models and their dependencies are automatically destroyed when the corresponding page or dialog is destroyed.

The BaseViewModel class implements the IDisposable interface and contains a virtual Dispose() method which can be overridden if the view model needs to clean up some resources:

public class MyViewModel : BaseViewModel
{
    protected override void Dispose( bool disposing )
    {
        base.Dispose( disposing );

        // TODO
    }
}