Switching between views according to state

2019-05-14 18:22发布

问题:

Say I've an application that displays user friends list. The friends list is displayed in as a TabItem. The user has to Log in first to the server, in order to get the list of friends.

I've created two user controls, one for when the user is logged in, the the other when he is unlogged. something alone this line:

UnloggedView.xaml

<UserControl x:Class="UnloggedView" ...>
    <TextBlock ...>You need to <Hyperlink Command="{Binding LoginCmd}">
        Login</Hyperlink>too see your friends list</TextBlock>
</UserControl>

LoggedView.xaml:

<UserControl x:Class="LoggedView" ...>
    ...
    <ListView ItemSource={Binding Path=friends}">...
</UserControl>

The main window has the following code:

....
<TabItem Header="Friends">
    <vw:UnloggedView />
</TabItem>

I believe everything is according to the MVVM principal. The LoginCmd is a simplified variation of the DelegateCommand (from prism), implemented in the ViewModel. Both Views works fine, and as the list is populated (asynchronously), notification are fired and the View is updated. I'm happy.

So I've two questions: First question is how to I fire the LoginWindow (where the user is prompted to enter his credentials? For now, I simply create the LoginWindow (a view object) and presents it with ShowDialog. It appears like I'm breaking the rules of MVVM here by directly manipulating the UI from the ViewModel.

main question is after I log-in with the server, what is the correct way to replace the content of the TabItem with the LoggedView. According to the MVVM principals, the ViewModel shouldn't not have knowledge to the internals of the View. I expose IsLogged property in the ViewModel (which will fire PropertyChanged notification) but what should I bind to what in order to make everything happens? I really don't want the ViewModel manipulating the View.

Thanks

回答1:

I see this question come up a lot, and have written something about switching between Views/UserControls here. Usually I use a ContentControl and switch out the ContentTemplate based on a DataTrigger, however the same principal works to switch a TabControl's ItemTemplate

<DataTemplate x:Key="LoggedOutTemplate">
     <local:UnloggedView />
</DataTemplate> 

<DataTemplate x:Key="LoggedInTemplate">
     <local:LoggedView />
 </DataTemplate>

 <TabControl>
     <TabControl.Style>
         <Style TargetType="{x:Type TabControl}">
             <Setter Property="ItemTemplate" Value="{StaticResource LoggedOutTemplate}" />
             <Style.Triggers>
                 <DataTrigger Binding="{Binding IsLoggedIn}" Value="True">
                     <Setter Property="ItemTemplate" Value="{StaticResource LoggedInTemplate}" />
                 </DataTrigger>
             </Style.Triggers>
         </Style>
     </TabControl.Style>
 </TabControl>

You may have to use ElementName or RelativeSource in your DataTrigger binding to find the IsLoggedIn property in your DataContext

As for firing a Login Command from your Logged Out view, there are multiple ways of doing it.

My preferred method is to use some kind of messaging system such as MVVM Light's Messenger, or Microsoft Prism's EventAggregator, and firing some kind of ShowLoginDialog message when the button is clicked, then let whatever ViewModel is taking care of showing the login dialog subscribe to receive those messages and handle them.

Another way is simply use a RelativeSource binding to find the object in the Visual Tree that has the LoginCommand in it's DataContext, and bind to that.

You can see examples of both here



回答2:

First I'll answer you second question... Just create an Enum as SessonState

 enum SesionState
 {
      LoggedOut=0,
      LoggedIn
 }

After that create a property in your ViewModel for window called SessionState and Update that Property with the required value when you Login and LogOut.

Xaml required for switching views

<Window>
        <Window.Resources>        
    <DataTemplate x:Key="LoggedOutView">
        <ViewLayer: LoggedOutView/>
    </DataTemplate>
    <DataTemplate x:Key="LoggedInView">
        <ViewLayer:LoggedInView/>
    </DataTemplate>
    <Style x:Key="mainContentControlStyle"
           TargetType="{x:Type ContentControl}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=SessionState}"
                         Value="0">
                <Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=LoggedOutView}" />
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=Mode}"
                         Value="1">
                <Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=LoggedInView}" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
    <Grid>
        <TabControl>
           <TabItem>
                        <ContentControl Grid.Row="0"
                    Content="{Binding}"
                    Style="{StaticResource ResourceKey=mainContentControlStyle}">  
           </TabItem>
        </TabControl>
    </Grid>
</Window>


回答3:

for your 1st question: you can simply use a ILogindialogservice from your viewmodel. i use the following for dialogs and mvvm. its "Unit Test"able and not breaking mvvm.

EDIT. in your viewmodel you would have then a line something like this:

var result = this.loginservice.ShowDialog("Login", loginvm);


标签: wpf mvvm