Blue Flower

Recently, we have learned about principle of direct injection and effective binding twist models with the concepts, as well as how to create custom binding. We continue the study of library Aero Framework and consider another architectural question.

Navigation between views (screens) in a XAML-based applications is quite important and interesting task. This is especially true of its implementation in the framework of the MVVM pattern. Ideally twist model should not contain any direct reference to the presentation, to be cross-platform and retained the ability to re-use them in multiple projects. Today we will learn how to achieve this.


With xaml-development is very closely linked to the concept of commands (Commands). Today, we do not consider the implementation of this pattern in the Aero Framework library, because it is intuitive and already briefly covered in the documentation.
Despite all the architectural significance of the issue, there is strikingly simple and reliable way to navigate ... you do not even believe it right, it's so simple.

In most cases, navigation occurs after clicking on the visual element and execute it on any team, so why not pass identifier (address or type) of the target presentation as a parameter to the team? It's very easy and logical.

<Button 
    Content="{Localizing About}"
    Command="{Context Key=Navigate}"
    CommandParameter="/Views/AboutView.xaml">

<Button 
    Content="{Localizing About}"
    Command="{Context Key=Navigate}"
    CommandParameter="{x:Type views:PaymentView}">

<Button 
    Content="{Localizing About}"
    Command="{Context Key=Navigate}"
    CommandParameter="http://makeloft.by/">

public class NavigationViewModel : ContextObject, IExposable
{
    public virtual void Expose()
    {
        this[Context.Navigate].CanExecute += (sender, args) => args.CanExecute = 'any conditions';
        
        this[Context.Navigate].Executed += (sender, args) => Navigator.GoTo(args.Parameter);
    }
}

The ability to navigate on the representation or otherwise regulated event CanExecute the team, and for the technical details of the responsible Navigator class. That is, the target address of a port-forwarded through the twist model.

A little more sophisticated navigation script would look like this:

<!--LoginView.xaml-->
<View DataContext="{Store Key=viewModels:LoginViewModel}">
    <StackPanel>
        <TextBlock Text="{Localizing Key=Name}"/>
        <TextBox Text="{Binding Name, Mode=TwoWay}"/>
        <TextBlock Text="{Localizing Key=Password}"/>
        <PasswordBox Password="{Binding Password, Mode=TwoWay}"/>
        
        <Button 
            Command="{Context Key=Login}" 
            CommandParameter="{x:Type ProductsView}" 
            Content="{Localizing Key=Login}"/>
        <TextBlock Text="{Binding Error}"/>
    </StackPanel>
</View>

[DataContract]
public class LoginViewModel : ContextObject, IExposable
{
    [DataMember]
    public string Name
    {
        get { return Get(() => Name); }
        set { Set(() => Name, value); }
    }
    
    public string Password
    {
        get { return Get(() => Name); }
        set { Set(() => Name, value); }
    }

    public UserData UserData
    {
        get { return Get(() => UserData); }
        set { Set(() => UserData, value); }
    }

    public virtual void Expose()
    {
        this[() => Name].PropertyChanged += (sender, args) => Context.Login.RaiseCanExecuteChanged();
        this[() => Password].PropertyChanged += (sender, args) => Context.Login.RaiseCanExecuteChanged();
        
        this[() => Name].Validation += () =>
            Error = Name == null || Name.Length < 3 
                ? Unity.App.Localize("InvalidName")
                : null;
                
        this[() => Password].Validation += (sender, args) => 
            Error = Name == null || Name.Length < 4 
                ? Unity.App.Localize("InvalidPassword")
                : null;

        this[Context.Login].CanExecute += (sender, args) =>
        {
            args.CanExecute = string.IsNullOrEmpty(Error); // Error contains last validation fail message
        };

        this[Context.Login].Executed += async (sender, args) =>
        {
            try
            {
                UserData = await Bank.Current.GetUserData();
                Navigator.GoTo(args.Parameter);
            }
            catch (Exception exception)
            {
                Error = Unity.App.Localize(exception.Message);
            }
        };

        this[Context.Logout].Executed += (sender, args) =>
        {
            UserData = null;
            Navigator.RedirectTo(args.Parameter);
        };
    }
}

After the presentation of navigation through the mechanism of direct injections twist gets the desired model from the container.

All this is somewhat like a web navigation, where every page is requested from the server by uri. But what in this case, to transfer parameters? In connection with the application of the principle of direct injection is no longer a real need to transfer any parameters, for each view, in fact, can get access to any twist model and extract the necessary information directly from it!

You may just want to come up with a tricky example, what if a transition to multiple views and is not known beforehand on what kind of ... But all this is solved in elementary in several ways: you can create a series of buttons, where each is a unique ID submission to the command parameter, and depending on logical conditions dizayblyatsya these buttons and / or concealed; In either case, the converter is allowed to create and bind to a property of CommandParameter.

Different variations can be devised very much, but the idea remains the same - the identity of the presentation while navigating passed to the command parameter. But perhaps someone will object, but what if you want to transmit to the team and the other option? However, there is a way out, you can easily transmit multiple arguments in the command:

public class Set : ObservableCollection<object>
{
}

<Set x:Key="ParameterSet">
    <system:String>/Views/AnyView.xaml</system:String>
    <system:String>SecondParaneter</system:String>
</Set>

<Button 
    Content="{Localizing GoToAnyView}"
    Command="{Context Key=GoTo}"
    CommandParameter="{StaticResource ParameterSet}">

The concept of the next: submission aware of other views (screens), which can occur navigation, and the very possibility of switching to either a different view of the state is determined by the logic and twist models. View model also has no reference to the visual interface and the identifier of the next screen as a command parameter are forwarding through it in the Navigator class that is already responsible for the technical implementation of the navigation mechanism on one platform or another. The need for passing parameters is no longer, because, thanks to the principle of direct injection, each view already has access to almost any twist model.

The result is a pure twist model and quite transparent navigation logic and, most importantly, reliable. Perhaps, in some cases, still have to use behaind code, but the author was unable to come up with this single example of life, which would not be solved by the considered approach.