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

问题描述 投票:0回答:1

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

View1 VM

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也不起作用,侧面导航栏按钮也可以正常工作。

c# wpf xaml user-controls
1个回答
0
投票

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

根据用户界面的结构,有多种实现方法。到目前为止,创建视图模型类的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属性来确保使用单个实例,从而确保globally。Singleton Pattern通常被认为是反模式。)

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

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>
© www.soinside.com 2019 - 2024. All rights reserved.