As many have observed with MVVM, you do not want the View and Model to be directly coupled. Therefore, I have my View bound to an ObservableCollection
in my ViewModel which needs to be updated from a List
in my Model.
What I have tried to do is create the CanvasObject
in the CanvasModel, add it to the List
, and send a reference, via events, to the ViewModel and update the ObservableCollection
there.
The following code and explanation will hopefully illustrate my attempt:
In my View, I have an ItemsControl
with the ItemSource binding to my ObservableCollection
with an ItemsPanelTemplate
pointing to a Canvas
.
<ItemsControl ItemsSource="{Binding CanvasObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="1000" Height="1000" Background="GhostWhite"
MouseDown="Canvas_MouseDown"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
To allow a mouse click, I added a MouseDown
event
public partial class CanvasView : UserControl
{
dynamic viewModel;
public CanvasView()
{
InitializeComponent();
Loaded += InitializeViewModel;
}
private void InitializeViewModel(object sender, RoutedEventArgs e)
{
viewModel = DataContext;
Loaded -= InitializeViewModel;
}
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
viewModel.MouseDownEventHandler(sender, e);
}
}
In my ViewModel, I implement INotifyPropertyChanged
and also implemented a custom Interface
for handling the mouse events from the View as you can see from the above code. I also implement a few event dispatchers for a separate ToolPanelView and other interconnected events. The ObservableCollection
is a basic Property and I am creating the Model here in the ViewModel
internal class CanvasViewModel : ViewModelBase, IMouseEventController
{
private readonly ToolPanelDispatcher pToolPanelDispatcher;
private readonly CanvasDispatcher pCanvasDispatcher;
private readonly CanvasObjectDispatcher pCanvasObjectDispatcher;
private CanvasModel pCanvasModel;
private ToolMode pToolMode;
private Point pCurrentMousePointerCoordinates;
private Point pPreviousMousePointerCoordinates;
public ObservableCollection<CanvasObject> CanvasObjects { get; set; }
public CanvasViewModel(ToolPanelDispatcher toolPanelDispatcher, CanvasDispatcher canvasDispatcher, CanvasObjectDispatcher canvasObjectDispatcher)
{
pToolPanelDispatcher = toolPanelDispatcher;
pCanvasDispatcher = canvasDispatcher;
pCanvasObjectDispatcher = canvasObjectDispatcher;
// Injects CanvasDispatcher and CanvasObjectDispatcher into CanvasModel
pCanvasModel = new CanvasModel(pCanvasDispatcher, pCanvasObjectDispatcher);
CanvasObjects = new ObservableCollection<CanvasObject>();
// Subscribes/listens for when ToolMode changes on the ToolPanelView
pToolPanelDispatcher.ToolModeChanged += ToolModeChangedEventHandler;
pCanvasDispatcher.CanvasObjectAdded += CanvasObjectAddedEventHandler;
}
private void ToolModeChangedEventHandler(ToolMode toolMode)
{
pToolMode = toolMode;
// Relays current tool mode to CanvasObjects
pCanvasDispatcher.PublishToolModeChanged(pToolMode);
}
private void CanvasObjectAddedEventHandler(CanvasObject canvasObject)
{
CanvasObjects.Add(canvasObject);
}
public void MouseDownEventHandler(object sender, MouseButtonEventArgs e)
{
pCurrentMousePointerCoordinates = e.GetPosition(sender as Canvas);
switch (pToolMode)
{
case ToolMode.Node:
pCanvasModel.DrawCanvasObject(CanvasObjectType.Node, pCurrentMousePointerCoordinates);
break;
}
pPreviousMousePointerCoordinates = pCurrentMousePointerCoordinates;
}
}
In my Model, it is pretty basic: I have my List
for CanvasObjects
, the event dispatchers, and a draw/create method.
internal class CanvasModel
{
private readonly List<CanvasObject> pCanvasObjects;
private readonly CanvasDispatcher pCanvasDispatcher;
private readonly CanvasObjectDispatcher pCanvasObjectDispatcher;
public CanvasModel(CanvasDispatcher canvasDispatcher, CanvasObjectDispatcher canvasObjectDispatcher)
{
pCanvasObjects = new List<CanvasObject>();
pCanvasDispatcher = canvasDispatcher;
pCanvasObjectDispatcher = canvasObjectDispatcher;
}
public void DrawCanvasObject(CanvasObjectType canvasObjectType, Point origin)
{
var canvasObject = new Node(origin, pCanvasDispatcher, pCanvasObjectDispatcher);
pCanvasObjects.Add(canvasObject);
pCanvasDispatcher.OnCanvasObjectAdded(canvasObject);
}
}
You may note that in the DrawCanvasObject
method there is an unused CanvasObjectType
. This is for expanded functionality that is not implemented yet. Also note that CanvasObject
is a base for Node
and other unnamed CanvasObjects
that are Custom Controls inherited from Control
to allow them to be children of a Canvas
and to utilize Mouse Events. Further note that the events are basic events utilizing this pattern:
public delegate void CanvasObjectAddedEventHandler(CanvasObject canvasObject);
public event CanvasObjectAddedEventHandler? CanvasObjectAdded;
public void OnCanvasObjectAdded(CanvasObject canvasObject)
{
CanvasObjectAdded?.Invoke(canvasObject);
}
However, to reitterate, this is only way that I was able to think on how to update the ObservableCollection
by "dispatching" a reference of the CanvasObject
and have the ViewModel listen for and then update the ObservableCollection
.
Is this the only way it can be done or is there a more efficient/direct way to update an ObservableCollection
from a List
?
Any help would be greatly appreciated, especially if there are best practices that could be better followed.