我将继续学习WPF,目前主要关注MVVM,并使用Karl Shifflett的“盒子中的MVVM”教程。但是有一个关于在 View / View 模型之间共享数据以及如何更新屏幕上的 View 的问题。 ps我还没有介绍IOC。

下面是我在测试应用程序中的MainWindow的屏幕截图。它分为3个部分( View ),一个标题,一个带按钮的滑动面板,其余作为应用程序的主 View 。该应用程序的目的很简单,登录到该应用程序。成功登录后,登录 View 应消失,而将其替换为新 View (即OverviewScreenView),并且应用程序幻灯片上的相关按钮应变为可见。

我认为该应用程序具有2个ViewModel。一种用于MainWindowView,一种用于LoginView,因为MainWindow不需要具有用于登录的命令,因此我将其分开。

由于我还没有介绍IOC,所以我创建了一个Singleton的LoginModel类。它仅包含一个属性“public bool LoggedIn”和一个名为UserLoggedIn的事件。

MainWindowViewModel构造函数将注册到事件UserLoggedIn。现在在LoginView中,当用户单击LoginView上的Login时,它将在LoginViewModel上引发一个命令,如果正确输入用户名和密码,则该命令将调用LoginModel并将LoggedIn设置为true。这会引发UserLoggedIn事件,该事件在MainWindowViewModel中进行处理,从而导致该 View 隐藏LoginView并将其替换为其他 View (即概述屏幕)。

问题

Q1。一个明显的问题是,这样登录是对MVVM的正确使用。即控制流程如下。 LoginView-> LoginViewViewModel-> LoginModel-> MainWindowViewModel-> MainWindowView。

Q2。假设用户已经登录,并且MainWindowViewModel已处理该事件。您将如何创建一个新的View并将其放置在LoginView所在的位置,同样,一旦不需要它,您将如何处置LoginView。 MainWindowViewModel中是否会有诸如“UserControl currentControl”之类的属性,该属性将设置为LoginView或OverviewScreenView。

Q3。应该在Visual Studio设计器中设置MainWindow的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
    }
}

最佳答案

长久的问题, bat 侠!

Q1:
该过程将正常工作,但是我不知道如何使用LoginModelMainWindowViewModel对话。

您可以尝试类似LoginView -> LoginViewModel -> [SecurityContextSingleton || LoginManagerSingleton] -> MainWindowView
我知道单例被某些人视为反模式,但是我发现这种情况最简单。这样,单例类可以实现INotifyPropertyChanged接口(interface),并在检测到login\out事件时引发事件。

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

第2季:
在这些情况下,我通常(还有另一个)单例类充当PageManagerViewModelManager。此类负责创建,处置和保存对顶级页面或CurrentPage的引用(仅在单页面的情况下)。

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

当MainWindowView显示ViewModelManager.CurrentPage实例时,一旦该实例发生更改,就会触发Unloaded事件,调用我页面的Dispose方法,最后GC进入,其余部分保持整齐。

第三季度:
我不确定我是否理解这一点,但希望您的意思是“用户未登录时显示登录页面”,在这种情况下,您可以指示ViewModelToViewConverter在用户未登录时忽略任何指令(通过检查SecurityContext单例)并仅显示LoginView模板,这在您希望只有某些用户有权查看或使用的页面可以在构造View之前检查安全性要求并将其替换的情况下很有用。安全提示。

抱歉,答案很长,希望对您有所帮助:)

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

编辑评论中的问题



抱歉,需要澄清一下-我并不是说LoginManager直接与MainWindowView交互(因为这应该只是一个 View ),而是LoginManager只是设置一个CurrentUser属性来响应LoginCommand的调用,依次引发PropertyChanged事件和MainWindowView(正在监听更改)做出相应的 react 。

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

LoginManager本质上是实际登录过程的最后一步,而View恰好反射(reflect)了这一点。

另外,在键入此命令时,我想到,可以将所有这些都放置在PageManager类中,而不是使用LoginManager单例。只需使用Login(string, string)方法,该方法可在成功登录时设置CurrentUser。



我不会将PageManager设计为View-ViewModel设计,而只是实现INotifyPropertyChanged的普通家用单例就可以解决问题,这样MainWindowView可以对CurrentPage属性的更改使用react。



是的。我将此类用作所有ViewModel的基类。

该类(class)包含

  • 在所有页面上使用的属性,例如Title,PageKey和
    OverriddenUserContext。
  • 常见的虚拟方法,例如PageLoaded,PageDisplayed,PageSaved和PageClose
  • 实现INPC并公开一个 protected OnPropertyChanged方法,以用于引发PropertyChanged事件
  • 并提供与页面进行交互的框架命令,例如ClosePageCommand,SavePageCommand等。



  • 就个人而言,我只会保留当前正在显示的ViewModelBase的实例。然后,由MainWindowView在ContentControl中进行引用,如下所示:Content="{Binding Source={x:Static vm:PageManager.Current}, Path=CurrentPage}"

    然后,我还使用转换器将ViewModelBase实例转换为UserControl,但这纯粹是可选的。您可以仅依赖ResourceDictionary条目,但是此方法还允许开发人员拦截调用并在需要时显示SecurityPage或ErrorPage。



    您可以设计该应用程序,以便向用户显示的第一页是OverviewScreen的实例。由于PageManager当前具有null的CurrentUser属性,因此ViewModelToViewConverter会拦截该属性,而不是显示OverviewScreenView UserControl,而是显示LoginView UserControl。

    如果并且当用户成功登录时,LoginViewModel将指示PageManager重定向到原始OverviewScreen实例,由于CurrentUser属性为非null,因此这次可以正确显示。



    我和你在一起,我喜欢我一个好单例。但是,应将它们的使用限制为仅在必要时使用。但是我认为它们确实具有完全有效的用法,但是不确定是否有人愿意对此事进行干预?

    编辑2:



    不,我使用的是过去十二个月左右创建并完善的框架。该框架仍然遵循大多数MVVM准则,但是包含一些个人方面的内容,从而减少了需要编写的总体代码量。

    例如,一些MVVM示例建立的 View 与您的 View 基本相同。而View在其ViewObject.DataContext属性内创建ViewModel的新实例。对于某些人来说,这可能工作得很好,但不允许开发人员从ViewModel挂钩某些Windows事件,例如OnPageLoad()。

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

    不仅如此,通过以这种方式创建ViewModel,每个 View 中的代码量最少增加三行。这听起来可能并不多,但对于创建重复代码的所有 View ,这些代码行不仅对于本质上是相同的,而且如果您的应用程序需要很多 View ,那么额外的行数可能会很快增加。那,我真的很懒。.我没有成为打字代码的开发人员。



    在这种情况下,PageManager不需要保留对每个打开的ViewModelBase类的直接引用,而仅需要保留对顶级类的直接引用。所有其他页面都是其父级的子级,以使您能够更好地控制层次结构并允许您滴入Save和Close事件。

    如果将它们放在PageManager的ObservableCollection<ViewModelBase>属性中,则只需要创建MainWindow的TabControl,以便它的ItemsSource属性指向PageManager上的Children属性,并让WPF引擎完成其余的工作。



    当然,要给您一个大纲,显示一些代码会更容易。
        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;
        }
    

    阅读以下部分的代码:
  • 如果value为null,则返回。简单的空引用检查。
  • 如果该值为ViewModelBase,并且该页面已经加载,则只需返回该View。如果不这样做,则每次显示该页面时都会创建一个新的 View ,这将导致某些意外行为。
  • 获取页面模板UserControl(如下所示)
  • 设置PageTemplate属性,以便可以将此实例挂接,因此我们不会在每次通过时都加载新实例。
  • 将View DataContext设置为ViewModel实例,从现在开始,这两行完全替换了我之前在每个 View 中谈论的那三行。
  • 返回模板。然后,它将显示在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相对应的View,并返回它认为应该的类型代码(可以为null)。
  • 如果不为空,则创建该类型的新实例。
  • 如果我们找不到要使用的View,请尝试为该ViewModel类型创建默认页面。我还有一些其他的ViewModel基类,这些基类继承自ViewModelBase,它们提供了页面类型之间的职责分离。
  • 例如,SearchablePage类将仅显示特定类型系统中所有对象的列表,并提供“添加”,“编辑”,“刷新”和“过滤器”命令。
  • MaintenancePage将从数据库中检索完整的对象,动态生成和定位该对象公开的字段的控件,基于该对象具有的任何集合创建子页面,并提供要使用的Save和Delete命令。
  • 如果我们仍然没有要使用的模板,请抛出一个错误,以便开发人员知道出了点问题。
  • 在catch块中,发生的任何运行时错误都会在友好的ErrorPage中向用户显示。

  • 所有这些使我可以只专注于创建ViewModel类,因为应用程序将简单地显示默认页面,除非开发人员已针对该ViewModel明确覆盖了View页面。

    关于c# - WPF MVVM-简单登录到应用程序,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/7114687/

    10-11 06:57