如何在WPF应用程序中的页面之间切换? [英] How do I toggle between pages in a WPF application?

查看:199
本文介绍了如何在WPF应用程序中的页面之间切换?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图创建一个WPF应用程序,该应用程序提供一个登录视图,并在成功登录后提供一个第一页,第二页和第三页(如向导).包括登录视图的每个页面"都有其各自的 ViewModel .我有一个 MainWindow.xaml ,其中包含四个 UserControls ,其中一个在任何给定状态下都可见.

I am trying to create a WPF application that presents a login view and after successful login, presents a first, second, and third page (like a wizard). Each "page" including the login view has its respective ViewModel. I have a MainWindow.xaml which holds four UserControls one of which will be visible at any given state.

我在处理能见度编排时遇到麻烦.对我来说,最有意义的是 MainWindowViewModel 是负责跟踪哪个 UserControl 是当前可见的那个,但我似乎不太了解代码起作用.

I am having trouble dealing with the orchestration of visibility. It makes the most sense to me that MainWindowViewModel is the one that is responsible for keeping track of which UserControl is the current visible one but I can't quite seem to get the code to work.

为了简化起见,我只会显示 MainWindow LoginView 的相关文件.

I will only show the relevant files for the MainWindow and the LoginView to keep things simpler.

MainWindow.xaml

<Grid>
    <local:LoginView Visibility="{Not sure what to bind to here}" />
    <local:PageOne Visibility="{Not sure what to bind to here}" />
    <local:PageTwo Visibility="{Not sure what to bind to here}" />
    <local:PageThree Visibility="{Not sure what to bind to here}" />
</Grid>

MainWindow.xaml.cs

public partial class MainWindow : Window
{        
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
    }     
}

MainWindowViewModel.cs

公共类MainWindowViewModel:BaseViewModel{公共ICommand WindowClosingCommand {get;}

public class MainWindowViewModel : BaseViewModel { public ICommand WindowClosingCommand { get; }

    public MainWindowViewModel()
    {
        WindowClosingCommand = new WindowClosingCommand(this);
    }
}

LoginView.xaml

<UserControl x:Class="MyProject.View.LoginView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MyProject.View"
             mc:Ignorable="d" 
             d:DesignHeight="800" d:DesignWidth="1200">

    <Grid>
      <!-- UI Layout stuff here -->
    </Grid>
</UserControl>

LoginView.xaml.cs

public partial class Login : UserControl
{
    public Login()
    {
        InitializeComponent();
        DataContext = new LoginViewModel();
    }
}

LoginViewModel.cs

public class LoginViewModel : BaseViewModel
{
    public ICommand ConnectCommand { get; }
    public ICommand WindowClosingCommand { get; }

    public LoginViewModel()
    {
        ConnectCommand = new ConnectCommand(this);
        WindowClosingCommand = new WindowClosingCommand(this);
    }

    public string UserName { get; set; }
}

如您所见,我想避免在文件 .xaml.cs 后面的代码中添加大量逻辑,因为这是最佳实践,并且我有一个 ViewModel 哪个 .xaml 文件.现在,通常,我会这样写:

So as you can see, I want to avoid putting a ton of logic in the code behind files .xaml.cs because that is best practice and I have a ViewModel for which .xaml file. Now, ordinarily, I would write something like:

public PageType CurrentPage;

public enum PageType
{
    Login, PageOne, PageTwo, PageThree
}

public Visibility LoginVisibility
{
    get { (CurrentPage == PageType.Login) ? Visibility.Visible : Visibility.Collapsed }
}

// Repeat for each of the other three pages

然后根据是否在每个页面上单击下一步"或后退"按钮,我将正确设置 CurrentPage 字段.

And then depending on if the "Next" or "Back" buttons were clicked on each page, I would set the CurrentPage field properly.

但是,如果我们回到我的 MainWindow.xaml ,我就不能这样做:

However, if we refer back to my MainWindow.xaml, I can't just do:

<local:LoginView Visibility="{Binding LoginVisibility}" />

因为 LoginViewModel 中不存在 LoginVisibility ,因此该控件是用户控件的数据上下文.而且,将该字段放在此处并不恰当,因为所有 ViewModels 随后都需要知道自己的可见性状态,并以某种方式将其传达给 MainWindow .

Because LoginVisibility does not exist in the LoginViewModel which is what that user control's data context is. And it wouldn't feel right to put that field in there because all ViewModels will then need to know their own visibility state and somehow communicate that up to the MainWindow.

基本上,我很困惑,不确定如何在应用程序中的页面之间进行切换.任何帮助或指导将不胜感激.

Basically, I am confused and unsure how to go about toggling between pages in my application. Any help or guidance would be greatly appreciated.

推荐答案

与使用 Frame 相对,最简单,最轻便的方法是为每个页面创建一个视图模型.然后创建一个包含所有页面并管理其选择的主视图模型. ContentControl 将使用分配给 ContentControl.ContentTemplate 属性的 DataTemplate 显示视图模型,或者在多页方案中使用 DataTemplateSelector分配给 ContentControl.ContentTemplateSelector 或隐式模板,方法是仅定义 DataTemplate.DataType 而没有 Key 属性:

The easiest and most lightweight way opposed to using a Frame, is to create a view model for each page. Then create a main view model which holds all pages and manages their selection. A ContentControl will display the view models using a DataTemplate assigned to the ContentControl.ContentTemplate property or in a multi page scenario either a DataTemplateSelector assigned to ContentControl.ContentTemplateSelector or implicit templates by only defining the DataTemplate.DataType without the Key attribute:

MainWindow.xaml

<Window>
  <Window.DataContext>
    <MainViewModel x:Key="MainViewModel" />
  </Window.DataContext>
  <Window.Resources>
    <!-- 
        The templates for the view of each page model.
        Can be moved to dedicated files.
    -->  
    <DataTemplate DataType="{x:Type LoginViewModel}">
      <Border Background="Coral">

        <!-- UserControl -->
        <local:LoginView />
      </Border>
    </DataTemplate>

    <DataTemplate DataType="{x:Type PageOneViewModel}">
      <Border Background="Red">
        <local:PageOne />
      </Border>
    </DataTemplate>    

    <DataTemplate DataType="{x:Type PageTwoViewModel}">
      <Border Background="DeepSkyBlue">
        <TextBox Text="{Binding PageTitle}" />
      </Border>
    </DataTemplate>    
  </Window.Resources>


<StackPanel>
    <Button Content="Load Login Page"
            Command="{Binding SelectPageFromIndexCommand}"
            CommandParameter="0" />
    <Button Content="Load Page One"
            Command="{Binding SelectPageFromIndexCommand}"
            CommandParameter="1" />
    <Button Content="Load Next Page"
            Command="{Binding SelectNextPageCommand}" />

    <!-- The actual page control -->
    <ContentControl Content="{Binding SelectedPage}" />
  </StackPanel>
</Window>

视图模型

MainViewModel.cs

class MainViewModel : INotifyPropertyChanged
{
  public MainViewModel()
  {
    this.Pages = new ObservableCollection<IPageViewModel>() 
    {
      new LoginViewModel(), 
      new PageOneViewModel(), 
      new PageTwoViewModel()
    };

    // Show startup page
    this.SelectedPage = this.Pages.First();
  }

  // Define the Execute and CanExecute delegates for the command
  // and pass them to the constructor
  public ICommand SelectPageFromIndexCommand => new SelectPageCommand(
    param => this.SelectedPage = this.Pages.ElementAt(int.Parse(param as string)),
    param => int.TryParse(param as string, out int index));

  // Define the Execute and CanExecute delegates for the command
  // and pass them to the constructor
  public ICommand SelectNextPageCommand => new SelectPageCommand(
    param => this.SelectedPage = this.Pages.ElementAt(this.Pages.IndexOf(this.SelectedPage) + 1),
    param => this.Pages.IndexOf(this.SelectedPage) + 1 < this.Pages.Count);

  private IPageViewModel selectedPage;    
  public IPageViewModel SelectedPage
  {
    get => this.selectedPage;
    set
    {
      if (object.Equals(value, this.selectedPage))
      {
        return;
      }

      this.selectedPage = value;
      OnPropertyChanged();
    }
  }

  private ObservableCollection<IPageViewModel> pages;    
  public ObservableCollection<IPageViewModel> Pages
  {
    get => this.pages;
    set
    {
      if (object.Equals(value, this.pages))
      {
        return;
      }

      this.pages = value;
      OnPropertyChanged();
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;    
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

SelectPageCommand.cs

class SelectPageCommand : ICommand
{
  public SelectPageCommand(Action<object> executeDelegate, Predicate<object> canExecuteDelegate)
  {
    this.ExecuteDelegate = executeDelegate;
    this.CanExecuteDelegate = canExecuteDelegate;
  }

  private Predicate<object> CanExecuteDelegate { get; }
  private Action<object> ExecuteDelegate { get; }

  #region Implementation of ICommand

  public bool CanExecute(object parameter) => this.CanExecuteDelegate?.Invoke(parameter) ?? false;

  public void Execute(object parameter) => this.ExecuteDelegate?.Invoke(parameter);

  public event EventHandler CanExecuteChanged
  {
    add => CommandManager.RequerySuggested += value;
    remove => CommandManager.RequerySuggested -= value;
  }

  #endregion
}

页面模型

IPageViewModel.cs

// Base type for all pages
interface IPageViewModel : INotifyPropertyChanged
{
  public string PageTitle { get; set; }
}

LoginViewModel.cs

// BaseViewModel implementation. 
// Consider to introduce dedicated abstract class Page which implements IPageViewModel 
class LoginViewModel : BaseViewModel
{
  // Implementation
}

PageOneViewModel.cs

// BaseViewModel implementation. 
// Consider to introduce dedicated abstract class Page which implements IPageViewModel 
class PageOneViewModel : IPageViewModel 
{    
  // Implementation
}

PageTwoViewModel.cs

// BaseViewModel implementation. 
// Consider to introduce dedicated abstract class Page which implements IPageViewModel 
class PageTwoViewModel : IPageViewModel 
{    
  // Implementation
}

这篇关于如何在WPF应用程序中的页面之间切换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆