簡體   English   中英

WPF MVVM - 簡單登錄到應用程序

[英]WPF MVVM - Simple login to an application

我正在繼續學習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>

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

我知道單身人士被認為是反模式,但我覺得這對於像這樣的情況最容易。 這樣,單例類可以實現INotifyPropertyChanged接口,並在檢測到login \\ out事件時引發事件。

LoginViewModel或Singleton上實現LoginCommand (就個人而言,我可能會在ViewModel上實現這一點,以在ViewModel和“后端”實用程序類之間添加一定程度的分離)。 此login命令將調用單例上的方法來執行登錄。

Q2:在這些情況下,我通常有(又一個)單例類作為PageManagerViewModelManager 此類負責創建,處理和保持對頂級頁面或CurrentPage的引用(僅限單頁情況)。

我的ViewModelBase類還有一個屬性來保存顯示我的類的UserControl的當前實例,這樣我就可以掛鈎Loaded和Unloaded事件。 這使我能夠擁有可在ViewModel定義的虛擬OnLoaded(), OnDisplayed() and OnClosed()方法,以便頁面可以執行加載和卸載操作。

當MainWindowView顯示ViewModelManager.CurrentPage實例時,一旦此實例發生更改,Unloaded事件將觸發,我的頁面的Dispose方法將被調用,最終GC進入並整理其余部分。

問題3:我不確定我是否理解這一點,但希望你的意思是“當用戶未登錄時顯示登錄頁面”,如果是這種情況,你可以指示你的ViewModelToViewConverter在用戶未登錄時忽略任何指令在(通過檢查SecurityContext單例)而只顯示LoginView模板的情況下,如果您希望只有特定用戶有權查看或使用的頁面,您可以在構建View之前檢查安全要求,並更換它帶有安全提示。

對不起,答案很長,希望這有幫助:)

編輯:另外,你拼錯了“管理”


編輯評論中的問題

LoginManagerSingleton如何直接與MainWindowView對話。 不應該所有內容都通過MainWindowViewModel,以便MainWindowView上沒有任何代碼

對不起,澄清一下 - 我不是說LoginManager直接與MainWindowView交互(因為這應該只是一個視圖),而是LoginManager只是設置一個CurrentUser屬性來響應LoginCommand所做的調用,反過來引發PropertyChanged事件,MainWindowView(正在監聽更改)會相應地做出反應。

然后,當您實施IOC時,LoginManager可以調用PageManager.Open(new OverviewScreen()) (或PageManager.Open("overview.screen") ),例如將用戶重定向到用戶登錄后看到的默認屏幕。

LoginManager本質上是實際登錄過程的最后一步,View只是適當地反映了這一點。

此外,在輸入時,我發現不是擁有一個LoginManager單例,而是所有這些都可以放在PageManager類中。 只需要一個Login(string, string)方法,該方法在成功登錄時設置CurrentUser。

我理解PageManagerView的想法,基本上是通過PageManagerViewModel

我不會將PageManager設計為View-ViewModel設計,只是一個實現INotifyPropertyChanged的普通INotifyPropertyChanged單例應該可以做到這一點,這樣MainWindowView就可以對更改CurrentPage屬性做出反應。

ViewModelBase是您創建的抽象類嗎?

是。 我使用這個類作為我所有ViewModel的基類。

這個類包含

  • 所有頁面上使用的屬性,如Title,PageKey和OverriddenUserContext。
  • 常見的虛擬方法,如PageLoaded,PageDisplayed,PageSaved和PageClosed
  • 實現INPC並公開受保護的OnPropertyChanged方法以用於引發PropertyChanged事件
  • 並提供與頁面交互的框架命令,如ClosePageCommand,SavePageCommand等。

檢測到登錄后,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類,只需要那些頂級的。 所有其他頁面都將是其父級的子級,以便您更好地控制層次結構,並允許您逐步刪除“保存”和“關閉”事件。

如果將它們放在PageManager中的ObservableCollection<ViewModelBase>屬性中,則只需要創建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;
    }

通過以下部分閱讀此代碼,內容如下:

  • 如果value為null,則返回。 簡單的空引用檢查。
  • 如果該值是ViewModelBase,並且該頁面已加載,則只返回該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的View並返回它認為應該是的類型代碼(這可能為null)。
  • 如果這不是null,則創建該類型的新實例。
  • 如果我們找不到要使用的視圖,請嘗試為該ViewModel類型創建默認頁面。 我還有一些從ViewModelBase繼承的ViewModel基類,它們提供了頁面類型之間的職責分離。
    • 例如,SearchablePage類將只顯示特定類型系統中所有對象的列表,並提供“添加”,“編輯”,“刷新”和“篩選”命令。
    • MaintenancePage將從數據庫中檢索完整對象,動態生成和定位對象公開的字段的控件,根據對象具有的任何集合創建子頁面,並提供要使用的保存和刪除命令。
  • 如果我們仍然沒有要使用的模板,則拋出錯誤,以便開發人員知道出現了問題。
  • 在catch塊中,發生的任何運行時錯誤都會在友好的ErrorPage中顯示給用戶。

這一切都允許我專注於僅創建ViewModel類,因為應用程序將簡單地顯示默認頁面,除非View頁面已由開發人員為該ViewModel顯式覆蓋。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM