【问题标题】:Best way to have a wpf window switching between views?在视图之间切换 wpf 窗口的最佳方法是什么?
【发布时间】:2021-08-28 22:16:39
【问题描述】:

我正在尝试制作一个 WPF MVVM 应用程序,其中有一个主窗口,其中有一个侧边栏,每个 MVVM 视图都有一个按钮,并且所述窗口的其余部分一次显示一个视图。

类似:

(忽略在本例中,侧边栏位于顶部,这仅用于说明目的)

我的目标是能够单击一个按钮并将黄色部分更改为与该按钮对应的视图。

我可以像这样硬编码地做到这一点:

<Button Command={Binding ChangeView} CommandParameter={Binding CalculatorView}/>

public FrameworkElement CurrentControlView { get; set; }

public ICommand ChangeViewCommand { get; }

// command initialization

void ChangeView(string viewName)
{
    Type viewType = Type.GetType($"Program.Views.{viewName}");
    CurrentControlView = (FrameworkElement)Activator.CreateInstance(viewType);
}

并通过CurrentControlView 属性绑定正在显示的视图。

但是我试图让它自己构建。我的意思是,我想编写代码来查找某个命名空间中的所有 MVVM 约定视图(带有代码隐藏的 XAML 文件),并创建一个按钮,当单击该按钮时,将触发显示其相应视图的代码。

到目前为止,我一直在考虑使用某种反射代码来收集视图,将它们放入一个集合中,然后将 ItemsControl 绑定到该集合,如下所示:

(再次记住图片中的按钮在顶部,但我希望它们在左侧)

    <ItemsControl ItemsSource="{Binding ViewsCollection}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Button Content="??" Command="{Binding ChangeViewCommand}" CommandParameter="??" HorizontalAlignment="Center" VerticalAlignment="Center" Height="40"/>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

但是在这里我不确定要为Content 放置什么,因为我希望按钮显示视图的名称。因此,如果我有“CalculatorView.xaml”,我会修剪“视图”部分,按钮就像图片中的内容“计算器”一样。

我也不知道如何为“CommandParameter”的每个单独按钮传递视图名称。即使传递按钮内容就足够了,因为我可以从那里处理它。

我怎样才能做到这一点?

【问题讨论】:

  • 创建一个类,比如ViewDefinition,具有两个属性string ViewName 和Type ViewType。通过获取每个反射的所有视图来填写您的ViewsCollection。将按钮内容绑定到 ViewName,将 CommandParameter 绑定到 ViewType。然后您可以在ChangeViewCommand 中创建视图实例,您将获得所需的类型作为参数。
  • 视图不应在后面的代码中创建。相反,应该有一组 DataTemplates 用于创建不同视图的不同视图模型。然后将特定选定视图模型的实例分配给 ContentControl 的 Content 属性。将自动选择适当的 DataTemplate 并因此选择适当的视图。见Data Templating Overview。
  • 这能回答你的问题吗? C# WPF Navigation Between Pages (Views)
  • @BionicCode,不,因为它是关于硬编码的视图数量。我需要我的动态填充。
  • 它是完全动态的。您只需将模型添加到池中并从该池中进行选择。视图将根据适当的 DataTemplate 自动呈现。它不能变得更有活力。

标签: c# wpf


【解决方案1】:

我想这就是你所追求的......

public class MainVM : INotifyPropertyChanged
{
    public List<ButtonViewModel> Navigation { get; private set; }

    private object _selectedView;

    public object SelectedView
    {
        get { return _selectedView; }
        set
        {
            _selectedView = value;
            OnPropertyChanged();
        }
    }

    public ICommand ChangeViewCommand { get; }


    public MainVM()
    {
        Navigation = new List<ButtonViewModel>
        {
            new ButtonViewModel
            {
                Content = "Button 1",
                CommandParameter = new VM1()
            },
            new ButtonViewModel
            {
                Content = "Button 2",
                CommandParameter = new VM2()
            }
        };

        ChangeViewCommand = new RelayCommand(param => ChangeView(param));
    }

    void ChangeView(object param)
    {
        SelectedView = param;
    }

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

public class VM1
{

}
public class VM2
{

}

public class ButtonViewModel
{
    public string Content { get; set; }
    public object CommandParameter { get; set; }
}
public class RelayCommand : ICommand
{
    private Action<object> execute;
    private Func<object, bool> canExecute;

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

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        this.execute = execute;
        this.canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return this.canExecute == null || this.canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        this.execute(parameter);
    }
}

主窗口

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <ItemsControl ItemsSource="{Binding Navigation}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Button Content="{Binding Content}" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ChangeViewCommand}"  CommandParameter="{Binding CommandParameter}" HorizontalAlignment="Center" VerticalAlignment="Center" Height="40"/>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

    <ContentPresenter Content="{Binding SelectedView}" Grid.Column="1" />
    
</Grid>

VM1View

<Grid>
    <TextBlock Text="I am VM1" />
</Grid>

VM2View

<Grid>
    <TextBlock Text="I am VM2" />
</Grid>

App.xaml

<Application x:Class="WpfApp.App"...>
    <Application.Resources>
        <DataTemplate DataType="{x:Type local:VM1}">
            <local:VM1View />
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:VM2}">
            <local:VM2View />
        </DataTemplate>
    </Application.Resources>
</Application>

演示

【讨论】:

    【解决方案2】:

    我的答案基于this answer,它已经显示了显示动态视图的推荐模式。我只修改了MainViewModel 逻辑来处理按钮项集合NavigationItem 作为ListBox 在动态显示实际导航按钮的视图中的源集合。
    映射将通过页面模型项的索引进行 - 但它可以是任何其他标识符。

    模式很简单:创建一个模型,通过将其绑定到ListBox(NavigationItem 模型集合)和ContentControl(选择IPage 或object 模型)将其添加到视图,然后让 WPF使用匹配的隐式 DataTemplate 呈现适当的视图。

    用这个替换链接答案的 MainViewModel.cs 和 MainWindow.xaml。

    MainViewModel.cs
    动态添加新页面的部分及其相应的导航按钮在AddPage() 方法中进行了简化显示。基本上该模式是定义一个NavigationItem 和相应的IPage 实现。映射为NavigationItem.PageIndex 到Pages 集合的此索引处的对应项:

    IPage nextPageModel = Pages[NavigationItem.PageIndex];
    

    您必须为每个添加的页面模型类型定义一个新的DataTemplate 来定义页面的实际内容。

    class MainViewModel
    {
      public ICommand SelectPageCommand => new RelayCommand(SelectPage); 
      public ObservableCollection<NavigationItem> NavigationItems { get; }   
      private Dictionary<int, IPage> Pages { get; }
    
      private IPage selectedPage;   
      public IPage SelectedPage
      {
        get => this.selectedPage;
        set 
        { 
          this.selectedPage = value; 
          OnPropertyChanged();
        }
      }
    
      public MainViewModel()
      {
        this.NavigationItems = new ObservableCollection<NavigationItem>
        {
          new NavigationItem("Home", 0, this.SelectPageCommand),
          new NavigationItem("Login", 1, this.SelectPageCommand)
        };
         
        this.Pages = new Dictionary<int, IPage>
        {
          { 0, new WelcomePageViewModel() },
          { 1, new LoginPageViewModel() }
        };
    
        this.SelectedPage = this.Pages.First().Value;
      }
    
      public void SelectPage(object param)
      {
        if (param is int pageIndex 
          && this.Pages.TryGetValue(pageIndex, out IPage selectedPage))
        {
          this.SelectedPage = selectedPage;
        }
      }
    
      public void AddPage()
      {
        int newPageIndex = this.Pages.Count;
    
        IPage calculatorPageModel = new CalculatotPageViewModel();
        this.Pages.Add(newPageIndex, calculatorPageModel);
    
        var navigationItem = new NavigationItem("Calculator", newPageIndex, this.SelectPageCommand);
        this.NavigationItems.Add(navigationItem);
      }
    
      public event PropertyChangedEventHandler PropertyChanged;
      protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 
        => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    

    NavigationItem.cs

    class NavigationItem
    {
      public string PageTitle { get; }  
      public string PageIndex { get; }
      public string NavigateCommand { get; }
    
    
      public NavigationItem(string pageTitle, int pageIndex, ICommand navigateCommand)
      {
        this.PageTitle = pageTitle;
        this.PageIndex = pageIndex;
        this.NavigateCommand = navigateCommand;
      }
    }
    

    MainWindow.xaml

    <Window>
      <Window.DataContext>
        <MainViewModel />
      </Window.DataContext>
    
      <Window.Resources>
        <DataTemplate DataType="{x:Type WelcomePageviewModel}">
          <WelcomPage />
        </DataTemplate>
    
        <DataTemplate DataType="{x:Type LoginPageviewModel}">
          <LoginPage />
        </DataTemplate>
    
        <DataTemplate DataType="{x:Type CalculatorPageviewModel}">
          <CalculatorPage />
        </DataTemplate>
      </Window.Resources>
    
      <StackPanel>
    
        <!-- Page navigation -->
        <ListBox ItemsSource="{Binding NavigationItems}">
          <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
              <VirtualizingStackPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
          </ListBox.ItemsPanel>
    
          <ListBox.ItemTemplate> 
            <DataTemplate> 
              <Button Content="{Binding PageTitle}" 
                      Command="{Binding NavigateCommand}" 
                      CommandParameter="{Binding PageIndex}" />
            </DataTemplate> 
          </ListBox.ItemTemplate> 
        </ListBox>
    
        <!-- 
          Host of SelectedPage. 
          Automatically displays the DataTemplate that matches the current data type 
        -->
        <ContentControl Content="{Binding SelectedPage}" />
      <StackPanel>
    </Window>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-16
      • 1970-01-01
      • 2021-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多