我有内容控件和两个单选按钮所在的主窗口. MainWindow.xaml:

<Grid>
<StackPanel >
    <RadioButton Content="First"
                 IsChecked="True"
                 Command="{Binding FirstViewCommand}"/>
    <RadioButton Content="Second" 
                 Command="{Binding SecondViewCommand}"/>
 </StackPanel> 

 <ContentControl Grid.Row="1"
                    Grid.Column="1"
                    Content="{Binding CurrentView}"/>
</Grid>

我将我的用户控件绑定到此内容控件.这里我有组合框、列表框和按钮.基于Cobobox.SelectedElement我需要在按下按钮后更改列表框中的数据. 用户控制:

<Grid>
<ComboBox x:Name="BoxOfDetails"
          ItemsSource="{Binding DetailsScheme}">
</ComboBox>

<Button Command="{Binding ProduceCommand}"
        CommandParameter="{Binding ElementName=BoxOfDetails, Path=SelectedItem}" />
<ListBox x:Name="ListOfMaterials"
         ItemsSource="{Binding Materials}" >
</ListBox>
</Grid>

我还有Main View Model,它是主窗口的数据上下文:

class MainVM : ObservableObject
  {
      public RelayCommand FirstViewCommand { get; set; } 
      public RelayCommand SecondViewCommand { get; set; }

      public FirstVM FirstViewModel { get; set; } 
      public SecondVM SecondViewModel { get; set; }
      private object _currentView;

      public object CurrentView
      {
          get { return _currentView; }
          set => Set(ref _currentView, value);
      }
      public MainVM()
      {
          FirstViewModel = new FirstVM();
          SecondViewModel = new SecondVM();
          CurrentView = FirstViewModel;

          FirstViewCommand = new RelayCommand(o =>
          {
              CurrentView = FirstViewModel;
          });
          SecondViewCommand = new RelayCommand(o =>
          {
              CurrentView = SecondViewModel;
    
          });
}

和我的用户控件的查看模型:


 class FirstVM : ObservableObject
 {
 private ObservableCollection<DetailScheme> _detailsScheme;
 private ObservableCollection<Material> _materials;
 
 public ObservableCollection<DetailScheme> DetailsScheme
     {
 get => _detailsScheme;
 
 public ObservableCollection<Material> Materials
     {
 get => _materials;
 set => Set(ref _materials, value);
     }
 public RelayCommand ProduceCommand { get; set; }
 
 public FirstVM () 
     {
 _detailsScheme = GetDetailsScheme("D:/ DetailsScheme.json");
 _materials = GetMaterials("D:/ Materials.json");
 ProduceCommand = new RelayCommand(o => 
     {***});
 }

当我的用户控件切换时,我需要将信息保存在我的列表框中(对于示例用户 Select 框中的内容并按下按钮,并且切换控制后列表框中的数据现在正在更新,我得到的信息与启动应用程序时相同)

我添加了将信息保存到我的用户控件视图模型中的文件的方法:

public void SaveFirstVM() 
{
    SaveDetails(_details, "D:/ Details.json");
    SaveMaterials(_materials, "D:/ Materials.json");
}

当我在命令的Main ViewModel中切换视图时,我try 调用它

SecondViewCommand = new RelayCommand(o =>
          {
              CurrentView = SecondViewModel;
              FirstViewModel.SaveFirstVM()
          });

但是什么都不会发生,我可以在每次按下UserControl View Model中的Product按钮后调用这个保存方法,但我认为这不是一个好方法. 我在我的项目中添加了一些断点,我只能建议问题是我的UserControl视图模型创建了两次,第一次是在MainVM中,第二次是在初始化发生时,当我更改数据时,它发生在初始化的对象中,当我试图从MainVM保存数据时,它会保存其他没有更改的对象

推荐答案

您已将您的问题标记为"MVVM",但您没有执行它.在MVVM中,有Model负责数据持久化.MVVM并不意味着要把所有的东西都塞进View Model个班级.Model分加上Model个班级,应该会让这种流动更加明显.

You could handle the Frameworkelement.Unloaded event to get notified about the current view being unloaded. However, the graceful solution would be to manage page related persistence where you are already in full control of page management: your MainVM class.
Code should be cohesive and similar or related tasks should not be spread across the application.

MainVM已经能够准确地识别页面切换,而不需要额外的努力.由于MainVM应该具有对一个或多个Model个类的引用,以便例如通过初始化页面视图模型类来获取它将expose 给视图的数据,因此它还能够调用相关的模型类以用于持久性.

Please note, that in proper clean OO programming you must care about type and type member accessibility (access modifiers like public or private). It's a very fundamental technique to make your code robust and APIs well defined.
Do not make everything public. Instead make everything private and change it to a less restrictive visibility e.g. public only when necessary. This also includes property accessors. For example, declare a private set; and public get;. Class internals must be only visible to the defining class or subclasses. See Data Hiding.

修复/改进的代码(包括正确的MVVM struct 和访问修饰符用法)如下所示:

PageId.cs

public enum PageId
{
  Default = 0,
  FirstPage,
  SecondPage,
}

MainVM.cs

public class MainVM : ObservableObject
{
  // Make code extensible by using a single navigation command 
  // which depends on the command parameter to identify the navigation target.
  public RelayCommand NextPageCommand { get; }

  // TODO::Choose a better name as the value is not a view, 
  // but a model for the view (e.g. CurrentViewData)
  private ObservableObject _currentView;
  public ObservableObject CurrentView
  {
    get => _currentView;
    private set => Set(ref _currentView, value);
  }

  // A Model reference
  private Repository Repository { get; }

  private Dictionary<PageId, ObservableObject> PageViewModels { get; }

  public MainVM()
  {
    this.PageViewModels = new Dictionary<PageId, ObservableObject>
    {
      { PageId.FirstPage, new FirstVM() },
      { PageId.SecondPage), new SecondVM() },
    };    

    this.NextPageCommand = new RelayCommand(ExecuteNextPageCommand);

    // Reference the model
    this.Repository = new Repository();

    // Initialize start page
    LoadPage(PageId.FirstPage);
  }
 
  private void ExecuteNextPageCommand(object commandParameter)
  { 
    if (commandParameter is not PageId pageId)
    {
      throw new ArgumentException($"Unexpected parameter type. ICommand.CommandParameter must be of type {typeof(PageId)}", nameof(commandParameter));
    }

    LoadPage(pageId);
  }

  private void LoadPage(PageId pageId)
  {
    ObservableObject oldPage = this.CurrentView;
    if (this.PageViewModels.TryGetValue(pageId, out ObservableObject pageViewModel))
    {
      this.CurrentView = pageViewModel;

      if (oldPage is not null)
      {
        SavePageData(oldPage);
      }
    }
  }

  /* Create overloads for individual page view model types */
  private void SavePageData(FirstVM pageViewModel)
  {
    // Delegate data persistence to the Model
    this.Repository.SaveDetails(pageViewModel.DetailsScheme);
    this.Repository.SaveMaterials(pageViewModel.Materials);
  }

  private void SavePageData(SecondVM pageViewModel)
  {
    // TODO::Delegate data persistence to the Model
    throw new NotImplementedException();
  }
}

按如下方式使用MainVM.NextPageCommand:

<Button Command="{Binding NextPageCommand}"
        CommandParameter="{x:Static local:PageId.FirstPage}" />
<Button Command="{Binding NextPageCommand}"
        CommandParameter="{x:Static local:PageId.SecondPage}" />

实现持久化的Model个类详细说明:

Repository.cs

public class Repository
{
  // Details about how persistence is implemented (file, database, web service etc.)
  // must be hidden from the View Model. 
  // Therefore, the Repository API must be kept simple.
  // Such details are encapsulated in a "low level" class,
  // in this case the type JsonRepository.
  private JsonRepository JsonRepository { get; }

  public Repository()
  {
    this.JsonRepository = new JsonRepository();
  }

  public void SaveMaterials(IEnumerable<Material> materials)
  {
    this.JsonRepository.SaveMaterials(materials);
  }

  public void SaveDetails(IEnumerable<DetailScheme> details)
  {
    this.JsonRepository.SaveDetails(details);
  }
}

JsonRepository.cs

internal class JsonRepository
{
  internal JsonRepository()
  {
  }

  internal void SaveMaterials(IEnumerable<Material> materials)
  {
    SaveMaterials(materials, "D:/Materials.json");
  }

  internal void SaveDetails(IEnumerable<DetailScheme> details)
  {
    SaveDetails(details, "D:/Details.json");
  }

  private void SaveMaterials(IEnumerable<Material> materials, string destinationPath) 
    => throw new NotImplementedException();

  private void SaveDetails(IEnumerable<DetailScheme> details, string destinationPath) 
    => throw new NotImplementedException();
}

Csharp相关问答推荐

VS Code - C# - dotnet run找不到文件,但我可以打开并编辑它们吗?

子组件:如何根据另一个组件的状态启用输入

MongoDB将JS查询转换为C#的问题

HttpContext. RequestAborted当Android APP失go 连接时未取消

并行令牌更新

如何告诉自己创建的NuGet包在应用程序中发生了变化?

实体框架核心上是否支持使用NPGSQL的字符串聚合?

当通过Google的Gmail Api发送邮件时,签名会产生dkim = neutral(正文散列未验证)'

异步实体框架核心查询引发InvalidOperation异常

.NET:从XPath定位原始XML文档中的 node

JsonPath在Newtonsoft.Json';S实现中的赋值

如何从非异步任务中正确返回TypeResult

在等待OnGetAsync时打开Razor Page显示微调器

ASP.NET MVC数据批注验证组复选框

SharpZipLib在文件名前加上目录名,生成tar.gz

将列表转换为带有逗号分隔字符串形式的值的字典

如何在更新数据库实体时忽略特定字段?

如何将行添加到DataGrid以立即显示它?

如何使ExecuteAsync异步运行

我可以阻止类型上的Object.ToString()吗?