首页 > 解决方案 > 单例视图模型 WPF

问题描述

我使用 MVVM 模式创建了一个数据网格(几乎就是这个例子)。最终目标是有一个图表来绘制来自该数据网格的所有数据,并且当手动移动(拖动)图表的一个点时数据网格应该更新。绘制的数据是机器人的一堆输入。

我打算使用 1 个模型(输入参数列表)、2 个视图模型(一个用于数据网格,一个用于图形)和 2 个视图(同一窗口)来构建它。

问题:两个视图模型都应该使用/更新ObservableCollection包含输入列表的相同视图模型。

为了解决这个问题,我尝试了几种方法:

为简单起见,我目前只关注数据网格。因此,使用 Singleton + MVVM 模式,我试图让它以与以前相同的方式工作(添加/删除行、拖放、更新ObservableCollection的命令)。

所以这是单例类:

class SingletonDoliInputCollection : ViewModelBase
{
    #region Events
    void OnDoliCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // Update item count
        this.ItemCount = this.DoliInputCollection.Count;

        // Resequence list
        SetCollectionSequence(this.DoliInputCollection);
    }
    #endregion

    #region Fields
    private ObservableCollection<DoliInput> _doliInputCollection;
    private int _itemCount;
    //Singleton
    private static SingletonDoliInputCollection _instance;
    #endregion

    #region Singleton Constructor
    private SingletonDoliInputCollection() { }
    #endregion

    #region Properties   
    public ObservableCollection<DoliInput> DoliInputCollection
    {
        get => _doliInputCollection;
        set
        {
            _doliInputCollection = value;
            OnPropertyChanged("DoliInputCollection");
        }
    }

    public static SingletonDoliInputCollection GetInstance(ObservableCollection<DoliInput> DoliInputCollection)
    {
        if (_instance == null)
        {
            _instance = new SingletonDoliInputCollection();
            _instance.Initialise(DoliInputCollection);
        }

        return _instance;
    }


    public int ItemCount
    {
        get => _itemCount;
        set
        {
            _itemCount = value;
            OnPropertyChanged("ItemCount");
        }
    }
    /// <summary>
    /// Return selected item in the grid
    /// </summary>
    public DoliInput SelectedItem { get; set; }
    #endregion

    #region Manage Sequencing
    /// <summary>
    /// Resets the sequential order of a collection.
    /// </summary>
    /// <param name="targetCollection">The collection to be re-indexed.</param>
    public static ObservableCollection<T> SetCollectionSequence<T>(ObservableCollection<T> targetCollection) where T : ISequencedObject
    {
        // Initialize
        var sequenceNumber = 1;

        // Resequence
        foreach (ISequencedObject sequencedObject in targetCollection)
        {
            sequencedObject.SequenceNumber = sequenceNumber;
            sequenceNumber++;
        }

        // Set return value
        return targetCollection;
    }
    #endregion

    #region Private Methods
    #region Initialise
    private void Initialise(ObservableCollection<DoliInput> DoliInputCollection)
    {
        //Create inputList
        _instance.DoliInputCollection = new ObservableCollection<DoliInput>();

        //Add items
        _instance.AddInput("Load", 3, 2, 1);
        _instance.AddInput("Position", 3, 11, 1);
        _instance.AddInput("Position", 3, 2, 4);
        _instance.AddInput("Load", 3, 2, 1);

        //Subscribe to the event that gets trigger when change occurs
        _instance.DoliInputCollection.CollectionChanged += OnDoliCollectionChanged;

        //Start indexing items
        this.DoliInputCollection = SetCollectionSequence(this.DoliInputCollection);

        //Update if changes
        this.OnPropertyChanged("DoliInputCollection");
        this.OnPropertyChanged("GridParam");
    }
    #endregion

    #endregion

    #region Public Methods
    public void AddInput(string CTRL, double Destination, double Speed, double Duration)
    {
        this.DoliInputCollection.Add(new DoliInput(CTRL, Destination, Speed, Duration));
    }
    #endregion

}

ViewModel 类如下所示:

public class DataGridVM : ViewModelBase
{
    #region Constructor

    public ObservableCollection<DoliInput> DoliInputCollection { get; set; }
    public DoliInput SelectedItem { get; set; }

    public DataGridVM()
    {
        //ObservableCollection<DoliInput> DoliInputCollection = new ObservableCollection<DoliInput>();
        SingletonDoliInputCollection doliInputs = GetDoliInputCollectionInstance(DoliInputCollection);
        DoliInputCollection = doliInputs.DoliInputCollection;
        SelectedItem = doliInputs.SelectedItem;
        Console.WriteLine(doliInputs.DoliInputCollection.ToString());
    }

    #endregion

    #region Properties
    public ICommand DeleteItem { get; set; }
    public ICommand AddRow { get; set; }

    #endregion

    #region Private Methods
    #region Initialise
    private static SingletonDoliInputCollection GetDoliInputCollectionInstance(ObservableCollection<DoliInput> DoliInputs)
    {
        SingletonDoliInputCollection singleton = SingletonDoliInputCollection.GetInstance(DoliInputs);
        return singleton;
    }
    #endregion

    #endregion

}

对于视图,这里只是列格式的示例:

<!--[...]-->
xmlns:dataGrid="clr-namespace:InteractiveGraph.Grid"
<!--[...]-->
<DataGridTextColumn Binding="{Binding Path=(dataGrid:DoliInput.Speed), Mode=TwoWay}" Header="Speed" />
<!--[...]-->

最后一个简化版的模型(还有其他3个属性:CTRL、目的地和持续时间,这里不显示)

public class DoliInput : ObservableObject, ISequencedObject
{
    #region Fields
    private double _speed;
    private int _seqNb;
    #endregion

    #region Properties

    public double Speed 
    { 
        get => _speed; 
        set 
        { 
            _speed = value; 
            OnPropertyChanged("Speed"); 
        } 
    }

    public int SequenceNumber 
    { 
        get => _seqNb;
        set
        {
            _seqNb = value;
            OnPropertyChanged("SequenceNumber");
        }
    }

    #endregion

    #region Constructor

    public DoliInput(){  }

    public DoliInput(string CTRL, double destination, double speed, double duration)
    {
        this._speed = speed;
    }
    #endregion
}

我尽量保持简短。如果需要更多信息,我可以添加。

免责声明:我显然不是专业的编码员。我一边做这件事,一边尝试学习新事物。如果您认为我对这段代码的整个方法是错误的,我完全愿意接受新的方法。

标签: c#wpfmvvmsingleton

解决方案


依赖注入意味着您将依赖项(在本例中为数据点)传递给对象(在本例中为视图模型),而不是让对象创建自己的依赖项并遇到像现在需要依赖项时遇到的问题共享。

您可以将数据集中在传递给两个视图模型的构造函数的某个对象中。但是,这将要求您引发事件/实施INotifyPropertyChanged,以便两个视图模型都知道更改和引发PropertyChanged事件。

你也可以让一个视图模型被两个不同的视图绑定,但要确保你没有违反单一责任原则。我认为应该首选这种方法。请注意,您不仅限于视图模型和视图之间的任何 1-1 关系。您可以将多个视图绑定到一个视图模型,一个视图绑定到多个视图模型等...

在任何情况下,都要避免单例,至少在这种情况下是这样。


推荐阅读