说我有一个显示用户好友列表的应用程序。好友列表以TabItem显示。用户必须先登录服务器才能获取好友列表。
我创建了两个用户控件,一个用于用户登录时,另一个用于未登录时。这条线上的东西:
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>
主窗口包含以下代码:
....
<TabItem Header="Friends">
<vw:UnloggedView />
</TabItem>
我相信一切都符合MVVM的原则。 LoginCmd是在ViewModel中实现的DelegateCommand(来自prism)的简化变体。两个视图都正常工作,并且在填充(异步)列表时,会触发通知并更新视图。我很高兴。
所以我有两个问题:第一个问题是我如何激活LoginWindow(提示用户输入他的凭据?现在,我只是创建LoginWindow(一个视图对象)并将其与ShowDialog一起呈现。看起来我通过直接操作ViewModel中的UI来打破MVVM的规则。
主要问题是我登录服务器后,用LoggedView替换TabItem内容的正确方法是什么。根据MVVM主体,ViewModel不应该对View的内部有所了解。我在ViewModel中暴露了IsLogged属性(它将触发PropertyChanged通知)但是我应该绑定什么才能使一切发生?我真的不希望ViewModel操纵View。
由于
答案 0 :(得分:9)
我看到这个问题出现了很多,并写了一些关于switching between Views/UserControls here的内容。通常我使用ContentControl
并根据ContentTemplate
切换DataTrigger
,但是相同的主体可以切换TabControl的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>
您可能必须在DataTrigger绑定中使用ElementName
或RelativeSource
来查找IsLoggedIn
DataContext
属性
对于从Logged Out视图触发Login Command,有多种方法可以执行此操作。
我首选的方法是使用某种消息传递系统,例如MVVM Light的Messenger或Microsoft Prism的EventAggregator,并在单击按钮时触发某种ShowLoginDialog
消息,然后让ViewModel负责显示登录对话框订阅接收这些消息并处理它们。
另一种方法是使用RelativeSource
绑定来查找Visual Tree中的对象,该对象的DataContext中包含LoginCommand
,并绑定到该对象。
您可以看到here
的示例答案 1 :(得分:1)
首先我会回答你第二个问题......只需创建一个Enum作为SessonState
enum SesionState
{
LoggedOut=0,
LoggedIn
}
之后,在ViewModel中为名为SessionState的窗口创建一个属性,并在Login和LogOut时使用所需值更新该属性。
<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>
答案 2 :(得分:0)
第一个问题:您只需使用viewmodel中的ILogindialogservice即可。我使用following对话框和mvvm。它的“单元测试”能够并且不会破坏mvvm。
EDIT。在你的viewmodel中,你会有一个这样的行:
var result = this.loginservice.ShowDialog("Login", loginvm);