Close or deactivate screen on shell

Jan 18, 2011 at 8:17 PM

Hi, I've seen the example of the simple navigation using conductors but I have a question about how to load different views in the content control, if I try to load a new view into the content control this isn't displayed but the previous view, I think I have to close or unload the previous view, I've seen that I can do that by using CloseItem or using a screen collection  to manage the views, the problem is that I can't find an example about how to accomplish that. If someone could tell me how to close or deactivate the view loaded int the content control, of the shell view to load another view I'll very grateful.

 

Coordinator
Jan 18, 2011 at 8:20 PM

To load a new view, you just call ActivateItem on the conductor and pass in the view model instance that you want to display. The framework will locate the view and inject it into the content control. The navigation sample is under source now. You can have a look at it there and use that to play around with adding new screens.

Jan 18, 2011 at 8:54 PM

The structure that I have is:

A shell view that load a login window, where after some validation redirect to the main window, the code snippet I'm using is, but the viewmodel I'm instantiating in LoginViewModel.cs is not loaded into the content control,  I'm doing something wrong?:

 

ShellView.xaml

<UserControl x:Class="NavigationTestMicro.Views.ShellView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:tc="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit">
    <tc:DockPanel>
        <ContentControl x:Name="ActiveItem" />
    </tc:DockPanel>
</UserControl>


ShellViewModel.cs

public class ShellViewModel : Conductor<object>
    {
        public ShellViewModel()
        {
            ActivateItem(new LoginViewModel());
        }

    }


LoginViewModel.cs

public class LoginViewModel : Conductor<object>
    {
        string username;

        public LoginViewModel()
        {
        }

        public string Username
        {
            get { return username; }
            set
            {
                username = value;
                NotifyOfPropertyChange(() => Username);
                NotifyOfPropertyChange(() => CanLogin);
            }
        }

        public bool CanLogin
        {
            get { return !string.IsNullOrWhiteSpace(Username); }
        }

        public void ShowMainPage()
        {
            ActivateItem(new MainWindowViewModel());
            //ShellViewModel shell = new ShellViewModel();
            //shell.ActivateItem(new MainWindowViewModel());
        }

        public void Autenticar()
        {
            if (Username.Equals("user"))
                this.ShowMainPage();
            else
                MessageBox.Show("Wrong");
        }
    }


LoginView.xaml

<UserControl x:Class="NavigationTestMicro.Views.LoginView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:tc="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit">
    <tc:DockPanel>
        <StackPanel Orientation="Vertical"
                    HorizontalAlignment="Center"
		            tc:DockPanel.Dock="Top">
            <TextBox x:Name="Username" />
            <Button x:Name="Autenticar"
			        Content="Login" />
        </StackPanel>
    </tc:DockPanel>
</UserControl>


MainWindowViewModel.cs


    public class MainWindowViewModel : Conductor<object>
    {
        public MainWindowViewModel()
        {
        }
    }



Coordinator
Jan 18, 2011 at 9:04 PM
Edited Jan 18, 2011 at 9:04 PM

Yes. The problem is that you are inheriting everything from conductor. The shell is the conductor. It is conducting which screen are shown. You should call ActivateItem on your shell rather than on your LoginViewModel. LoginViewModel only needs to inherit from PropertyChangedBase.

Jan 18, 2011 at 9:11 PM

LoginViewModel.ShowMainPage method should invoke ActivateItem on its parent (ShellViewModel).
Calling ActivateItem against LoginViewModel has no effect since the corresponding LoginView has no "hosting ContentControl" to display child content.
Actually, as I understand your scenario, LoginViewModel shouldn't be a Conductor<> at all, just a simple Screen.

LoginVM might call ActivateItem on its parent through the Parent property; this way, however, you may end introducing unwanted coupling between LoginVM and ShellVM.
As an alternative approach you might provide an event or a callback delegate on LoginVM to signal the login completion (and its result) externally. The ShellVM could subscribe the event to switch the current view after the login has been completed.

Jan 18, 2011 at 9:16 PM

Hi, Thanks for your quick reply

I've fixed the inheritance to PropertyChangedBase, but  If I want to load the MainWindowViewModel due a validation method in the LoginViewModel how can I call the desired view model, I've tried by creating an object of type shellviewmodel that instantiate the MainWindowViewModel but it don't seems to work.

 

 

Jan 18, 2011 at 11:06 PM

I don't know how to implement the suggested solution using the Parent or a delegate event could you please show some code snippet showing it??

Jan 19, 2011 at 9:09 AM

You don't have to instantiate a *new* ShellVM; you have to call ActivateItem on the *existing* ShellVM, thus asking it to replace the current VM (which is LoginVM currently) with another one (an instance of MainWindowViewModel). You may want to have a look to the related documentation for an explanation of how Conductors work.
That being said, you need to reach the parent ShellVM to invoke ActivateItem on it.
This could be done in a very simple way using the IChild.Parent property:

	public void ShowMainPage()
        {
            ShellViewModel shell = (ShellViewModel)this.Parent;
            shell.ActivateItem(new MainWindowViewModel());
        }

Note that LoginViewModel have to implement IChild or to inherit from Screen.

Though simple, I don't like this solution too much because it implies that LoginVM is hosted into ShellVM.
The other option I suggested is providing an event on LoginVM and use it from the calling side:

public class LoginViewModel : Screen
{
    public event EventHandler LoginCompleted = delegate {};
	
    public void Autenticar()
    {
	if (Username.Equals("user"))
            LoginCompleted(this, EventArgs.Empty);
        else
	    MessageBox.Show("Wrong");
    }
}


public class ShellViewModel : Conductor<object>
{
	LoginViewModel _login;
	public ShellViewModel()
	{
		_login = new LoginViewModel();
		_login.LoginCompleted += OnLoginCompleted;
		ActivateItem();
	}

	void OnLoginCompleted(object sender, EventArgs args) {
		_login.LoginCompleted -= OnLoginCompleted;
		ActivateItem(new MainWindowViewModel());
	}
}

Jan 19, 2011 at 2:18 PM

I have a question about the ActivateItem() method with no parameters that you call, I'm not very sure about what that method do

Coordinator
Jan 19, 2011 at 4:12 PM

I think he meant that to be:

ActivateItem(_login);

Jan 19, 2011 at 9:26 PM

Exactly ;-)