2
votes

I have searched and tried for days and finally must ask the question here. I have a Silverlight 5 application, Using MVVM Light, where I want to be able to dynamically switch views in the main view.

For the sake of simplicity, lets say I have 2 buttons.

Button1 will switch to TestView1.

Button2 will switch to TestView2.

 <Button Content="TestView1" Grid.Column="1" Command="{Binding CallTestView1Command}" HorizontalAlignment="Left" Margin="185,17,0,0" VerticalAlignment="Top" Width="75"/>
    <Button Content="TestView2" Grid.Column="1" Command="{Binding CallTestView2Command}" HorizontalAlignment="Left" Margin="280,17,0,0" VerticalAlignment="Top" Width="75"/>

The way I have done it is by binding a relaycommand to the button and then instanciating a new viewmodel of the corresponding view. ie:

private RelayCommand _callTestView1Command;
    public RelayCommand CallTestView1Command
    {
        get
        {
            return _callTestView1Command ??
                   (_callTestView1Command = new RelayCommand(() =>
                       {
                           CurrentView = ViewModelLocator.NinjectKernel.Get<TestViewModel1>();
                       }));
        }
    }

The CurrentViewmodel is then set to the new viewmodel. In the MainView I have bound the CurrentView to a ContentControl:

<Border x:Name="displayedView" Grid.Row="2">
        <ContentControl Content="{Binding CurrentView}" />
    </Border>

This will actually work to some extend, since the CurrentView will change but instead of actually showing the content of the view it simply shows the Namespace of the ViewModel that is instanciated.

So far I have primarily used the knowledge taken from these sources:

http://rachel53461.wordpress.com/2011/05/28/switching-between-viewsusercontrols-using-mvvm/

Loading Views into ContentControl and changing their properties by clicking buttons

but they do not solve my problem, or I do not quite understand how to actually show the views.:-(

So does anyone have a good explanation on how to switch the views correct in Silverlight 5 using MVVM Light from GalaSoft.

Thanks

4
in CallTestView1Command first set CurrentView = null; then assign new item to it, it's maybe solve this problemMasoomian

4 Answers

2
votes

The part you are missing is the DataTemplates that tell WPF how to render your ViewModels

<Window.Resources>
    <DataTemplate TargetType="{x:Type local:TestViewModel1}">
        <local:TestView1 />
    </DataTemplate>

    <DataTemplate TargetType="{x:Type local:TestViewModel2}">
        <local:TestView2 />
    </DataTemplate>
</Window.Resources>

When you insert an object in the Visual Tree, such as placing a ViewModel object in ContentControl.Content, it will get drawn by default using a TextBlock bound to the .ToString() of the object, which is why you are only seeing the namespace.classname of the ViewModel in your ContentControl

By defining an implicit DataTemplate in your Resources somewhere (that's a DataTemplate with only a TargetType defined - no x:Key), you are telling WPF to draw the specified object using the specified DataTemplate anytime it tries to draw that object, instead of using the default TextBlock bound to the .ToString() of the object.

It should be noted that implicit DataTemplates are not supported in earlier versions of Silverlight, however they are supported in 5.0+. For earlier versions of Silverlight, I usually use a DataTemplateSelector instead.

1
votes

Id first suggest that you do not display your views via a ContentControl but look into using the navigation Frame in the silverlight toolkit. Also, we dont want our ViewModel creating Views... that'd not be so good. We don't mind, however, if our ViewModel does business logic and DETERMINES which view to show. Get the toolkit here: http://silverlight.codeplex.com/

Now setup your XAML as so in your main page:

<Border x:Name="displayedView" Grid.Row="2"> 
        <navigation:Frame x:Name="ContentFrame" /> 
    </Border> 

Since you are using MVVM Light, we will use messaging. Your View model will get the command to change views, determine which view to change, then send a message to the main page to instruct it to change views.

Setup a listener in your main page for a navigate request as so:

public MainPage()
{
    InitializeComponent();  
    Messenger.Default.Register<Uri>(this, "NavigationRequest", (uri) => ContentFrame.Navigate(uri));
}

Next, setup your command in your view model.

    private RelayCommand _callTestView1Command;         
public RelayCommand CallTestView1Command         
{         
    get         
    {         
        return _callTestView1Command ??         
               (_callTestView1Command = new RelayCommand(() =>         
                   {         
                        Messenger.Default.Send<Uri>(new Uri("/Views/.../Page.xaml", UriKind.Relative), "NavigationRequest");

                   }));         
    }         
}   

These are the basics that work for me. You can expand on this and get real "architecty". For example, you can create a base class for you view models that sends the navigation requests, create a helper class that generates URIs (so they are not hard coded everywhere in your app, etc etc. Good luck!

1
votes

So i actually solved this problem, in a way where there is no need to create datatemplates in the MainView, which i did not like. imo the MainView should know nothing about the views it is displaying, when we are talking about switching the views.

Prerequisite: You must use MVVM Light from GalaSoft for this solution

This is my test solution: Two buttons are added to my MainView, Each button will open a new view. The clickevent are bound to Commands.

<Button Content="TestView1" Grid.Column="1" Command="{Binding CallTestView1Command}" HorizontalAlignment="Left" Margin="185,17,0,0" VerticalAlignment="Top" Width="75"/>
<Button Content="TestView2" Grid.Column="1" Command="{Binding CallTestView2Command}" HorizontalAlignment="Left" Margin="280,17,0,0" VerticalAlignment="Top" Width="75"/>

In the MainView i have a Border that should contain the views than can switch. Since all views inherit from UserControl i bind the content to the property CurrentView of the MainViewModel

    <Border x:Name="displayedView" Grid.Row="2">
        <UserControl Content="{Binding CurrentView}" />
    </Border>

In the MainViewModel i have the property CurrentView.

 public const string CurrentViewPropertyName = "CurrentView";

    private UserControl _currentView;

    /// <summary>
    /// Sets and gets the "CurrentView property.
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary>
    public UserControl CurrentView
    {
        get
        {
            return _currentView;
        }

        set
        {
            if (_currentView == value)
            {
                return;
            }

            RaisePropertyChanging(CurrentViewPropertyName);
            _currentView = value;
            RaisePropertyChanged(CurrentViewPropertyName);
        }
    }

When a button is clicked the corresponding Command is called in the MainViewModel:

        private RelayCommand _callTestView1Command;
    public RelayCommand CallTestView1Command
    {
        get
        {
            return _callTestView1Command ??
                   (_callTestView1Command = new RelayCommand(() =>
                       {
                           CurrentView = new TestView1();
                       }));
        }
    }

    private RelayCommand _callTestView2Command;
    public RelayCommand CallTestView2Command
    {
        get
        {
            return _callTestView2Command ??
                   (_callTestView2Command = new RelayCommand(() =>
                   {
                       CurrentView = new TestView2();
                   }));
        }
    }

As seen each command will set CurrentView to a new view, and the views will switch in the MainView, because CurrentView will raise a ProperTyChanged Event.

0
votes

This will actually work to some extend, since the CurrentView will change but instead of actually showing the content of the view it simply shows the Namespace of the ViewModel that is instanciated.

Because you are changing the CurrentView property to a viewmodel instance and bind that as the Content. This is wrong as the Content should be a view and you should set the DataContext of that view to a viewmodel.

The simplest thing you can do here is to create a View instance inside the command and set the viewmodel as its DataContext and then you can set the view to the CurrentView property. Of course this would violate the MVVM pattern so you should move this responsibility to a separate component. Instead of writing your own navigating logic I suggest you to pick up an existing solution as this kind of task is not as straightforward as it seems.

I suggest to use the Prism library