WPF c#成功登录后,如何切换到仪表板视图?这是m代码

问题描述

这是我的xaml代码。尝试成功登录后,我想更新当前视图。

<Grid>
    <Grid.RowDeFinitions>
        <RowDeFinition Height="auto"/>
        <RowDeFinition Height="auto"/>
        <RowDeFinition Height="*"/>
    </Grid.RowDeFinitions>
    <Button Content="Login Page" 
            Command="{Binding UpdateCurrentviewmodelCommand}"
            CommandParameter="LoginView"/>
    <Button Content="Register Page" 
            Command="{Binding UpdateCurrentviewmodelCommand}"
            CommandParameter="RegisterView"
            Grid.Row="1"/>
    <ContentControl Content="{Binding CurrentView}" Grid.Row="2"/>
</Grid>
public class Mainviewmodel : Baseviewmodel
{
    private Baseviewmodel _currentView = new Loginviewmodel();
    public Baseviewmodel CurrentView
    {
        get
        {
            return _currentView;
        }
        set
        {
            _currentView = value;
            OnPropertyChanged(nameof(CurrentView));
        }
    }
    public ICommand UpdateCurrentviewmodelCommand { get; set; }
    public Mainviewmodel()
    {
        UpdateCurrentviewmodelCommand = new RelayCommand(UpdateCurrentView);
    }

    private void UpdateCurrentView(object obj)
    {
        if (obj.ToString() == "LoginView")
        {
            CurrentView = new Loginviewmodel();
        }
        else if (obj.ToString() == "RegisterView")
        {
            CurrentView = new Registerviewmodel();
        }
        else if (obj.ToString() == "DashboardView")
        {
            CurrentView = new Dashboardviewmodel();
        }
    }
}

这里,当用户登录时,它应该更新当前视图,它正在执行命令,我也正在获取命令参数中的值,并且还更新了Mainviewmodel中的属性CurrentView,但是问题是,不是正在更新视图未显示的UI ...

public class Loginviewmodel : Baseviewmodel
{
    private string _email;
    public string Email
    {
        get
        {
            return _email;
        }
        set
        {
            _email = value;
            OnPropertyChanged(nameof(Email));
        }
    }
    private string _password;
    public string Password
    {
        get
        {
            return _password;
        }
        set
        {
            _password = value;
            OnPropertyChanged(nameof(Password));
        }
    }
    public ICommand LoginCommand { get; set; }
    private StringBuilder ErrorMessages { get; set; } = new StringBuilder();
    public Loginviewmodel()
    {
        LoginCommand = new RelayCommandAsync(async (para) => await LoginUser(para));
    }
    private async Task LoginUser(object para)
    {
        sqlConnector sqlConnector = new sqlConnector();
        if (ValidateForm() == false)
        {
            MessageBox.Show(ErrorMessages.ToString());
            return;
        }
        User user = await sqlConnector.FindUserByEmail(Email);
        if (user == null)
        {
            MessageBox.Show("Incorrect username or password");
            return;
        }
        IPasswordHasher passwordHasher = new PasswordHasher();
        var passwordResult = passwordHasher.VerifyHashedPassword(user.PasswordHash,Password);
        if (passwordResult == PasswordVerificationResult.Success)
        {
            MessageBox.Show("Login success.");
            //here is the problem...I am telling my Mainviewmodel's CurrentView property to update 
            but it's not listening to me. 
                //new Mainviewmodel().UpdateCurrentviewmodelCommand.Execute("DashboardView");
            new Mainviewmodel().CurrentView = new Dashboardviewmodel();
        }
        else
        {
            MessageBox.Show("Incorrect username or password");
        }
        ClearProperties();
    }
    private bool ValidateForm()
    {
        ErrorMessages.Clear();
        bool isValid = true;
        if (string.IsNullOrWhiteSpace(Email))
        {
            isValid = false;
            ErrorMessages.Append("Email cannot be empty\n");
        }
        if (string.IsNullOrWhiteSpace(Password))
        {
            isValid = false;
            ErrorMessages.Append("Password cannot be empty\n");
        }
        return isValid;
    }
    private void ClearProperties()
    {
        Email = Password = null;
    }

解决方法

这不起作用,因为成功登录后您正在创建MainViewModel的新实例。这不是您的视图DataContext的实例。

您可以例如通过构造函数将对MainViewModel实例的引用传递给LoginViewModel。但是由于LoginViewModel并不真正依赖MainViewModel,所以我不会这样做。
相反,我建议以下两种解决方案之一。通常,您的页面视图模型不应在乎内容导航。这应该仅是父级导航视图模型的责任,该模型已经知道导航详细信息,例如当前页面或下一页等。两个示例都遵循此想法。

还要注意,由于每次用户导航时都要创建新的页面视图模型,因此您将丢失所有内容。返回上一页将显示空白的初始页面。同样,switch在可扩展性方面也很糟糕。添加新页面不是很好,并且会破坏您的UpdateCurrentView方法。

我重构了您的代码,以显示一种简单的方法来保持页面导航的简单性和可扩展性。这只是很小的变化:添加enum替换字符串以启用Intellisense和编译时类型检查支持,并添加Dictionary替换switch

// Enum used to identify the requested page e.g as CommandParameter
public enum PageId
{
   None = 0,LoginView,RegisterView,DashboardView
}

// Use a Dictionary to navigate to content based on the PageId enum
public class MainViewModel : BaseViewModel
{
    private Dictionary<PageId,BaseViewModel> Pages { get; set; }

    public MainViewModel()
    {
        this.Pages = new Dictionary<PageId,BaseViewModel>
        {
            { PageId.LoginView,new LoginViewModel() },{ PageId.RegisterView,new RegisterViewModel() },{ PageId.DashboardView,new DashboardViewModel() }
       };
    }

    private void UpdateCurrentView(object commandParameter)
    {
        if (commandParameter is PageId pageId
          && this.Pages.TryGetValue(pageId,out BaseViewModel nextPage))
        {
            this.CurrentView = nextPage;
        }
    }
}

<!-- Modified view to use the enum -->
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Button Content="Login Page" 
            Command="{Binding UpdateCurrentViewModelCommand}"
            CommandParameter="{x:Static PageId.LoginView}"/>
    <Button Content="Register Page" 
            Command="{Binding UpdateCurrentViewModelCommand}"
            CommandParameter="{x:Static PageId.RegisterView}"
            Grid.Row="1"/>
    <ContentControl Content="{Binding CurrentView}" Grid.Row="2"/>
</Grid>

解决方案1:LoginSuccessful事件

您可以公开一个LoginSuccessful事件,导航视图模型可以侦听此事件:

MainViewModel.cs

public class MainViewModel : BaseViewModel
{
    private Dictionary<PageId,BaseViewModel>
        {
            { PageId.RegisterView,new DashboardViewModel() }
       };
    
       var loginView = new LoginViewModel();
       loginView.LoginSuccessful += OnLoginSuccessful;
       this.Pages.Add(PageId.LoginView,loginView);
    }

    private void UpdateCurrentView(object commandParameter)
    {
        if (commandParameter is PageId pageId
          && this.Pages.TryGetValue(pageId,out BaseViewModel nextPage))
        {
            this.CurrentView = nextPage;
        }
    }

    private void OnLoginSuccessful(object sender,EventArgs e)
    {
        var loginViewModel = sender as LoginViewModel;
        loginViewModel.LoginSuccessful -= OnLoginSuccessful;

        UpdateCurrentView(PageId.LoginView);
    }
}

LoginViewModel.cs

public class LoginViewModel : BaseViewModel
{  
    public event EventHandler LoginSuccessful;
    private void OnLoginSuccessful() => this.LoginSuccessful?.Invoke(this,EventArgs.Empty);

    private async Task LoginUser(object para)
    {
        SqlConnector sqlConnector = new SqlConnector();
        if (ValidateForm() == false)
        {
            MessageBox.Show(ErrorMessages.ToString());
            return;
        }
        User user = await sqlConnector.FindUserByEmail(Email);
        if (user == null)
        {
            MessageBox.Show("Incorrect username or password");
            return;
        }
        IPasswordHasher passwordHasher = new PasswordHasher();
        var passwordResult = passwordHasher.VerifyHashedPassword(user.PasswordHash,Password);
        if (passwordResult == PasswordVerificationResult.Success)
        {
            MessageBox.Show("Login success.");
            OnLoginSuccessful();
        }
        else
        {
            MessageBox.Show("Incorrect username or password");
        }
        ClearProperties();
    }
}

解决方案2:继续回调

或强制导航视图模式通过构造函数提供延续回调:

MainViewModel.cs

public class MainViewModel : BaseViewModel
{
    private Dictionary<PageId,new LoginViewModel(() => UpdateCurrentView(PageId.LoginView)) },out BaseViewModel nextPage))
        {
            this.CurrentView = nextPage;
        }
    }
}

LoginViewModel.cs

public class LoginViewModel : BaseViewModel
{
    public Action LoginSuccessfulContinuation { get; set; }

    // Constructor
    public LoginViewModel(Action loginSuccessfulContinuation) => this.LoginSuccessfulContinuation = loginSuccessfulContinuation; 

    private async Task LoginUser(object para)
    {
        SqlConnector sqlConnector = new SqlConnector();
        if (ValidateForm() == false)
        {
            MessageBox.Show(ErrorMessages.ToString());
            return;
        }
        User user = await sqlConnector.FindUserByEmail(Email);
        if (user == null)
        {
            MessageBox.Show("Incorrect username or password");
            return;
        }
        IPasswordHasher passwordHasher = new PasswordHasher();
        var passwordResult = passwordHasher.VerifyHashedPassword(user.PasswordHash,Password);
        if (passwordResult == PasswordVerificationResult.Success)
        {
            MessageBox.Show("Login success.");
            this.LoginSuccessfulContinuation?.Invoke();
        }
        else
        {
            MessageBox.Show("Incorrect username or password");
        }
        ClearProperties();
    }
}