Wednesday, 21 September 2011

Using IActiveAware and INavigationAware

prism[Concrete/Little bit interesting] The Microsoft Prism framework provides a couple of very useful interfaces for managing awareness of view activation and navigation.

 

 

 

IActiveAware

IActiveAware is a simple interface you can implement on your views to indicate that you want the view to be notified when it is made active or inactive. It looks like this:

public interface IActiveAware
{
    /// <summary>
    /// Gets or sets a value indicating whether the object is active.
    /// </summary>
    /// <value><see langword="true" /> if the object is active; otherwise <see langword="false" />.</value>
    bool IsActive { get; set; }

    /// <summary>
    /// Notifies that the value for <see cref="IsActive"/> property has changed.
    /// </summary>
    event EventHandler IsActiveChanged;
}

The IsActive flag lets you know if your view is active, and the IsActiveChanged event will fire when that state changes.

If you implement this on your view class, then you need to ensure the event gets fired when the state is changed by the region behaviour. You do this by adding the IActiveAware interface to your View-Model:

public class LoginViewModel : NotificationObject, IActiveAware, INavigationAware
{ 
}

An example of the implemented interface is shown here:

#region ViewModel activation
private bool _IsActive = false;
public bool IsActive
{
    get
    {
        return _IsActive;
    }
    set
    {
        _IsActive = value;
        if (value)
            OnActivate();
        else
            OnDeactivate();
    }
}

public event EventHandler IsActiveChanged;
#endregion

In the above example I use the changing state of IsActive to trigger the private methods OnActivate() and OnDeactivate(). These methods are thus called when the view active state changes.

IActiveAware Example - Managing Devices

A good example of the use of IActiveAware is the subscription to an control of devices. We use event aggregation to interface with physical devices, that is, we subscribe to device notifications. The simplest scheme is to subscribe to all the device events in the View-Model constructor:

//Subscribe to device events
eventAggregator.GetEvent<MagneticStripeReaderEvent>().Subscribe(MagneticStripeReaderInputReceived);
eventAggregator.GetEvent<ScannerEvent>().Subscribe(ScannerInputReceived);

Of course, this means that the View-Model will be called back on device events during the entire lifetime of the module housing the View-Model and the associated views.

To ensure you only act on the device events when your view is the active view, simply use the IsActive property appropriately in the event callback:

private void ScannerInputReceived(ScannerEventParameter param)
{
    //Handle input if active and OperatorId has focus
    if (IsActive == false || OperatorHasFocus == false)
        return;

    if (!string.IsNullOrEmpty(param.Barcode))
    {
        OperatorId = param.Barcode;
        ValidateLoginInput(false, null);
    }
    else
    {
        ProgressMessage = Resources.Message_InvalidInput;
    }
}

OnActivate() and OnDeactivate()

These methods when implemented should be used to handle the View-Model initialisation and termination state. For example, in a Login screen, during OnActivate(), the View-Model should initialise the various entry fields and properties used to control the view:

private void OnActivate()
{
    eventAggregator.GetEvent<ChangeTitleEvent>().Publish(Properties.Resources.Title_Login);
    ProgressMessage = String.Empty;
    ChangingPassword = false;
    NewBiometricEntry = false;
    LoginWithPassword = configurationManager.GetBool(ConfigurationNames.LoginWithPassword);
    ResetOperatorPassword();

    LogoutCurrentOperator();
}

Here we are setting the title through a published event, clearing the progress message, clearing some flags (used to control the visibility state of page elements), resetting the OperatorId and Password and ensuring we are logged off. These operations will be performed every time a view in this module is activated.

IActiveAware Limitations and INavigationAware

Responding to IActiveAware works fine in a module that supports a single view. In this scenario, the View-Model activation is one-to-one with the View activation. However, if there are more than one view associated with a View-Model, you will have to implement INotificationAware:

public interface INavigationAware
{
    bool IsNavigationTarget(NavigationContext navigationContext);
    void OnNavigatedFrom(NavigationContext navigationContext);
    void OnNavigatedTo(NavigationContext navigationContext);
}

This interface when implemented provides callbacks to handle the navigationContext switch in the form of OnNavigatedFrom() and OnNavigatedTo(). A typical implementation here is to keep a copy of the navigationContext passed to the OnNavigatedTo(). This can be used later to perform view-to-view navigation:

#region Navigation Aware for view switching in this module
private IRegionNavigationService navigationService;

public bool IsNavigationTarget(NavigationContext navigationContext)
{
    return true;
}

public void OnNavigatedFrom(NavigationContext navigationContext)
{
}

public void OnNavigatedTo(NavigationContext navigationContext)
{
   navigationService = navigationContext.NavigationService;
}
#endregion

and to perform view-to-view navigation (for example, Go Back):

private void onGoBackHandler()
{
    if (navigationService.Journal.CanGoBack)
    {
       navigationService.Journal.GoBack();
    }
}

Note that in the above implementation of the interface we are returning true from the IsNavigationTarget() method. This method is called by the framework to determine if the current views should be reused for the navigation target. Returning true indicated the views should be reused. Since we pre-create all our views, you should return true.

2 comments:

  1. Does the Prism framework raise the IsActiveChanged event or is it up to the implementer of the IsActive property?
    A minor proof reading point, you called INavigationAware INotificationAware.

    ReplyDelete
  2. There is no sensible way for the Prism framework to raise the IsActiveChanged event if you implement IActiveAware in your own viewmodel.
    You can think of IsActiveChanged.raise() as a protected method.

    It would be possible to raise the event from outside via reflecting a non-public member, which requires elevated trust, so is a security concern and a no-go for silverlight applications (one of prism's targets) and similar.

    ReplyDelete