XAML WPF如何获取有关从一个视图模型更改另一个视图模型的信息

ebdffaop  于 12个月前  发布在  其他
关注(0)|答案(2)|浏览(109)

我有主窗口中的内容控制和两个收音机按钮loceted. 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>


我还有主视图模型,它是主窗口的数据上下文:

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 => 
     {***});
 }


我需要保存在我的列表框中的信息时,我的用户控制切换(例如,用户选择框中的东西,并按下一个按钮,列表框中的数据正在更新后,现在切换控制我有相同的信息,因为它是当我启动应用程序)
我在我的用户控件视图模型中添加了将信息保存到文件的方法:

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


当我在主视图模型中切换视图时,

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


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

ca1c2owp

ca1c2owp1#

你已经用“MVVM”标记了你的问题,但你没有实现它。在MVVM中,你有负责数据持久化的 Model。MVVM并不意味着把所有东西都压缩到 View Model 类中。拥有一个带有 Model 类的 Model 应该会让流程更明显。
您可以处理Frameworkelement.Unloaded事件以获得有关当前视图正在卸载的通知。然而,优雅的解决方案是管理与页面相关的持久性,您已经完全控制了页面管理:您的MainVM类。
代码应该是内聚的,类似或相关的任务不应该分散在应用程序中。
MainVM已经能够精确地识别页面切换,而无需额外的努力。由于MainVM应该引用一个或多个 Model 类,以便获取它将暴露给视图的数据,例如通过初始化页面视图模型类,它也能够调用相关的模型类进行持久化。
请注意,在正确的干净的OO编程中,你必须关心类型和类型成员的可访问性(访问修饰符,如publicprivate)。这是一个非常基本的技术,可以使你的代码健壮,API定义良好。
不要将所有内容都设置为public。相反,将所有内容设置为private,并仅在必要时将其更改为限制较少的可见性,例如public。这还包括属性访问器。例如,声明private set;和公共get;。类内部必须仅对定义类或子类可见。请参阅数据隐藏。
修复/改进的代码(包括正确的MVVM结构和访问修饰符用法)如下所示:

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 类:

仓库.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();
}

wko9yo5t

wko9yo5t2#

我使用一个名为ObservableHandler<T>的类,它让我可以监视另一个实现INotifyPropertyChanged并侦听PropertyChanged事件的类。
如果你要使用它,你可以在另一个ViewModel中观察属性的变化,而不必保存到文件中。

用法

public class OtherViewModel
{
    private ObservableHandler<ViewModel> _handler;
    
    public OtherViewModel(ViewModel vm)
    {
        _handler = new ObservableHandler<ViewModel>(vm)
            .Add(v => v.Name, OnOtherNameChanged);
    }
    
    private void OnOtherNameChanged(ViewModel vm)
    {
        // react to the other property changing
    }
}

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string Name { get; set; }
}

字符串

Observable可观察性

public class ObservableHandler<T> : IWeakEventListener
    where T : class, INotifyPropertyChanged
{
    private readonly WeakReference<T> _source;
    private readonly Dictionary<string, Action> _handlers = new();
    private readonly Dictionary<string, Action<T>> _handlersT = new();

    public ObservableHandler(T source)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }

        _source = new(source);
    }

    
    public ObservableHandler<T> Add(Expression<Func<T, object>> expression, Action handler)
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }

        var source = GetSource();
        if (source == null)
        {
            throw new InvalidOperationException("Source has been garbage collected.");
        }

        var propertyName = GetPropertyNameFromLambda(expression);

        _handlers[propertyName] = handler;
        PropertyChangedEventManager.AddListener(source, this, propertyName);

        return this;
    }

    
    public ObservableHandler<T> Add(Expression<Func<T, object>> expression, Action<T> handler)
    {
        if (handler == null)
        {
            throw new ArgumentNullException(nameof(handler));
        }

        var source = GetSource();
        if (source == null)
        {
            throw new InvalidOperationException("Source has been garbage collected.");
        }

        var propertyName = GetPropertyNameFromLambda(expression);

        _handlersT[propertyName] = handler;
        PropertyChangedEventManager.AddListener(source, this, propertyName);

        return this;
    }

    public ObservableHandler<T> AddAndInvoke(Expression<Func<T, object>> expression, Action handler)
    {
        Add(expression, handler);
        handler();
        return this;
    }

    public ObservableHandler<T> AddAndInvoke(Expression<Func<T, object>> expression, Action<T> handler)
    {
        Add(expression, handler);
        handler(GetSource());
        return this;
    }

    private T GetSource()
    {
        if (_source.TryGetTarget(out var source)) return source;

        throw new InvalidOperationException($"{nameof(source)} has been garbage collected.");
    }

    bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
        return OnReceiveWeakEvent(managerType, sender, e);
    }

    public virtual bool OnReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
        if (managerType != typeof(PropertyChangedEventManager))
        {
            return false;
        }

        var propertyName = ((PropertyChangedEventArgs)e).PropertyName;
        Notify(propertyName);

        return true;
    }

    protected void Notify(string propertyName)
    {
        var source = GetSource();
        if (source == null)
        {
            throw new InvalidOperationException("Confused, received a PropertyChanged event from a source that has been garbage collected.");
        }

        if (string.IsNullOrEmpty(propertyName))
        {
            foreach (var handler in _handlers.Values)
            {
                handler();
            }
            foreach (var handler in _handlersT.Values)
            {
                handler(source);
            }
        }
        else
        {
            if (_handlers.TryGetValue(propertyName, out var handler))
                handler();

            if (_handlersT.TryGetValue(propertyName, out var handlerT))
                handlerT(source);
        }
    }

    public static string GetPropertyNameFromLambda(Expression<Func<T, object>> expression)
    {
        var lambda = expression as LambdaExpression;
        MemberExpression memberExpression;

        if (lambda.Body is UnaryExpression unaryExpression)
        {
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }

        if (memberExpression == null)
            throw new ArgumentException("Property name lambda expression needs to be in the form: n = > n.PropertyName");

        var propertyInfo = memberExpression.Member as PropertyInfo;

        if (propertyInfo == null)
            throw new InvalidOperationException("Bug, memberExpression.Member as PropertyInfo cast failed.");

        return propertyInfo.Name;
    }
}

相关问题