如何在WPF应用程序中的页面之间切换?

时间:2019-11-14 02:32:50

标签: c# wpf xaml

我正在尝试创建一个WPF应用程序,该应用程序提供一个登录视图,并在成功登录后提供一个第一页,第二页和第三页(如向导)。包括登录视图的每个“页面”都有其各自的"Parse error: syntax error, unexpected end of file, expecting function (T_FUNCTION) in H:\root\home\cmadurawala-001\www\site1\wp-includes\class-wp-hook.php on line 492" 。我有一个ViewModel,其中包含四个MainWindow.xaml,其中一个在任何给定状态下都可见。

我在处理可见度编排时遇到麻烦。对我来说,最有意义的是UserControls是负责跟踪哪个MainWindowViewModel是当前可见的那个,但我似乎不太想让代码正常工作。

为了简化起见,我只会显示UserControlMainWindow的相关文件。

MainWindow.xaml

LoginView

MainWindow.xaml.cs

<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>

MainWindowViewModel.cs

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

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

LoginView.xaml

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

LoginView.xaml.cs

<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>

LoginViewModel.cs

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

如您所见,我想避免在文件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

然后根据是否在每个页面上单击了“下一步”或“后退”按钮,我将正确设置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,我将无法做到:

MainWindow.xaml

因为<local:LoginView Visibility="{Binding LoginVisibility}" /> 中不存在LoginVisibility,所以用户控件的数据上下文就是其中的内容。而且,将该字段放在其中并不恰当,因为所有LoginViewModel都将需要知道自己的可见性状态,并以某种方式将其传达给ViewModels

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

2 个答案:

答案 0 :(得分:1)

您可以在枚举更改的基础上在主窗口资源中创建数据模板,并将适当的数据模板绑定到控件模板(在网格内,您希望在其中显示),而不是绑定可见性

以下是一个大概的想法。

在您的mainwindow.xaml内

 <Window.Resources>
    <ResourceDictionary>
        <DataTemplate x:Key="DTLoginView">
            <local:LoginView />
        </DataTemplate>
        <DataTemplate x:Key="DTPageOne">
            <local:PageOne />
        </DataTemplate>
    </ResourceDictionary>
</Window.Resources>

现在,在您的mainwindow视图模型内部,执行一些逻辑,并根据该逻辑存储页面的值。您当前的页面属性应实现INotifyPropertyChanged,其外观应类似于以下内容。 (注意:我为基本的MVVM接线添加了Haley.Flipper.MVVM nuget包(免责声明:Haley nuget包由我开发)。您可以实现自己的INotifyPropertyChanged或使用某些MVVM库)< / p>

  private PageType _CurrentPage;
    public PageType CurrentPage
    {
        get { return _CurrentPage; }
        set { _CurrentPage = value; onPropertyChanged(); }
    }

在XAML内部的MainWindow中。 (在有网格的地方)

<Grid x:Name="grdPages" DataContext={Binding}>
<ContentControl >
        <ContentControl.Style>
            <Style TargetType="{x:Type ContentControl}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding ElementName=grdPages, Path=DataContext.CurrentPage, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Value="0">
                        <Setter Property="ContentTemplate" Value="{StaticResource DTLoginView}"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding ElementName=grdPages, Path=DataContext.CurrentPage, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Value="1">
                        <Setter Property="ContentTemplate" Value="{StaticResource DTPageOne}"/>
                    </DataTrigger>
               </Style.Triggers>
            </Style>
        </ContentControl.Style>                
    </ContentControl>

如果您看一下上面的xaml代码,我对数据触发绑定的值为“ 0”“ 1”,因为枚举应具有0、1、2、3,依此类推。但是,您也可以将枚举直接绑定为值。进行一些搜索,您可以轻松找到答案。

当前页面的属性(枚举值)应由某些逻辑(由您实现)设置。完成后,它将自动触发向xaml的通知。

希望这可以在某种程度上帮助您。

答案 1 :(得分:1)

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

风景

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">
        <local:LoginView />
      </Border>
    </DataTemplate>

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

    <DataTemplate DataType="{x:Type PageTwoViewModel}">
      <Border Background="DeepSkyBlue">
        <local:PageTwo />
      </Border>
    </DataTemplate>    

    <DataTemplate DataType="{x:Type PageThreeViewModel}">
      <Border Background="Yellow">
        <local:PageThree />
      </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<BaseViewModel>() 
    {
      new LoginViewModel(), 
      new PageOneViewModel(), 
      new PageTwoViewModel(),  
      new PageThreeViewModel()
    };

    // 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 BaseViewModel selectedPage;    
  public BaseViewModel SelectedPage
  {
    get => this.selectedPage;
    set
    {
      if (object.Equals(value, this.selectedPage))
      {
        return;
      }

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

  private ObservableCollection<BaseViewModel> pages;    
  public ObservableCollection<BaseViewModel> 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
}

页面模型

BaseViewModel.cs

// Base type for all pages
abstract class BaseViewModel : INotifyPropertyChanged
{
}

LoginViewModel.cs

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

PageOneViewModel.cs

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

PageTwoViewModel.cs

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

PageThreeViewModel.cs

// BaseViewModel implementation. 
// Consider to introduce dedicated abstract class Page which extends 
class PageThreeViewModel : BaseViewModel
{    
  // Implementation
}