I’ve got an ObservableCollection of objects that are classified with a name and a set of 3D coordinates. I’ve also got an ObservableCollection of layers, each supposed to hold a 2D grid.
Problem Setting
The objective is to use an ItemsControl, probably nested, to display those objects in the following fashion: if the Z coordinate determines the layer, and the X and Y coordinates specify each object’s position on the grid, then the object should be displayed in a TabControl, where the TabItem corresponds to the Z coordinate and hosts a Grid where the Grid.Row and Grid.Column attributes determine where the object’s name is written on the TabItem.
Important: while each 3D coordinate is only used once, one “Entry” object may have multiple 3D coordinates, and as such may appear various times on a grid and/or different TabItems.
Note: the data model is not carved in stone; if the problem can be solved with another data model, I’m open to change it. The display, however, is a customer requirement—it might be modified, but I’d need extremely good arguments for that.
Background Info
The objects look like this (BindableBase is from the Prism Library):
public class Entry : BindableBase {
public string Name { set; get; }
private EntryCoordinates coordinates;
public EntryCoordinates Coordinates {
set { SetProperty(ref coordinates, value); }
get { return coordinates; }
}
}
public class EntryCoordinates : BindableBase {
private int x;
public int X {
set { SetProperty(ref x, value); }
get { return x; }
}
private int y;
public int Y {
set { SetProperty(ref y, value); }
get { return y; }
}
private int z;
public int Z {
set { SetProperty(ref z, value); }
get { return z; }
}
}
The Entry objects are hosted in “entry layers”:
public class EntryLayer : ObservableCollection<Entry> {
}
Eventually, I want to be able to modify the Entry objects (which are more complex in reality) via the UI, so two-way data binding is an absolute necessity.
Effort so far
Using #Rachel’s excellent WPF Grid Extension, I implemented an ItemsControl that populates a Grid as desired:
<ItemsControl ItemsSource="{Binding EntryLayers, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid GridExtension.RowCount="{Binding RowCount}"
GridExtension.ColumnCount="{Binding ColumnCount}"
GridExtension.StarRows="{Binding StarRows}"
GridExtension.StarColumns="{Binding StarColumns}"
IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding Coordinates.X}"/>
<Setter Property="Grid.Column" Value="{Binding Coordinates.Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<GridCellThumb XCoordinate="{Binding Coordinates.X}"
YCoordinate="{Binding Coordinates.Y}">
<Thumb.Template>
<ControlTemplate>
<TileControl Entry="{Binding}"/>
</ControlTemplate>
</Thumb.Template>
</GridCellThumb>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The GridCellThumb is a custom control that allows for drag and drop (omitted here for clarity’s sake) and exposes coordinate dependency properties:
public class GridCellThumb : Thumb {
public static readonly DependencyProperty XCoordinateProperty = DependencyProperty.Register("XCoordinate", typeof(int), typeof(GridCellThumb));
public int XCoordinate {
get { return (int)GetValue(XCoordinateProperty); }
set { SetValue(XCoordinateProperty, value); }
}
public static readonly DependencyProperty YCoordinateProperty = DependencyProperty.Register("YCoordinate", typeof(int), typeof(GridCellThumb));
public int YCoordinate {
get { return (int)GetValue(YCoordinateProperty); }
set { SetValue(YCoordinateProperty, value); }
}
}
The TileControl is a user control that displays an Entry’s name:
<StackPanel Orientation="Vertical">
<Label Content="{Binding Path=Name}" />
</StackPanel>
I’m stuck in finding out how to wrap the original ItemsControl in a TabControl template so that the objective of displaying an entry, possible various times, correctly. For instance, binding to the Coordinates.Z path works, but creates as many TabItems as there are entries:
<TabControl ItemsSource="{Binding Entries}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header"
Value="{Binding Coordinates.Z}" />
<Setter Property="TabIndex"
Value="{Binding Coordinates.Z}" />
</Style>
</TabControl.ItemContainerStyle>
<!-- the ItemsControl goes here -->
</TabControl>
I’ve tried the solutions proposed by #greg40 (nested ItemsControl), #d.moncada (another nested ItemsControl), and #Sheridan (going up the visual tree), but I always gloriously fail when relating an Entry to a given EntryLayer.
Does anyone have further ideas to explore? As I said, I’m also open to re-structuring my data model, if that leads to an easier solution.
Update 2018-01-08
I’ve explored the path of using Buttons instead of a TabControl in the sense of data-binding their clicked state to displaying varying information on the grid. This, however, only shifted the problem and created a new one: the data isn’t pre-loaded anymore, which is crucial to the customer’s requirements.
I’m now strongly considering to suggest a change of requirements to the customer and devise a different data model altogether. In order to avoid taking a wrong route again, I’d very much appreciate if there was someone in the community with a strong opinion about this problem that they’d be willing to share with me.
After some very valuable offline advice from a peer expert, I decided against the customer suggested data model and provide them with an alternative solution. The approach is now to fill the View in a top-down fashion.
In essence, this means that what was previously my Z coordinate is now the index of a dedicated EntryLayer object defining the TabItems. Each EntryLayer has an ObservableCollection<Entry> that refers to those Entrys that a parts of that EntryLayer.
This contrasts to the initial model in the sense that now it’s not possible anymore that a Entry can have multiple coordinates; instead, the Entry itself might exist multiple times with different coordinates.
How did I sell it to the customer? I told them that now an Entry could have customization options that may differ between its representations on the same or other EntryLayers, e.g., by using user-specified colors and fonts, while still pointing back to the same base information. It gives me some more work to do implementation-wise, but it elegantly solves the deadlock by putting it in the shape of a new feature.
Related
I have a map as a bitmap, on top of which I add markers - which can have different icons of different sizes, which contain data, and have various other options. The map is an image, and the way it's implemented now, is redrawing the map's area at certain position, with marker icons by iterating over the picture and setting pixels one by one using marker's image. But I realize this is probably not the best option avaiable so I am asking here.
I was thinking about using markers as controls, but I am not sure how I would accurately and programatically get and set the markers positions.
A usual pattern followed in WPF apps is MVVM - you should have a look at online resources to get familiar with it and how it can leverage the power of WPF.
To answer your specific problem, you would build your app in a way that is data-driven: you create and expose a collection of markers as data objects and you bind the view to this collection by telling how an individual marker should be displayed.
Let's create a MarkerViewModel class that will contain all information the view needs to display them:
public class MarkerViewModel {
public double X { get; set; }
public double Y { get; set; }
public string Description { get; set; }
}
Your MainWindow code-behind could be:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new ObservableCollection<MarkerViewModel> {
new MarkerViewModel() { X = 30, Y = 30},
new MarkerViewModel() { X = 100, Y = 20},
new MarkerViewModel() { X = 100, Y = 150}
};
}
}
We put some data in a collection (of type ObservableCollection) and assigned it to the DataContext property of the MainWindow.
Its XAML could be:
<Grid>
<Image Source="Chrysanthemum.jpg" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ItemsControl ItemsSource="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:MarkerViewModel}">
<StackPanel>
<Ellipse Width="10"
Height="10"
Fill="Blue"/>
<TextBlock FontWeight="Bold" Text="{Binding Description}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The Grid is here to overlay its two children: an Image which is the background picture and could be your base map, and an ItemsControl whish is repsonsible for displaying a view for each item in the DataContext collection.
The ItemsControl.ItemTemplate property lets you tell WPF how you want each marker displayed: I chose here a blue Ellipse and a TextBlock.
The ItemsControl.ItemContainerStyle lets you tell WPF where you want to position each item it creates (it can do much more).
I suggest you start reading some tutorials on data binding, styling and templating and ItemsControl specifically. Also read more on the MVVM pattern, but hopefully my example will help you get kickstarted.
I'm trying to improve performance with my WPF application and I'm having problems with a complex ItemsControl. Although I've added Virtualization, there's still a performance problem and I think I've worked out why.
Each item contains a series of expandable areas. So the user sees a summary at the start but can drill down by expanding to see more information. Here's how it looks:
As you can see, there's some nested ItemsControls. So each of the top level items has a bunch of Hidden controls. The virtualization prevents off-screen items from loading, but not the hidden items within the items themselves. As a result, the relatively simple initial layout takes a significant time. Flicking around some of these views, 87% of time is spent parsing and Layout, and it takes a few seconds to load.
I'd much rather have it take 200ms to expand when (if!) the user decides to, rather than 2s to load the page as a whole.
Asking for advice really. I can't think of a nice way of adding the controls using MVVM however. Is there any expander, or visibility based virtualization supported in WPF or would I be creating my own implementation?
The 87% figure comes from the diagnostics:
If you simply have
- Expander
Container
some bindings
- Expander
Container
some bindings
+ Expander
+ Expander
... invisible items
Then yes, Container and all bindings are initialized at the moment when view is displayed (and ItemsControl creates ContentPresenter for visible items).
If you want to virtualize content of Expander when it's collapsed, then you can use data-templating
public ObservableCollection<Item> Items = ... // bind ItemsControl.ItemsSource to this
class Item : INotifyPropertyChanged
{
bool _isExpanded;
public bool IsExpanded // bind Expander.IsExpanded to this
{
get { return _isExpanded; }
set
{
Data = value ? new SubItem(this) : null;
OnPropertyChanged(nameof(Data));
}
}
public object Data {get; private set;} // bind item Content to this
}
public SubItem: INotifyPropertyChanged { ... }
I hope there is no need to explain how to to do data-templating of SubItem in xaml.
If you do that then initially Data == null and nothing except Expander is loaded. As soon as it's expanded (by user or programmatically) view will create visuals.
I thought I'd put the details of the solution, which is pretty much a direct implementation of Sinatr's answer.
I used a content control, with a very simple data template selector. The template selector simply checks if the content item is null, and chooses between two data templates:
public class VirtualizationNullTemplateSelector : DataTemplateSelector
{
public DataTemplate NullTemplate { get; set; }
public DataTemplate Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
{
return NullTemplate;
}
else
{
return Template;
}
}
}
The reason for this is that the ContentControl I used still lays out the data template even if the content is null. So I set these two templates in the xaml:
<ContentControl Content="{Binding VirtualizedViewModel}" Grid.Row="1" Grid.ColumnSpan="2" ><!--Visibility="{Binding Expanded}"-->
<ContentControl.Resources>
<DataTemplate x:Key="Template">
<StackPanel>
...complex layout that isn't often seen...
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="NullTemplate"/>
</ContentControl.Resources>
<ContentControl.ContentTemplateSelector>
<Helpers:VirtualizationNullTemplateSelector Template="{StaticResource Template}" NullTemplate="{StaticResource NullTemplate}"/>
</ContentControl.ContentTemplateSelector>
</ContentControl>
Finally, rather than using a whole new class for a sub-item, it's pretty simple to create a "VirtualizedViewModel" object in your view model that references "this":
private bool expanded;
public bool Expanded
{
get { return expanded; }
set
{
if (expanded != value)
{
expanded = value;
NotifyOfPropertyChange(() => VirtualizedViewModel);
NotifyOfPropertyChange(() => Expanded);
}
}
}
public MyViewModel VirtualizedViewModel
{
get
{
if (Expanded)
{
return this;
}
else
{
return null;
}
}
}
I've reduced the 2-3s loading time by about by about 75% and it seems much more reasonable now.
This simple solution helped me:
<Expander x:Name="exp1">
<Expander.Header>
...
</Expander.Header>
<StackPanel
Margin="10,0,0,0"
Visibility="{Binding ElementName=exp1, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
<Expander x:Name="exp2">
<Expander.Header>
...
</Expander.Header>
<StackPanel
Margin="10,0,0,0"
Visibility="{Binding ElementName=exp2, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
An easier way to achieve this is to change the default Visibility of the contents to Collapsed. In this case WPF won't create it initially, but only when a Trigger sets it to Visible:
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility"Value="Visible" TargetName="ExpandSite"/>
</Trigger>
Here "ExpandSite" is the ContentPresenter within the default ControlTemplate of the Expander control.
Note that this has been fixed in .NET - see the default style from the WPF sources on github.
In case you have an older version, you can still use this fixed control template to update the old one with an implicit style.
You can apply the same technique to any other panel or control.
It's easy to check if the control was already created with Snoop. Once you attached it to your application, you can filter the visual tree with the textbox on the top left. If you don't find one control in the tree, it means it was not created yet.
Let me state problem first. I would like to implement wrapper around Canvas (let me call it Page) which would implement selecting rectangle around its UIElements which are actually selected.
For this I implemented ISelect interface like so :
interface ISelect {
Point Center {get; set;} //Center of selecting rectangle
Size Dimensions {get; set;} //Dimensions of selecting rectangle
}
Every object that is put to Page implements ISelect interface.
Page has SelectedElements of type ObservableCollection which holds reference to all currently selected elements.
For every entry in SelectedElements i would like to draw rectangle around it.
I have few ideas how to do this :
Every UIElement can implement on its own this rectangle and show it when selected. This option would require for new objects to implement this every time. So I rather not use it.
In Page I could create rectangles in code-behind in add them to the Page. It isn't MVVM recommended priniciple.
In Page XAML create somehind like ItemsControl and bind it to SelectedElements with specific template. This option seems like the best one to me. Please help me in this direction. Should I somehow use ItemsControl?
Thank you.
I don't have time to dig a complete working solution, so this is mostly a collection of suggestions.
Each element should have view model
public abstract class Element: INotifyPropertyChanged
{
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
}
}
}
public class EllipseElement : Element {}
public class RectangleElement : Element {}
Then there are data templates to visualize elements (I can't give you converter code, but you can replace it with another, look here).
<DataTemplate DataType="{x:Type local:EllipseElement}">
<Border Visibility="{Binding IsSelected, Converter={local:FalseToHiddenConverter}}">
<Ellipse ... />
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type local:RectangleElement}">
<Border Visibility="{Binding IsSelected, Converter={local:FalseToHiddenConverter}}">
<Rectangle ... />
</Border>
</DataTemplate>
Then bind ObservableCollection of elements to canvas (which is tricky, see this answer, where ItemsControl is used to support binding).
Your selection routine has to hit-test elements and set/reset their IsSelected property, which will show border. See here regarding how to draw over-all selection rectangle.
I've previously written several application in WPF, but I'm not sure how I should do this one:
I'm trying to make an "Universal App", designed toward the small screen on a raspberry Pi with win10 Iot.
I would like to create a usercontrol, which displays a value, and which when clicked expands to take up the full screen, allowing me to edit it nicely with some additional buttons (which show in the expanded version of the usercontrol)(e.g, numerical stepped Up/Down, + Ok/Cancel buttons). And when I click on the Ok Button in this expanded usercontrol, it should copy the EditedValue to the realValue (vars).
I'm a little stuck on how to do the part with a different display (different layouts, different components, taking all the place of the windows) and would like some help.
#Jordy van Eijk provided you with a viable solution but since you asked for an example I will provide you with my own implementation. Please note there are plenty of variations and other ways you may do this, and as your question seems to be quite broad I will only fill in the initial design.
My approach uses the MVVM design pattern:
A content presenter will bind to a view model and show the current data template. The data template will relate a view model to the user control. The user control contains your view, bindings to resize your selected item, and triggers to show/hide your extended display.
The Content Presenter (MainWindow.xaml):
<ContentPresenter Content="{Binding CurrentView}"/>
The Data Template (MainWindow.xaml):
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:UserControl1ViewModel}" >
<view:UserControl1/>
</DataTemplate>
</Window.Resources>
The User Control (UserControl1.xaml):
<UserControl.Resources>
<Style x:Key="ExtendedControl" TargetType="Button">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid HorizontalAlignment="Left" Background="Blue" Width="525">
<Button Content="Resize Control" VerticalAlignment="Top" HorizontalAlignment="Left" Width="{Binding Width}" Height="265" Command="{Binding ResizeCommand}" Margin="10,30,0,0"/>
<Button Content="Extended Control" Style="{StaticResource ExtendedControl}" Margin="383,32,25,258"/>
</Grid>
The User Control 1 View Model (UserControl1ViewModel.cs):
public class UserControl1ViewModel : ViewModelBase
{
public ICommand ResizeCommand
{
get
{
if (_resizeCommand == null) _resizeCommand = new RelayCommand(ResizeButton, () => true);
return _resizeCommand;
}
}
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
RaisePropertyChanged("IsVisible");
}
}
public double Width
{
get { return _width; }
set
{
_width = value;
RaisePropertyChanged("Width");
}
}
private RelayCommand _resizeCommand;
private bool _isVisible;
private double _width;
public UserControl1ViewModel()
{
Width = 100.0;
IsVisible = false;
}
private void ResizeButton()
{
Width = Application.Current.MainWindow.ActualWidth * .65;
IsVisible = true;
}
}
Before Click:
After Click:
This outlines the main pieces you will need to create a base application like you are specifying. When the resize control is pressed, its width binding is changed to expand its size to 65% of the application main window and the visibility binding of the extended control button is changed to true. Id include pictures of the resize but my reputation doesn't allow it just yet. I hope you look into MVVM as a future architectural pattern as others have suggested and if you have any further question beyond my simple overview feel free to contact me. Good Luck!
Edit: the classes for the base view model, commands, and binding properties come from the MVVM Light library. You can import this into your project from visual studio using: Tools -> NuGet Package Manager -> Manage NuGet Packages For Solution -> search for "MVVM Light"
Edit 2
For an example related to your comment. We have a parent grid containing an editor view that is always at 70 percent of max window size and a binding for our extended control panel size:
View:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="{Binding Width}"/>
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding ViewManager.EditorControlView}" Grid.Column="0"/>
<ContentPresenter Content="{Binding ViewManager.ExtendedControlView}" Grid.Column="1"/>
</Grid>
Binding:
public string Width
{
get { return _width; }
set
{
_width = value;
RaisePropertyChanged("Width")
}
}
//set our extended control to the other 30 percent of the window size
Width = "3*";
To properly change this width adhering to MVVM standards, we need to use some form of communication between our view models and luckily that is another lesson you can pick up on from here. Just because iv used MVVM Light throughout this example id recommend searching google for "MVVM Light Messenger" as it provides a solid way of doing this communication. Using this you can just raise a change width to hide other window components in the parent view from anywhere in your application. :D
Why not create a Usercontrol with a controltemplate that will change upon some property its template. A ControlTemplate with Triggers
What you are looking is an Accordian Control. These are easy to implement, but it can take a little bit of work to make then look/animate nicely. HERE is an open source project with an example and docs. This duplicate question (by which I mean yours is the duplicate, the other came first) also provides a variety of solutions.
Addressing the second component of your question- how to accommodate a small screen- there are several good design guidelines out there. You could look at the Apple Watch Human Interface Guidelines (that's certainly a small screen), or you could ask for recommendations in the UX/UI Stack Exchange.
I have been developing my first MVVM WPF application, in which I want to draw a graph containing nodes and edges. Currently I am doing all drawing logic in the code behind of my view, iterating over the nodes, creating shapes accordingly and adding them to a canvas.
Because I do not want to keep track of the shapes, and just want them to be drawn based on the data that is given (i.e. the nodes) I have decided to create an ObservableCollection of both the Nodes and Edges, and bind an ItemsControl to these in order to automatically draw the shapes.
For now I am focussing on drawing the nodes, and came up with the following XAML code:
<ItemsControl x:Name="ItemsControlCanvas" ItemsSource="{Binding Path=Nodes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="{Binding ?}" ScaleY="{Binding ?}"/>
</Canvas.LayoutTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Fill="Blue" Width="4" Height="4" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Path=Position.Y}" />
<Setter Property="Canvas.Left" Value="{Binding Path=Position.X}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
In my view, I have two properties ScaleX and ScaleY which I set when the user scrolls on the canvas. In my earlier code behind version, I would create a ScaleTransform with those properties, and apply it to the named canvas using a LayoutTransform.
public partial class GraphOverview : Page
{
public double ScaleX { get; set; }
public double ScaleY { get; set; }
The problem in this version is that I cannot get around to get it working in my new XAML only code. I would like for the ScaleX and ScaleY attributes of the ScaleTransform in the ItemsPanelTemplate to be bound to the properties in my view. Only, normal ElementName binding does not work for some reason. More clearly, the canvas is presumably not aware of the view, I assume because it is a template. Moreover, I cannot call the canvas from the code behind, even when it has a name.
I have tried several solutions, fumbling with RelativeResources and the like, but I think I do not clearly understand why a ItemsPanelTemplate is so disconnected from everything else in the XAML code. Thanks in advance!
Update
Maybe I should clarify that this could be easily resolved by moving the ScaleX and ScaleY properties to the ViewModel, and bind to those properties. But in my opinion such View-specific properties should not reside in the ViewModel.
You are correct when you say view specific information should not be in the view model. But in your case, the positions becomes part of a model because the positions are nothing but data based on which your view should behave. In such cases you can consider them as part of model rather than view. Write a separate Model Class and use it in the observable collection of your ViewModel Class. I hope it clears your doubt.
public class Node
{
public double ScaleX { get; set; }
public double ScaleY { get; set; }
}
Edit
Answering your questions. -
But the Scale properties specify how the Canvas should behave, so does the View not already serve as a 'model' for this? It seems superfluous to me to create a new Model just to set the scale properties of a single Canvas? The view should be merely concerned with drawing data, and since the properties serve as modifications of these drawings, they belong in the View, don't they?
You want to draw the graph based on the data provided. The Node class is going to provide the data for you. It stores the data and not the logic how the canvas should render, the XAML code uses this data and has the logic to show it in the view.
so does the View not already serve as a 'model' for this?
The implementation you have tried has the model in the view, the idea is to separate the model from the view. It might look superfluous initially to create a class just to hold 2 double values. But there are some advantages if you abstract them away from the view.
In the view model you can create a observable collection for this Node class(Model). You cannot access it from Viewmodel if you have the ScaleX an ScaleY in View.
In future you might want to change ScaleX and ScaleY to be dervied into different scale e.g. Logarmic scale/ Different unit. In such cases you will have to change the logic in ViewModel to do so and never have to worry about changing the View. But if you have this Observable collection in the view, you have to change the view for making a change to the data/model.
Lastly - you can write unit tests for whatever you have in the ViewModel but not the View.
Normally ScaleX and ScaleY will be part of the view, but in your case they change and stores data. Hence you need to abstract this ScaleX and ScaleY into a different layer for preserving the MVVM concept.
I'm not sure if you have set the DataContext of your view, it would help to show more of your code behind
internal ViewModel viewModel { get; set; }
public View()
{
DataContext = (viewModel = new ViewModel());
}
Are you binding the ObservableCollection to ItemSource of the nodes? I'm not familiar with drawing, but when I've bound to children, I've never achieved results.
But I've only developed about 4 projects utilizing MVVM.