Add N Rectangles to Canvas With Mvvm in Wpf

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



Leave a reply



Submit