如何修改发送到另一个类的模型的值而不影响原始模型

问题描述 投票:0回答:1
public class MyModel : INotifyPropertyChanged
{
    private MyEnum _myEnumValue;

    public MyEnum MyEnumValue
    {
        get => _myEnumValue;
        set
        {
            if (value == _myEnumValue) return;
            _myEnumValue = value;
            OnPropertyChanged();
        }
    }
//OnPropertyChanged things
}

public class MainViewModel
{
    private MyModel _myModelVariable;
    public MyModel MyModelVariable
    {
        get=>_myModelVariable;
        set{_myModelVariable = value; OnPropertyChanged();} 
    }

    public MainViewModel()
    {
        MyModelVariable = new();
        MyModelVariable.MyEnumValue = MyEnum.Enum1;
    }

    public void OptionsButton()
    {
        _windowManager.ShowDialogAsync(new EditViewModel(MyModelVariable));
    }   
}

public class EditViewModel : INotifyPropertyChanged
{
    private MyModel _localModel;

    public EditClass(MyModel myModel)
    {
        _localModel = myModel
    }

    public MyModel LocalModel
    {
        get=>_localModel;
        set
        {
            _localModel = value;
            OnPropertyChanged();
        }
    }

    public void OkButton()
    {
        // sending LocalModel to Main View Model with EventAggregator
    }

    public void CancelButton()
    {
        CloseWindow();
    }
}

给定的代码是根据原始代码模拟的。 在我的 WPF 应用程序中,我使用 Caliburn Micro 并使用视图模型。在我的屏幕之一上,用户按下选项按钮来更新值。当按下按钮时,会打开一个新窗口,该窗口由 EditViewModel 控制。我使用“new”创建窗口并使用 IWindowManager 打开它。打开它时,我发送了我的原始模型。在 EditViewModel 中,我将原始模型分配给局部变量并对其执行操作。绑定前端ComboBox中的Selected值。当用户更改它时,我也会更改局部变量。如果用户按“确定”,我将使用 EventAggregator 将新值发送到 MainViewModel。如果他们按“取消”,我将关闭窗口而不执行任何操作。

问题:当用户改变EditViewModel前端的值时,MainViewModel中原始模型的值也会改变。这是一种可能的情况,因为它将原始值视为参考,但我不希望这种情况发生。我尝试过克隆、复制等解决方案,但无法解决。您有什么建议?

问题解决了
类是引用类型。我将

private MyEnum _myEnum
变量添加到 EditViewModel。 我在构造函数中将 _localModel.MyEnumValue 分配给 _myEnum 并解决了问题。在我看来,Enum 是值类型,因此我将 MyModel 的引用类型转换为值类型。

c# wpf class model
1个回答
0
投票

为什么你的模型参考是

public
,你绑定到它们了吗?您是否知道模型不能暴露给视图?
为什么您的视图模型与按钮事件以及打开和关闭窗口有关?您是否知道视图模型不得主动与视图交互?不要这样做。除了增加额外和不必要的复杂性之外,您还允许 UI 逻辑渗透到应用程序逻辑中。我们使用各种设计模式和设计原则来避免这种情况。
对话框始终在视图中进行管理。

您必须确保编辑原始实例的副本。编辑过程完成后,您将更新原始实例。
关键是在可编辑对象中添加一个中间数据层,以启用临时数据状态(编辑和提交等编辑流程)和数据回滚。

我建议实现

IEditableObject
界面。

以下示例展示了如何编辑数据并支持提交或取消临时编辑。如果取消,对象的数据更改将恢复。
该示例还展示了如何正确处理对话框和应用程序模型。

ChildViewModel.cs
可编辑的数据对象。

public class ChildViewModel : IEditableObject, INotifyPropertyChanged
{
  private class ChildViewModelEditableDataModel : ICloneable
  {
    public ChildViewModelEditableDataModel()
    {
    }

    // Copy constructor for creating a deep copy
    public ChildViewModelEditableDataModel(ChildViewModelEditableDataModel myClassDataModelToCopy)
    {
      this.NumericValue = myClassDataModelToCopy.NumericValue;
      this.TextValue = myClassDataModelToCopy.TextValue;
    }

    public int NumericValue { get; set; } 
    public string TextValue { get; set; }

    // Create a shallow copy
    public ChildViewModelEditableDataModel Clone() => (ChildViewModelEditableDataModel)MemberwiseClone();

    // Create a deep copy
    public ChildViewModelEditableDataModel DeepClone() => new ChildViewModelEditableDataModel(this);

    object ICloneable.Clone() => Clone();
  }

  public ChildViewModel()
  {
    this.CurrentDataModel = new ChildViewModelEditableDataModel();
    this.BackupDataModel = this.CurrentDataModel.DeepClone();
  }

  public void BeginEdit() => this.BackupDataModel = this.CurrentDataModel;
  public void CancelEdit() => this.CurrentDataModel = this.BackupDataModel;
  public void EndEdit() => OnDataChanged();

  protected virtual void OnDataChanged() 
    => this.DataChanged?.Invoke(this, EventArgs.Empty);

  public event EventHandler DataChanged;

  public int NumericValue 
  { 
    get => this.CurrentDataModel.NumericValue;
    set => this.CurrentDataModel.NumericValue = value; 
  }

  public string TextValue
  {
    get => this.CurrentDataModel.TextValue;
    set => this.CurrentDataModel.TextValue = value;
  }

  private ChildViewModelEditableDataModel CurrentDataModel { get; set; }
  private ChildViewModelEditableDataModel BackupDataModel { get; set; }
}

MainViewModel.cs

public class MainViewModel : INotifyPropertyChanged
{
  public ChildViewModel ChildViewModel { get; }

  // Do not expose the Model to the View
  private SomeModelClass SomeModelClass { get; }

  public MainViewModel()
  {
    this.ChildViewModel = new ChildViewModel();

    // React to committed data changes of an edit procedure
    this.ChildViewModel.DataChanged += OnChildViewModelDataChanged;

    this.SomeModelClass = new SomeModelClass();
  }

  private void OnChildViewModelDataChanged(object? sender, EventArgs e)
  {
    // Update the Model with the modified data (example).
    // Make sure to not pass the view model instance.
    // Instead pasas a model type that is defined in the Model.
    this.SomeModelClass.Update(this.ChildViewModel.TextValue, this.ChildViewModel.NumericValue);
  }
}

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public static RoutedCommand ShowEditDialogCommand = new RoutedCommand(nameof(ShowEditDialogCommand), typeof(MainWindow));

  public MaainWindow()
  {
    InitializeComponent();

    var showEditDialogCommandBinding = new CommandBinding(ShowEditDialogCommand, ExecuteShowEditDialogCommand, CanExecuteShowEditDialogCommand);
    this.CommandBindings.Add(showEditDialogCommandBinding);

    this.DataContext = new MainViewModel();
  }

  private void CanExecuteShowEditDialogCommand(object sender, CanExecuteRoutedEventArgs e) 
    => e.CanExecute = e.Parameter is IEditableObject;
  
  private void ExecuteShowEditDialogCommand(object sender, ExecutedRoutedEventArgs e)
  {
    IEditableObject editableData = (IEditableObject)e.Parameter;
    var editDialog = new Window() { DataContext = editableData };
    editableData.BeginEdit();
    bool? dialogResult = editDialog.ShowDialog();
    if (dialogResult == true)
    {
      editableData.EndEdit();
    }
    else
    {
      editableData.CancelEdit();
    }
  }
}

MainWindow.xaml

<Window>
  <StackPanel DataContext="{Binding ChildViewModel}">
    <TextBox Text="{Binding TextValue}" />
    <TextBox Text="{Binding NumericValue}" />

    <!-- Trigger the dialog to edit the data -->
    <Button Content="Edit"
            Command="{x:Static MainWindow.ShowEditDialogCommand}"
            CommandParameter="{Binding}" />
  </StackPanel>
</Window>
© www.soinside.com 2019 - 2024. All rights reserved.