单例视图模型 WPF
Singleton ViewModel WPF
我使用 MVVM 模式创建了一个数据网格 (almost exactly this example)。最终目标是绘制一个图表,绘制来自该数据网格的所有数据,并且当手动移动(拖动)图表的一个点时,数据网格应该更新。绘制的数据是机器人的一堆输入。
我打算构建它的方式是使用 1 个模型(输入参数列表)、2 个视图模型(一个用于数据网格,一个用于图形)和 2 个视图(相同 window)。
问题:两个视图模型应该 use/update 包含输入列表的相同 ObservableCollection
。
为了解决这个问题,我尝试了几种方法:
- 中介者模式-我不明白
- 依赖注入 - 与调解器模式相同,所有示例都是我
我发现很难理解
- Singleton pattern - 感觉我理解那个,但不能
正确实施它(我正在使用我发现清楚的 this example)
为简单起见,我目前只关注数据网格。所以使用 Singleton + MVVM 模式,我试图让它像以前一样工作(命令 add/remove 行,拖放,更新 ObservableCollection) .
所以这是单例 class:
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 class 看起来像这样:
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、destination和duration,这里不显示)
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
}
我尽量保持简短。如果需要更多信息,我可以添加。
Disclamer:我显然不是专业的编码员。我一边做这件事,一边尝试学习新事物。如果您认为我对这段代码的整个方法是错误的,我完全愿意接受新的方法。
依赖注入意味着您将依赖项(在本例中为数据点)传递给对象(在本例中为视图模型),而不是让对象创建自己的依赖项并遇到像您现在遇到的问题需要共享的依赖项。
您可以将数据集中在传递给两个视图模型的构造函数的某个对象中。但是,这将需要您引发事件/实施 INotifyPropertyChanged
,以便两个视图模型都知道更改并引发 PropertyChanged
事件。
您也可以让一个视图模型受两个不同视图的约束,但请确保您没有违反单一责任原则。我认为应该首选这种方法。请注意,您不限于视图模型和视图之间的任何 1-1 关系。您可以将多个视图绑定到一个视图模型,一个视图绑定到多个视图模型等...
无论如何,至少在这种情况下,避免使用单例。
我使用 MVVM 模式创建了一个数据网格 (almost exactly this example)。最终目标是绘制一个图表,绘制来自该数据网格的所有数据,并且当手动移动(拖动)图表的一个点时,数据网格应该更新。绘制的数据是机器人的一堆输入。
我打算构建它的方式是使用 1 个模型(输入参数列表)、2 个视图模型(一个用于数据网格,一个用于图形)和 2 个视图(相同 window)。
问题:两个视图模型应该 use/update 包含输入列表的相同 ObservableCollection
。
为了解决这个问题,我尝试了几种方法:
- 中介者模式-我不明白
- 依赖注入 - 与调解器模式相同,所有示例都是我 我发现很难理解
- Singleton pattern - 感觉我理解那个,但不能 正确实施它(我正在使用我发现清楚的 this example)
为简单起见,我目前只关注数据网格。所以使用 Singleton + MVVM 模式,我试图让它像以前一样工作(命令 add/remove 行,拖放,更新 ObservableCollection) .
所以这是单例 class:
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 class 看起来像这样:
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、destination和duration,这里不显示)
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
}
我尽量保持简短。如果需要更多信息,我可以添加。
Disclamer:我显然不是专业的编码员。我一边做这件事,一边尝试学习新事物。如果您认为我对这段代码的整个方法是错误的,我完全愿意接受新的方法。
依赖注入意味着您将依赖项(在本例中为数据点)传递给对象(在本例中为视图模型),而不是让对象创建自己的依赖项并遇到像您现在遇到的问题需要共享的依赖项。
您可以将数据集中在传递给两个视图模型的构造函数的某个对象中。但是,这将需要您引发事件/实施 INotifyPropertyChanged
,以便两个视图模型都知道更改并引发 PropertyChanged
事件。
您也可以让一个视图模型受两个不同视图的约束,但请确保您没有违反单一责任原则。我认为应该首选这种方法。请注意,您不限于视图模型和视图之间的任何 1-1 关系。您可以将多个视图绑定到一个视图模型,一个视图绑定到多个视图模型等...
无论如何,至少在这种情况下,避免使用单例。