如何通过主视图模型将 UserControl 内容更改为另一个 UserControl。如何在内容之间导航

Posted

技术标签:

【中文标题】如何通过主视图模型将 UserControl 内容更改为另一个 UserControl。如何在内容之间导航【英文标题】:How to change UserControl content to another UserControl through main view model. How to navigate between content 【发布时间】:2020-06-07 16:29:44 【问题描述】:

我有一个带有导航侧栏的主窗口和一个用户控件,其中我显示了 3 个视图(默认,view1,view2)。在主视图模型(称为 AppVM)中,我将 contentcontrol 初始化为默认视图,该视图有一个按钮可以前进到 view1(除了导航侧边栏)。我在 AppVM 中有命令可以切换到三个视图中的任何一个。 View1 然后有另一个按钮,它应该移动到 view2(使用主视图模型中的命令)。但是,每当我按下 view1 中的按钮(移动到 view2)时,显示都不会改变。奇特的是,在调试时,按下view1中的按钮时,内容控件绑定的变量设置为默认视图,而不是当前视图view1。

我认为我设置命令的方式是创建内容控件绑定变量的新实例,但我不知道如何使它使用相同的实例而不是一次又一次地打开新实例。

主视图模型 (AppVM)

  public class AppVM : ObservableObject
    

        //Create a property that controls current view
        private object _currentView;
        public object CurrentView
        
            get  return _currentView; 
            private set
            
                OnPropertyChanged(ref _currentView, value);
            
        

        private string _textboxText;

        public string TextboxText
        
            get  return _textboxText; 
            set
            
                OnPropertyChanged(ref _textboxText, value);
            
        


        //Instantiate the relaycommands, we will need to instantiate relaycommand objects for every command we need to perform. 
        //This means that we will need to do this for preses of all buttons
        public RelayCommand View1ButtonCommand  get; private set; 
        public RelayCommand View2ButtonCommand  get; private set; 

        public RelayCommand DefaultCommand  get; private set; 



        public AppVM()
        

            //CurrentView = this;
            CurrentView = new DefaultVM();
            View1ButtonCommand = new RelayCommand(ShowView1, AlwaysTrueCommand);
            View2ButtonCommand = new RelayCommand(ShowView2, AlwaysTrueCommand);
            DefaultCommand = new RelayCommand(ShowDefault, AlwaysTrueCommand);


        

        public void ShowDefault(object dummy)
        
          //  CurrentView = null;
            CurrentView = new DefaultVM();

        

        public void ShowView1(object dummy)
        
            //CurrentView = null;
            CurrentView =  new View1(dummy as string);

        

        public void ShowView2(object dummy)
        
            // CurrentView = null;
            CurrentView =  new View2();
        


        public bool AlwaysTrueCommand(object dummy)
        
            return true;
               
    

查看 1 个虚拟机

public class View1VM : ObservableObject     

        public InfoClass View1InfoClass  get; set; 



        public View1VM()                   View1InfoClass = new InfoClass //Apparently I  need to instantiate and initialize this to activate binding          

                FirstName =  "Abbas",
                //FirstName = passedInforClass,
                LastName = "Syed",
                Number = 12

            ;


           

view1.xaml 中的命令

<UserControl.Resources>
        <vm:AppVM x:Name="AppVMinView1" x:Key="AppVMinView1"></vm:AppVM>
    </UserControl.Resources>
    <UserControl.DataContext>
        <vm:View1VM></vm:View1VM>
    </UserControl.DataContext>
    <Grid Background="Aqua">
        <StackPanel Margin="100">
            <TextBlock Text="First Name"/>
            <TextBox x:Name="firstNameTextBoxView1" Text="Binding View1InfoClass.FirstName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged"></TextBox>
            <TextBlock Text="Last Name"/>
            <TextBox x:Name="lastNameTextBoxView1" Text="Binding View1InfoClass.LastName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged"></TextBox>
            <TextBlock Text="Random Useless Number" ></TextBlock>
            <TextBox x:Name="randomUselessNumberView1" Text="Binding View1InfoClass.Number, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged"></TextBox>

            <TextBlock Text="First Name Entered"></TextBlock>
            <TextBlock Text="Binding View1InfoClass.FirstName"></TextBlock>
            <TextBlock Text="Last Name Entered" ></TextBlock>
            <TextBlock Text="Binding View1InfoClass.LastName"></TextBlock>
            <TextBlock Text="Random Useless Number Entered"></TextBlock>
            <TextBlock Text="Binding View1InfoClass.Number"></TextBlock>

            <Button DataContext="DynamicResource AppVMinView1" Content="Go to view2" Height="20" Width="70" Command="Binding View2ButtonCommand" />



        </StackPanel>
    </Grid>

根据我所阅读的内容(此处和互联网上),我需要将视图设为单例,我尝试这样做的方法是声明 view2 的静态属性,并使用私有设置器初始化为新的 view2,但是那没有削减它。对于这方面的任何帮助,我将不胜感激。

我还应该补充一点,即使 view1 中更改为 view2 的按钮不起作用,侧面导航栏按钮也可以正常工作。

【问题讨论】:

你的问题格式很奇怪。有些代码甚至没有显示。代码应该被格式化为代码而不是块引用。 您的Button 已磨损DataContext 设置。 DataContext 必须是 AppVM,否则 Command 绑定无法解析。将视图设为单例是没有用的。 你好 BionicCode。感谢您的回复并指出格式不正确(对不起!)我将它绑定到 AppVM,但是在调试时,单击 View1 中的按钮导航到 View2 时,我到达 AppVM 中的断点,但 CurrentView 变量设置为 DefaultVM(但它应该是 View1 [这是它的最后一个值])。 看起来您正在创建多个 AppVm 实例。确保您始终使用相同的实例。您正在修改的实例不是触发新视图的实例。 你好 BionicCode。谢谢你回到我身边。这就是我实际上感到困惑的部分。我读到可以声明主视图模型(AppVM)的静态属性以使用相同的实例。但是,由于我有非静态成员(命令),所以我不能有静态构造函数。还有另一种方法吗?如果我听起来很愚蠢,我很抱歉,这是我的第一个项目。感谢您抽出宝贵时间! 【参考方案1】:

您似乎正在创建AppVm 的多个实例。View1中的按钮和导航栏的按钮显然没有绑定到AppVm的同一个实例。 同样适用于CurrentView 属性:您的内容主机绑定到不同的实例,即不同的属性值引用,而不是您在View1 中修改的CurrentView。因此,从 View1 内部更改 CurrentView 对内容主机没有影响 --> 视图永远不会改变。 确保您始终在相同的上下文中引用相同的(共享)实例。

根据用户界面的结构,有多种方法可以实现这一点。创建视图模型类的 Singleton 是迄今为止最糟糕的选择。应该并且总是可以避免单例。

最简单的解决方案是将视图模型声明为 App 中的资源,xaml ResourceDictionary:

App.xaml 此文件中定义的资源可通过StaticResource 标记扩展资源字典查找在任何 XAML 上下文中全局可用。

<Application>
  <Application.Resources>
    <AppVM x:Key="AppVMinView1" />
  </Application.Resources>
</Application>

在任何 XAML 文件中(应用程序范围):

<UserControl>
  <UserControl.DataContext>
    <View1VM />
  </UserControl.DataContext>

  <!-- Reference resources defined in App.xaml, using the StaticResource markup extension -->
  <Button Command="Binding Source=StaticResource AppVMinView1, Path=View2ButtonCommand" />
</UserControl>

推荐解决方案

看起来您正在视图模型中创建视图或页面的实例(我假设new View1(dummy as string) 创建了一个控件,因为视图模型名为View1VM)。仅使用视图模型也可以以更优雅的方式解决您的问题。

使用页面视图模型的单个实例非常重要,以防您不想在切换视图时丢失状态(和数据)。 (不要将此与 Singleton 混淆,这是一种设计模式,通过将单个实例分配给 static 属性来确保全局使用单个实例。Singleton 模式通常被认为是一种反模式。)

这是关于如何显示和导航页面的简短但完整的示例:

AppVM.cs

// Main view model
class AppVM : ObservableObject     

  // Create a property that controls current view
  private ObservableObject _currentView;
  public ObservableObject CurrentView
  
    get => _currentView; 
    private set => OnPropertyChanged(ref _currentView, value);
  

  private Dictionary<string, ObservableObject> Pages  get; set; 

  public AppVM()
  
    // Create and store the pages, 
    // so that the same instances can be reused. 
    // All pages must extend ObservableObject (or any other common base type).
    this.Pages = new Dictionary<string, ObservableObject>()
    
       nameof(DefaultVM), new DefaultVM() ,
       nameof(View1VM), new View1VM() ,
       nameof(View2VM), new View2VM() ,
    ;    

    // Initialize first page
    this.CurrentView = this.Pages[nameof(DefaultVM)];

    this.DefaultCommand  = new RelayCommand(param => this.CurrentView = this.Pages[nameof(DefaultVM)], param => true);
    this.View1ButtonCommand = new RelayCommand(param => this.CurrentView = this.Pages[nameof(View1VM)], param => true);
    this.View2ButtonCommand = new RelayCommand(param => this.CurrentView = this.Pages[nameof(View2VM)], param => true);
    

View1.xaml

<!-- DataContext is inherited from the surrounding DataTemplate and is the corresponding page view model -->
<UserControl>    
  <StackPanel>
    <TextBox Text="Binding View1InfoClass.FirstName" />

    <!-- 
      Bind to the command of the same view model instance,
      which is the DataContext of the content host 
    -->
    <Button Content="Show View2"
            Command="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=MainWindow, Path=DataContext.View2ButtonCommand" />
  </StackPanel>
</UserControl>

MainWindow.xaml

<Window>
  <Window.DataContext>
    <AppVM />
  </Window.DataContext>
  <Window.Resources>

    <!-- Define the views as implicit (keyless) DataTemplate -->
    <DataTemplate DataType="x:Type DefaultVM">
      <DefaultView />
    </DataTemplate>

    <DataTemplate DataType="x:Type View1VM">
      <View1 />
    </DataTemplate>

    <DataTemplate DataType="x:Type View2VM">
      <View2 />
    </DataTemplate>
  </Window.Resources>

  <!-- 
    Host of the pages.
    The implicit DataTemplates will apply automatically 
    and show the control that maps to the current CurrentView view model
  -->
  <ContentPresenter Content="Binding CurrentView" />
</Window>

【讨论】:

你好 BionicCode。非常感谢您的详细回答!

以上是关于如何通过主视图模型将 UserControl 内容更改为另一个 UserControl。如何在内容之间导航的主要内容,如果未能解决你的问题,请参考以下文章

使用 MVVM 在 MainWindow 上绑定 UserControl 视图模型

如何在 UserControl 中扩展模型?

在 UserControl WPF MVVM caliburn 内的 UserControl 之间切换

如何使用 MVVM 灯光从 CustomControl 引用视图模型

从 Catel WPF UserControl 中的 ResourceDictionary 中绑定

当焦点可见时,将焦点设置为usercontrol