Jon*_*lis 22 c# wpf user-interface mvvm
我正在继续学习WPF,目前专注于MVVM并使用Karl Shifflett的"MVVM In a Box"教程.但是有一个关于在views/viewmodels之间共享数据以及它如何在屏幕上更新视图的问题.ps我还没有报道过IOC.
下面是我在测试应用程序中的MainWindow的屏幕截图.它分为3个部分(视图),一个标题,一个带按钮的滑动面板,其余部分作为应用程序的主视图.应用程序的目的很简单,登录到应用程序.在成功登录后,登录视图应该被新视图(即OverviewScreenView)替换,并且应用程序幻灯片上的相关按钮应该可见.
我认为应用程序有2个ViewModel.一个用于MainWindowView,另一个用于LoginView,因为MainWindow不需要具有Login命令,所以我将它保持独立.
由于我还没有介绍过IOC,我创建了一个LoginModel类,它是一个单例.它只包含一个属性"public bool LoggedIn",以及一个名为UserLoggedIn的事件.
MainWindowViewModel构造函数注册到事件UserLoggedIn.现在在LoginView中,当用户在LoginView上单击Login时,它会在LoginViewModel上引发一个命令,如果正确输入了用户名和密码,则会调用LoginModel并将LoggedIn设置为true.这会导致UserLoggedIn事件触发,这将在MainWindowViewModel中处理,以使视图隐藏LoginView并将其替换为不同的视图,即概览屏幕.
问题
Q1.明显的问题,就是这样登录正确使用MVVM.即控制流程如下.LoginView - > LoginViewViewModel - > LoginModel - > MainWindowViewModel - > MainWindowView.
Q2.假设用户已登录,并且MainWindowViewModel已处理该事件.您将如何创建新视图并将其放在LoginView所在的位置,同样如何在不需要时处理LoginView.MainWindowViewModel中是否存在类似"UserControl currentControl"的属性,该属性设置为LoginView或OverviewScreenView.
Q3.MainWindow是否应该在visual studio设计器中设置LoginView.或者它应该留空,并以编程方式确认没有人登录,因此一旦加载MainWindow,它就会创建一个LoginView并在屏幕上显示它.
下面的一些代码示例是否有助于回答问题
MainWindow的XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns:local="clr-namespace:WpfApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="372" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<local:HeaderView Grid.ColumnSpan="2" />
<local:ButtonsView Grid.Row="1" Margin="6,6,3,6" />
<local:LoginView Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)
MainWindowViewModel
using System;
using System.Windows.Controls;
using WpfApplication1.Infrastructure;
namespace WpfApplication1
{
public class MainWindowViewModel : ObservableObject
{
LoginModel _loginModel = LoginModel.GetInstance();
private UserControl _currentControl;
public MainWindowViewModel()
{
_loginModel.UserLoggedIn += _loginModel_UserLoggedIn;
_loginModel.UserLoggedOut += _loginModel_UserLoggedOut;
}
void _loginModel_UserLoggedOut(object sender, EventArgs e)
{
throw new NotImplementedException();
}
void _loginModel_UserLoggedIn(object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
}
Run Code Online (Sandbox Code Playgroud)
LoginViewViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Input;
using WpfApplication1.Infrastructure;
namespace WpfApplication1
{
public class LoginViewViewModel : ObservableObject
{
#region Properties
private string _username;
public string Username
{
get { return _username; }
set
{
_username = value;
RaisePropertyChanged("Username");
}
}
#endregion
#region Commands
public ICommand LoginCommand
{
get { return new RelayCommand<PasswordBox>(LoginExecute, pb => CanLoginExecute()); }
}
#endregion //Commands
#region Command Methods
Boolean CanLoginExecute()
{
return !string.IsNullOrEmpty(_username);
}
void LoginExecute(PasswordBox passwordBox)
{
string value = passwordBox.Password;
if (!CanLoginExecute()) return;
if (_username == "username" && value == "password")
{
LoginModel.GetInstance().LoggedIn = true;
}
}
#endregion
}
}
Run Code Online (Sandbox Code Playgroud)
fat*_*tty 28
神圣的长问题,蝙蝠侠!
Q1:
这个过程会起作用,但我不知道如何使用它LoginModel
来与之交谈MainWindowViewModel
.
你可以试试像 LoginView -> LoginViewModel -> [SecurityContextSingleton || LoginManagerSingleton] -> MainWindowView
我知道单身人士被认为是反模式,但我觉得这对于像这样的情况最容易.这样,单例类可以在INotifyPropertyChanged
检测到login\out事件时实现接口并引发事件.
实现LoginCommand
on或者LoginViewModel
Singleton(就我个人而言,我可能会ViewModel
在ViewModel和"后端"实用程序类之间添加一定程度的分离).此login命令将调用单例上的方法来执行登录.
Q2:
在这些情况下,我通常有(又一个)单身人士作为PageManager
或ViewModelManager
.此类负责创建,处理和保持对顶级页面或CurrentPage的引用(仅限单页情况).
我的ViewModelBase
类还有一个属性来保存显示我的类的UserControl的当前实例,这样我就可以挂钩Loaded和Unloaded事件.这使我能够拥有OnLoaded(), OnDisplayed() and OnClosed()
可以定义的虚拟方法,ViewModel
以便页面可以执行加载和卸载操作.
当MainWindowView显示ViewModelManager.CurrentPage
实例时,一旦此实例发生更改,Unloaded事件将触发,我的页面的Dispose方法将被调用,并最终GC
进入并整理其余部分.
问题3:
我不确定我是否理解这个,但希望你的意思是"当用户没有登录时显示登录页面",如果是这种情况,你可以指示你ViewModelToViewConverter
在用户没有登录时忽略任何指令(通过检查SecurityContext单例)而只显示LoginView
模板,如果您希望只有特定用户有权查看或使用的页面,您可以在构建View之前检查安全要求,并将其替换为安全提示.
对不起,答案很长,希望这有帮助:)
编辑:另外,你拼错了"管理"
编辑评论中的问题
LoginManagerSingleton如何直接与MainWindowView对话.不应该所有内容都通过MainWindowViewModel,以便MainWindowView上没有任何代码
对不起,澄清一下 - 我不是说LoginManager直接与MainWindowView交互(因为这应该只是一个视图),而是LoginManager
只是设置一个CurrentUser属性来响应LoginCommand所做的调用,其中turn引发PropertyChanged事件,MainWindowView(正在侦听更改)做出相应的反应.
然后,LoginManager可以调用PageManager.Open(new OverviewScreen())
(或者PageManager.Open("overview.screen")
当您实现IOC时),例如将用户重定向到用户登录后看到的默认屏幕.
LoginManager本质上是实际登录过程的最后一步,View只是适当地反映了这一点.
此外,在输入时,我发现不是拥有一个LoginManager单例,而是所有这些都可以放在PageManager
类中.只需要一个Login(string, string)
方法,即在成功登录时设置CurrentUser.
我理解PageManagerView的想法,基本上是通过PageManagerViewModel
我不会将PageManager设计为View-ViewModel设计,只是一个普通的家用单例实现INotifyPropertyChanged
应该做的技巧,这样MainWindowView可以对更改CurrentPage属性做出反应.
ViewModelBase是您创建的抽象类吗?
是.我使用这个类作为我所有ViewModel的基类.
这个类包含
检测到登录后,CurrentControl将设置为新视图
就个人而言,我只会持有当前正在显示的ViewModelBase的实例.然后由ContentControl中的MainWindowView引用它,如下所示:Content="{Binding Source={x:Static vm:PageManager.Current}, Path=CurrentPage}"
.
然后我还使用转换器将ViewModelBase实例转换为UserControl,但这纯粹是可选的; 您可以只依赖ResourceDictionary条目,但此方法还允许开发人员拦截调用并在需要时显示SecurityPage或ErrorPage.
然后,当应用程序启动时,它会检测到没有人登录,因此创建一个LoginView并将其设置为CurrentControl.而不是强调它默认显示LoginView
您可以设计应用程序,以便向用户显示的第一个页面是OverviewScreen的实例.其中,由于PageManager当前具有null的CurrentUser属性,ViewModelToViewConverter将拦截此而不是显示OverviewScreenView UserControl,而是显示LoginView UserControl.
如果用户成功登录,LoginViewModel将指示PageManager重定向到原始的OverviewScreen实例,这次正确显示,因为CurrentUser属性为非null.
人们如何像其他人一样提到这个限制,单身人士是坏人
我和你在一起,我喜欢我一个好的单身人士.但是,这些的使用应限于仅在必要时使用.但是在我看来它们确实有完全有效的用途,但不确定是否还有其他人想要在这件事情上加入?
编辑2:
您是否为MVVM使用公开的框架/类集
不,我正在使用我在过去12个月左右创建和改进的框架.该框架仍然遵循大多数 MVVM指南,但包括一些个人触摸,减少了编写所需的整体代码量.
例如,一些MVVM示例就像你一样建立了他们的观点; 而View在其ViewObject.DataContext属性中创建ViewModel的新实例.这可能适用于某些人,但不允许开发人员从ViewModel挂钩某些Windows事件,如OnPageLoad().
我的案例中的OnPageLoad()是在页面上的所有控件都已创建后调用的,并且可以在调用构造函数后的几分钟内立即进入屏幕查看,或者根本不可以.例如,如果该页面在当前未选中的选项卡内有多个子页面,那么我可以在此处执行大部分数据加载以加快页面加载过程.
但不仅如此,通过以这种方式创建ViewModel,每个View中的代码量增加了至少三行.这可能听起来不是很多,但是这些代码行不仅对于创建重复代码的所有视图基本相同,而且如果您的应用程序需要许多视图,则额外的行数会非常快.那,我真的很懒.我没有成为开发人员输入代码.
通过您对页面管理器的想法,我将在未来的修订版中做的就是像tabcontrol一样打开几个视图,其中页面管理器控制页面块而不是单个userControl.然后,可以通过绑定到页面管理器的单独视图选择选项卡
在这种情况下,PageManager不需要直接引用每个打开的ViewModelBase类,只需要那些顶级的.所有其他页面都将是其父级的子级,以便您更好地控制层次结构,并允许您逐步删除"保存"和"关闭"事件.
如果将它们放在ObservableCollection<ViewModelBase>
PageManager 中的属性中,则只需要创建MainWindow的TabControl,以便它的ItemsSource属性指向PageManager上的Children属性,并让WPF引擎完成其余的工作.
你可以在ViewModelConverter上进一步扩展吗?
当然,为了给你一个大纲,显示一些代码会更容易.
public override object Convert(object value, SimpleConverterArguments args)
{
if (value == null)
return null;
ViewModelBase vm = value as ViewModelBase;
if (vm != null && vm.PageTemplate != null)
return vm.PageTemplate;
System.Windows.Controls.UserControl template = GetTemplateFromObject(value);
if (vm != null)
vm.PageTemplate = template;
if (template != null)
template.DataContext = value;
return template;
}
Run Code Online (Sandbox Code Playgroud)
通过以下部分阅读此代码,内容如下:
返回模板.然后,它将显示在ContentPresenter中供用户查看.
public static System.Windows.Controls.UserControl GetTemplateFromObject(object o)
{
System.Windows.Controls.UserControl template = null;
try
{
ViewModelBase vm = o as ViewModelBase;
if (vm != null && !vm.CanUserLoad())
return new View.Core.SystemPages.SecurityPrompt(o);
Type t = convertViewModelTypeToViewType(o.GetType());
if (t != null)
template = Activator.CreateInstance(t) as System.Windows.Controls.UserControl;
if (template == null)
{
if (o is SearchablePage)
template = new View.Core.Pages.Generated.ViewList();
else if (o is MaintenancePage)
template = new View.Core.Pages.Generated.MaintenancePage(((MaintenancePage)o).EditingObject);
}
if (template == null)
throw new InvalidOperationException(string.Format("Could not generate PageTemplate object for '{0}'", vm != null && !string.IsNullOrEmpty(vm.PageKey) ? vm.PageKey : o.GetType().FullName));
}
catch (Exception ex)
{
BugReporter.ReportBug(ex);
template = new View.Core.SystemPages.ErrorPage(ex);
}
return template;
}
Run Code Online (Sandbox Code Playgroud)这是转换器中执行大部分繁重工作的代码,通读您可以看到的部分:
这一切都允许我专注于仅创建ViewModel类,因为应用程序将简单地显示默认页面,除非View页面已由开发人员为该ViewModel显式覆盖.