[英]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:該過程可行,但我不知道如何使用LoginModel
與MainWindowViewModel
。
你可以試試像LoginView -> LoginViewModel -> [SecurityContextSingleton || LoginManagerSingleton] -> MainWindowView
LoginView -> LoginViewModel -> [SecurityContextSingleton || LoginManagerSingleton] -> MainWindowView
我知道單身人士被認為是反模式,但我覺得這對於像這樣的情況最容易。 這樣,單例類可以實現INotifyPropertyChanged
接口,並在檢測到login \\ out事件時引發事件。
在LoginViewModel
或Singleton上實現LoginCommand
(就個人而言,我可能會在ViewModel
上實現這一點,以在ViewModel和“后端”實用程序類之間添加一定程度的分離)。 此login命令將調用單例上的方法來執行登錄。
Q2:在這些情況下,我通常有(又一個)單例類作為PageManager
或ViewModelManager
。 此類負責創建,處理和保持對頂級頁面或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的基類。
這個類包含
檢測到登錄后,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;
}
通過以下部分閱讀此代碼,內容如下:
返回模板。 然后,它將顯示在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; }
這是轉換器中執行大部分繁重工作的代碼,通讀您可以看到的部分:
這一切都允許我專注於僅創建ViewModel類,因為應用程序將簡單地顯示默認頁面,除非View頁面已由開發人員為該ViewModel顯式覆蓋。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.