我正在尝试创建一个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
是当前可见的那个,但我似乎不太想让代码正常工作。>
为了简化起见,我只会显示UserControl
和MainWindow
的相关文件。
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
。
基本上,我很困惑,不确定如何在应用程序中的页面之间进行切换。任何帮助或指导将不胜感激。
答案 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
}