简单登录到应用程序

我正在继续学习WPF,并且专注于MVVM,并使用Karl Shifflett的“MVVM In a Box”教程。 但是有一个关于在views / viewmodels之间共享数据的问题,以及它如何更新屏幕上的视图。 ps我还没有涉及国际奥委会的。

以下是我的MainWindow在测试应用程序中的截图。 它分为3个部分(视图),一个标题,一个带按钮的滑动面板,其余部分作为应用程序的主视图。 该应用程序的目的很简单,即登录到应用程序。 在成功登录时,登录视图应该被新视图(即OverviewScreenView)取代,并且应用程序幻灯片上的相关按钮应该可见。

主窗口

我将该应用程序视为具有2个ViewModel。 一个用于MainWindowView,另一个用于LoginView,因为MainWindow不需要登录命令,所以我保持独立。

由于我还没有涉及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>

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();
        }
    }
}

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
    }
}

神圣的长期问题,蝙蝠侠!

Q1:这个过程会起作用,但我不知道如何使用LoginModelMainWindowViewModel对话。

你可以尝试像LoginView -> LoginViewModel -> [SecurityContextSingleton || LoginManagerSingleton] -> MainWindowView LoginView -> LoginViewModel -> [SecurityContextSingleton || LoginManagerSingleton] -> MainWindowView

我知道单身人士被某些人认为是反模式,但我发现这对于这些情况来说是最简单的。 这样,singleton类可以实现INotifyPropertyChanged接口,并在检测到登录 out事件时引发事件。

LoginViewModel或Singleton上实现LoginCommand (就个人而言,我可能会在ViewModel上实现此功能,以在ViewModel和“后端”实用程序类之间添加一定程度的分隔)。 这个登录命令会调用单例中的一个方法来执行登录。

Q2:在这些情况下,我通常有(另一个)单例类作为PageManagerViewModelManager 。 此类负责创建,处理和保存对顶级页面或当前页面的引用(仅在单页面情况下)。

我的ViewModelBase类也有一个属性来保存显示我的类的UserControl的当前实例,这样我就可以挂接Loaded和Unloaded事件。 这使我能够在ViewModel定义虚拟OnLoaded(), OnDisplayed() and OnClosed()方法,以便页面可以执行加载和卸载操作。

当MainWindowView显示ViewModelManager.CurrentPage实例时,一旦这个实例改变,Unloaded事件触发,我的页面的Dispose方法被调用,并且最终GC进入并整理剩下的事情。

问题3:我不确定是否理解这一点,但希望您的意思是“用户未登录时显示登录页面”,如果是这种情况,您可以指示ViewModelToViewConverter在用户未登录时忽略任何指示(通过检查SecurityContext单例),而仅显示LoginView模板,这对于希望只有特定用户才有权查看或使用的页面可以在构建视图之前检查安全性需求,以及替换它带有安全提示。

对不起,很长的回答,希望这有助于:)

编辑:另外,你拼错了“管理”


编辑评论中的问题

LoginManagerSingleton如何直接与MainWindowView对话。 不应该一切都通过MainWindowViewModel,以便在MainWindowView上没有任何代码

对不起,澄清 - 我并不是说LoginManager直接与MainWindowView交互(因为它应该只是一个视图),而是LoginManager只是设置一个CurrentUser属性来响应LoginCommand所做的调用,反过来引发PropertyChanged事件,并且MainWindowView(正在侦听更改)相应地作出反应。

然后,LoginManager可以在实现IOC时调用PageManager.Open(new OverviewScreen()) (或PageManager.Open("overview.screen") ),例如将用户重定向到用户一旦登录后看到的默认屏幕。

LoginManager基本上是实际登录过程的最后一步,View只是适当地反映了这一点。

另外,在输入时,我想到,不是有一个LoginManager单例,而是所有这些都可以存放在PageManager类中。 只需要有一个Login(string, string)方法,它可以在成功登录时设置CurrentUser。

我理解基本上通过PageManagerViewModel的PageManagerView的想法

我不会将PageManager设计为View-ViewModel设计,只是一个实现INotifyPropertyChanged的普通家庭单身人士应该这样做,这样MainWindowView可以对改变CurrentPage属性做出反应。

ViewModelBase是您创建的抽象类吗?

是。 我使用这个类作为我所有ViewModel的基类。

这个类包含

  • 所有页面上使用的属性,例如Title,PageKey和OverriddenUserContext。
  • 常见的虚拟方法,如PageLoaded,PageDisplayed,PageSaved和PageClosed
  • 实现INPC并公开一个受保护的OnPropertyChanged方法用于引发PropertyChanged事件
  • 并提供框架命令与页面进行交互,如ClosePageCommand,SavePageCommand等。
  • 当检测到登录时,CurrentControl被设置为新的View

    就个人而言,我只能保存当前正在显示的ViewModelBase的实例。 然后,ContentControl中的MainWindowView将引用此Content="{Binding Source={x:Static vm:PageManager.Current}, Path=CurrentPage}"如下所示: 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属性非空。

    当你像其他人一样提到时,人们如何绕过这个限制,单身人士是不好的

    我和你在一起,我喜欢我一个很好的单身人士。 但是,这些应用仅限于必要时使用。 但在我看来,它们的确具有完全有效的用途,但不确定是否有其他人想要在这个问题上发表意见?


    编辑2:

    您是否使用公共可用框架/ MVVM类的集合?

    不,我正在使用我在过去十二个月左右创建和完善的框架。 该框架仍然遵循MVVM指南的大部分内容,但包含一些个人风格,可减少需要编写的总体代码量。

    例如,在那里的一些MVVM示例建立的视图与您的视图非常相似; 而视图在其ViewObject.DataContext属性内创建ViewModel的新实例。 这可能适用于某些人,但不允许开发人员从ViewModel(如OnPageLoad())钩住某些Windows事件。

    在我的情况下,OnPageLoad()在页面上的所有控件都已创建并进入屏幕上后立即调用,在调用构造函数后的几分钟内调用,或者根本不会。 例如,如果该页面在当前未选中的选项卡内具有多个子页面,则这是我加载大部分数据以加快页面加载过程的位置。

    但不仅如此,通过以这种方式创建ViewModel,可以将每个视图中的代码量增加至少三行。 这听起来可能不是很多,但是这些代码行不仅对于创建重复代码的所有视图基本相同,而且如果您有需要许多视图的应用程序,则额外行数可以相当快地累加起来。 那个,我真的很懒。我没有成为开发人员来输入代码。

    通过页面管理器的想法,我将在未来的修订中做的事情是将多个视图像tabcontrol一样打开,其中页面管理器控制页面标签,而不是仅控制一个userControl。 然后,标签可以通过绑定到页面管理器的单独视图进行选择

    在这种情况下,PageManager不需要直接引用每个打开的ViewModelBase类,只有那些位于顶层的类。 所有其他页面都将是其父级的子级,以便您可以更好地控制层次结构并允许您逐步保存和关闭事件。

    如果你把这些在ObservableCollection<ViewModelBase>在PageManager中的财产,你才需要创建主窗口的TabControl的,这样它的ItemsSource属性点对PageManager中的儿童财产,有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;
        }
    

    通过这段代码读取部分内容:

  • 如果值为null,则返回。 简单的空引用检查。
  • 如果该值是一个ViewModelBase,并且该页面已经被加载,那么返回该View。 如果你不这样做,每次页面显示时你都会创建一个新的View,并且会导致一些意想不到的行为。
  • 获取页面模板UserControl(如下所示)
  • 设置PageTemplate属性,以便可以挂钩此实例,因此我们不会在每次传递时加载新实例。
  • 将View DataContext设置为ViewModel实例,这两行完全替代了我之前从每个视图中讨论的那三行。
  • 返回模板。 然后这将显示在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;
    }
    
  • 这是转换器中的代码,它可以完成大部分的咕噜作业,阅读您可以看到的部分:

  • 主try..catch块用于捕捉任何类的构造错误,包括,
  • 页面不存在,
  • 构造函数代码中的运行时错误,
  • XAML中的致命错误。
  • convertViewModelTypeToViewType()只是试图找到与ViewModel相对应的视图,并返回它认为应该是的类型代码(可能为null)。
  • 如果这不是null,则创建一个新类型的实例。
  • 如果我们无法找到要使用的视图,请尝试为该ViewModel类型创建默认页面。 我有一些从ViewModelBase继承的ViewModel基类,它们提供了页面类型之间职责的分离。
  • 例如,SearchablePage类将只显示某个类型的系统中的所有对象的列表,并提供Add,Edit,Refresh和Filter命令。
  • 维护页面将从数据库中检索完整对象,动态生成并定位对象公开的字段的控件,根据对象所具有的任何集合创建子页面,并提供要使用的“保存”和“删除”命令。
  • 如果我们还没有使用模板,请抛出一个错误,以便开发人员知道出了什么问题。
  • 在catch块中,发生的任何运行时错误都会在友好的ErrorPage中显示给用户。
  • 这一切都使我可以专注于创建ViewModel类,因为应用程序将简单显示默认页面,除非ViewModel的开发人员明确覆盖了View页面。

    链接地址: http://www.djcxy.com/p/39541.html

    上一篇: Simple login to an application

    下一篇: Delay jquery hover event?