0

I'm currently using the MVVM pattern in one of my apps, to be more specific I'm using the MVVMLight framework. In one of the pages, I will have a screen where the user can input the width and length to draw rectangles, there is not much code logic so, I was thinking to put all of my code in the code-behind since most of what will be happening in this screen is UI related.

Does that make sense to use the code-behind in this case? If not, how would you structure the code to use the MVVM pattern, what would you put in the ViewModel in this case and what would you put in your code behind?

Here is the code without using MVVM.

XAML:

<Window x:Class="DrawingRectangles.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DrawingRectangles"
        mc:Ignorable="d"
        Title="MainWindow" Height="531.798" Width="782.115">
    <Grid Name="MyGrid" Width="480" Height="240" Margin="27,23,267,174">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="59*"/>
            <ColumnDefinition Width="421*"/>
        </Grid.ColumnDefinitions>
        <Canvas Name="MyCanvas" Background="#FFF1F0F0" Margin="10" Grid.ColumnSpan="2"/>

        <Grid Margin="10,235,10,-92" Background="WhiteSmoke" Grid.ColumnSpan="2">
            <Button x:Name="drawButton" Content="Draw"  Click="drawButton_Click"/>
            <Button x:Name="resetButton" Content="Reset" Click="resetButton_Click"/>
            <TextBox x:Name="textBoxPartWidth"/>
            <TextBox x:Name="textBoxPartLength"/>

        </Grid>
    </Grid>
</Window>

CODE-BEHIND:

namespace DrawingRectangles
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            InitializeComponent();

        }

        private void drawButton_Click(object sender, RoutedEventArgs e)
        {
            clearScreen();

            int xParts = 10;
            int yParts = 10;

            for (int i = 0; i < xParts; i++) {

                for (int j = 0; j < yParts; j++) {
                    // Create a rectangle.
                    Rectangle myRectangle = new Rectangle();
                    myRectangle.Width = Convert.ToDouble(textBoxPartLength.Text);
                    myRectangle.Height = Convert.ToDouble(textBoxPartWidth.Text);
                    myRectangle.Margin = new Thickness((Convert.ToInt32(myRectangle.Width) + 1) * i, (Convert.ToInt32(myRectangle.Height) + 1) * j, 0, 0);
                    myRectangle.Fill = new SolidColorBrush(Color.FromArgb(170, 51, 51, 255));
                    MyCanvas.Children.Add(myRectangle);
                }
            }
        }

        private void resetButton_Click(object sender, RoutedEventArgs e)
        {
            MyCanvas.Children.Clear();
        }

        private void clearScreen()
        {
            MyCanvas.Children.Clear();
        }
    }
}

UI

enter image description here

EDIT: Second Image (reference only): enter image description here

fs_tigre
  • 10,650
  • 13
  • 73
  • 146
  • 1
    Generally speaking in MVVM button click = command and text fields = bindings to properties in view model. Your aim to have zero code behind and it seems it's possible for your task. – Sinatr Jul 02 '18 at 13:40
  • 1
    You can start [here](https://stackoverflow.com/q/22324359/1997232) to see how to use canvas in mvvm. – Sinatr Jul 02 '18 at 13:44
  • 1
    Also take a look at this: https://stackoverflow.com/a/40190793/1136211 – Clemens Jul 02 '18 at 13:46
  • Thank you both for the good info. – fs_tigre Jul 02 '18 at 13:48

1 Answers1

1

The Button in the view should be bound to an ICommand property of the view model. The command will be executed when you click on the Button. Please refer to this blog post for information about how to handle events in an MVVM application. In MvvmLight, the ICommand implementation is called RelayCommand.

You should also bind the Text properties of the TextBoxes to two source properties of the view model and the Canvas element in your view should be replaced with an ItemsControl that you bind to a collection of objects that are defined in the view model.

Please refer to the following sample code.

Model:

public class Model
{
    public int Width { get; set; }
    public int Height { get; set; }
    public Thickness Margin { get; set; }
    public Brush Fill { get; set; }
}

View:

<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas Background="#FFF1F0F0" Margin="10" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Rectangle Width="{Binding Width}" 
                       Height="{Binding Height}" 
                       Margin="{Binding Margin}"
                       Fill="{Binding Fill}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

<Button Content="Draw" Command="{Binding DrawCommand}" />
<Button Content="Reset" Command="{Binding ResetCommand}" />
<TextBox Text="{Binding Width}"/>
<TextBox Text="{Binding Height}"/>

View Model:

public class ViewModel
{
    public ViewModel()
    {
        DrawCommand = new RelayCommand(Draw);
        ResetCommand = new RelayCommand(Clear);
    }

    public ObservableCollection<Model> Items { get; } = new ObservableCollection<Model>();

    public RelayCommand DrawCommand { get; }
    public RelayCommand ResetCommand { get; }

    public int Width { get; set; }
    public int Height { get; set; }

    private void Draw()
    {
        Clear();

        int xParts = 10;
        int yParts = 10;
        for (int i = 0; i < xParts; i++)
        {
            for (int j = 0; j < yParts; j++)
            {
                Model model = new Model();
                model.Width = Width;
                model.Height = Height;
                model.Margin = new Thickness((model.Width + 1) * i, (model.Height + 1) * j, 0, 0);
                model.Fill = new SolidColorBrush(Color.FromArgb(170, 51, 51, 255));
                Items.Add(model);
            }
        }
    }

    private void Clear()
    {
        Items.Clear();
    }
}

In this example all application logic has been moved to the view model where it belongs. There is no logic left in the code-behind class of the view.

Also note that the view models creates instances of Model objects rather than creating Rectangle elements. It's generally considered be a bad practice to reference UI elements in a view model class. The Rectangle elements are created by the ItemsControl. See the ItemTemplate in the view.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • 1
    you are creating multiple `new SolidColorBrush(Color.FromArgb(170, 51, 51, 255))` in a view model when SolidColorBrush is a UI element. it is a bad practice – ASh Jul 02 '18 at 15:28
  • 1
    have you linked your own blog? if yes, then it will be a good idea to disclose that. – ASh Jul 02 '18 at 15:29
  • Code correction, `Items.Add(myRectangle);` should be `Items.Add(model);`. – fs_tigre Jul 04 '18 at 14:47
  • 1
    @fs_tigre: Fixed. – mm8 Jul 04 '18 at 14:48
  • @mm8 - Quick question: This is more of a WPF question - what would be the best way to draw a second set of rectangles right after the first one. Something like the second image I uploaded (just now), note that the second set of rectangles are oriented differently than the first one. Do I need another `ObservableCollection` and another `ItemsControl` and just off-set the second `itemsControl` accordingly? Or can a second grid be added to the same `ItemsControl`? I know this is not related to the original question so, please feel free to ignore it. – fs_tigre Jul 04 '18 at 15:38
  • 1
    @fs_tigre: Please ask a new question if you have another issue. – mm8 Jul 05 '18 at 14:00