Add n rectangles to canvas with MVVM in WPF
In a proper MVVM approach you would have a view model with an abstract representation of a list of rectangles, e.g. like this:
public class RectItem
{
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
}
public class ViewModel
{
public ObservableCollection<RectItem> RectItems { get; set; }
}
Then you would have a view that uses an ItemsControl to visualize a collection of such Rect
items. The ItemsControl would have a Canvas as its ItemsPanel
and an appropriate ItemContainerStyle
and ItemTemplate
which each bind to the appropriate view model properties. It might look like this:
<ItemsControl ItemsSource="{Binding RectItems}">
<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>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Black"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
An alternative without Bindings in Style Setters (which don't work in UWP) might look like this:
<ItemsControl ItemsSource="{Binding RectItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Black">
<Rectangle.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Add rectangle to Canvas using MVVM
Cannot explicitly modify Children collection of Panel used as ItemsPanel for ItemsControl. ItemsControl generates child elements for Panel.
This means that you cannot use Canvas.Children.Add
when you use Canvas
as an ItemsPanel
for an ItemsControl
. You should add items on where the ItemsControl.ItemsSource
property is bound to (in your case ListRectangle
).
Add Rectangle and TextBlock to canvas dynamically using MVVM
You are using a Stackpanel
to display the Rectangle
and the TextBlock
... so the Rectangle
and the TextBlock
are stacked!
Use a Grid
or a Canvas
instead. Then you will be able to manage the position of your controls relatively to their container.
<Grid>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Transparent" Stroke="Red" StrokeThickness="3" />
<TextBlock Text="Sample Text" Width="100" Height="100"/>
</Grid>
<Canvas>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Transparent" Stroke="Red" StrokeThickness="3" />
<TextBlock Text="Sample Text" Width="100" Height="100" Canvas.Left="50" Canvas.Top="50"/>
</Canvas>
WPF - MVVM: how to draw a movable Rectangle with mouse
MVVM pattern is just a desired pattern to separate visual design from applicaton data structure, so when you say "MVVM pattern doesn't allow to put event handler (for mouse interaction) in view code-behind" maybe that is being too drastic. Try to stick to it for better designs but don't get too crazy at the beginning. Have a look at this page, it might be of interest.
WPF creates automatically the event handlers for you in the code behind, so that is the place for them. What you may want to do is to, instead of modifying your data directly inside your event handler, there will be a ViewModel whose methods will help you modify your data.
You can use the PreviewMouse(Up,Down,Move) events associated to the Canvas. It might be easier than trying to click on your shape, specially when several shapes may overlap. Obviously, if you have many shapes, there must be a way to know which shapes is going to be edited (manual selection through combobox, finding the closes shape to mouse click, etc)
From your design, one aspect that may give you some trouble is the fact that your viewModel manages 1 shape. It isn't wrong, but you need another VM layer that manages a collection of shapes. It can be done as another class or as a bunch of static methods in your MyRectViewModel
:
void MyCanvas_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
MyShapeCollectionViewModel.StoreMouseDown(e);
}
void MyCanvas_PreviewMouseMove(object sender, MouseButtonEventArgs e)
{
MyShapeCollectionViewModel.ModifyShapes(e);
}
void MyCanvas_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
MyShapeCollectionViewModel.ModifyShapes(e);
}
Where ModifyShapes()
will calculate the mouse drag, find the shape to be edited and invoke its edition methods. Then, once the shape data is modified, the corresponding event will be fired to update the associated View
How to draw dynamic rectangles hierarchical? (MVVM-WPF)
You could make a flat collection of all SketchRectangleViewModel
objects in your view model:
How to flatten tree via LINQ?
...and bind the collection of all SketchRectangleViewModel
objects to an ItemsControl
:
<ItemsControl ItemsSource="{Binding YourCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="100" Height="100" />
</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>
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Green" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note that you can only bind to public properties of the SketchRectangleViewModel
class, so you need to turn your fields into properties:
public double X { get; set; }
WPF Canvas, how to add children dynamically with MVVM code behind
ItemsControl
is your friend:
<Grid>
<Image Source="..."/>
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="1" Width="{Binding Width}" Height="{Binding Height}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The above assumes your VM exposes a collection of points via a Points
property, and that each point VM has X
, Y
, Width
, and Height
properties.
How do I display an ObservableCollection of Rectangles in a Canvas using ReactiveUI?
As per my comment above I added a data context to MainWindow.xaml header so I can bind to the collection in my ViewModel:
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
and did a XAML binding to my Shapes ObservableCollection
<ItemsControl ItemsSource="{Binding Shapes}" ... >
After talking to Ani Betts on the reactiveui Slack channel, I found that this is the best way to do it. You can make it work by giving a data template to the rxui binding; it creates a lot of overhead and since canvas in non-recycling, with a lot of canvas items, it can be expensive memory-wise.
Related Topics
What Are Regular Expression Balancing Groups
Add N Rectangles to Canvas With Mvvm in Wpf
Getting All Types That Implement an Interface
How to Build a Query String For a Url in C#
A Field Initializer Cannot Reference the Nonstatic Field, Method, or Property
How to Generate Random Alphanumeric Strings
Using Setwindowpos With Multiple Monitors
How to Check If a Number Is a Power of 2
Create an Instance of a Class from a String
Could Not Load File or Assembly or One of Its Dependencies
Does Json.Net Cache Types' Serialization Information
Best Way to Get Application Folder Path
Why Does Property Set Throw Stackoverflow Exception
Mvc5 Razor Html.Dropdownlistfor Set Selected When Value Is in Array