0

I'm learning MVVM right now. Since I've seen many tutorials or projects use only View and ViewModel, I a little confused. This is my code.

MODEL :

public class StudentModel : PropertyChangedBase
{
    private String _firstName;
    public String FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            NotifyOfPropertyChange(() => FirstName);
        }
    }

    private Double _gradePoint;
    public Double GradePoint
    {
        get { return _gradePoint; }
        set
        {
            _gradePoint = value;
            NotifyOfPropertyChange(() => GradePoint);
        }
    }
}

VIEW :

<UserControl x:Class="MVVMLearningWithCaliburnMicro.Views.StudentView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:cal="http://www.caliburnproject.org">
    <Grid Width="525" Height="300" Background="Lavender">
        <DockPanel>
            <TextBlock HorizontalAlignment="Center" Text="Student Data"
                       DockPanel.Dock="Top" FontSize="20" />
            <StackPanel Orientation="Vertical" HorizontalAlignment="Center"
                        VerticalAlignment="Stretch"
                        Margin="0,8" DockPanel.Dock="Top">
                <StackPanel Orientation="Horizontal" Margin="0,5">
                    <TextBlock Text="Name" FontSize="15" Margin="5,0" />
                    <TextBox Name="txtName" Text="{Binding Path=Student.FirstName}" Width="250" />
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="0,5">
                    <TextBlock Text="Grade" FontSize="15" Margin="5,0" />
                    <TextBox Name="txtGrade" Text="{Binding Path=Student.GradePoint}" Width="250" />
                </StackPanel>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="0,5" HorizontalAlignment="Center"
                        VerticalAlignment="Bottom"
                        DockPanel.Dock="Bottom">
                <Button Name="btnSave" Width="100" Height="40"
                        cal:Message.Attach="SaveStudent">
                    <TextBlock Text="Save" FontSize="15" />
                </Button>
            </StackPanel>
        </DockPanel>
    </Grid>
</UserControl>

VIEWMODEL :

public class StudentViewModel
{
    public StudentModel Student { get; set; }

    public void SaveStudent()
    {
        MessageBox.Show(String.Format("Saved: {0} - ({1})", Student.FirstName, Student.GradePoint));
    }

    public StudentViewModel()
    {
        Student = new StudentModel { FirstName = "Tom Johnson", GradePoint = 3.7 };
    }

    private Boolean CanSaveStudent()
    {
        return Student.GradePoint >= 0.0 || Student.GradePoint <= 4.0;;
    }
}

Q :
1. How do I put my guard property since NotifyOfPropertyChange()'s are in Model ?
2. (Silly question) Is my MVVM pattern has pointed to the right way ?

asakura89
  • 531
  • 1
  • 12
  • 20
  • btw you probably dont even need a view model in this case.. Usually View should bind to ViewModel properties which fires NotifyPropertyChanged and propagates changes to model. Not implying that you are doing it wrong just my few cents. – Sandeep Singh Rawat Jun 09 '12 at 20:32
  • Guard property is property that used for guarding an event. It's like validation when event is fired up. – asakura89 Jun 10 '12 at 03:27

4 Answers4

2

One solutions is inherit the viewmodel from PropertyChangedBase and subscribe to property changes of the StudentModel. Then convert the guard method to a property, like this:

public class StudentViewModel: PropertyChangedBase
{
    public StudentModel Student { get; set; }

    public void SaveStudent()
    {
        MessageBox.Show(String.Format("Saved: {0} - ({1})", Student.FirstName, Student.GradePoint));
    }

    public StudentViewModel()
    {
        Student = new StudentModel { FirstName = "Tom Johnson", GradePoint = 3.7 };
        Student.PropertyChanged += delegate { NotifyOfPropertyChanged( () => CanSaveStudent)};
    }

    public Boolean CanSaveStudent
    {
        get 
        {
            return Student.GradePoint >= 0.0 || Student.GradePoint <= 4.0;
        }
    }
}

Hope it works.

Jone Polvora
  • 2,184
  • 1
  • 22
  • 33
  • But in this case, I suggest you merge the viewmodel and the model. – Jone Polvora Jun 09 '12 at 21:21
  • Then my pattern would be VVM. Is there a cleaner way to do if I want separate VM and M ? – asakura89 Jun 10 '12 at 03:04
  • In MVVM there's no design limitation on having childviewmodels inside a viewmodel. In my answer I showed an inner instance of a PropertyChangedBase notifying the parent viewModel. – Jone Polvora Jun 10 '12 at 10:03
  • 1
    VIEWMODEL is a model for the VIEW. Think in data needed to feed visual controls/user inputs. The MODEL can be a DTO, or a domain model, or any data structure that the viewmodel will hook into for getting/setting properties and logic that will reflected in the view. The viewmodel can call services that get/post data. – Jone Polvora Jun 10 '12 at 10:19
  • Thanks. I always imagining Model is like my answer below. Cheers :) – asakura89 Jun 12 '12 at 08:09
1

Notify event should be present in view model, cause it communicates changes from the model and push them to UI and vice versa. This is according MVVM design guidelines.

In your specific case, you can or leave it as is, but remove unnecessary view model, or move notifiers in view model

Tigran
  • 61,654
  • 8
  • 86
  • 123
  • Model is something that *reflects* your data base or is something that *is* data by itself. – Tigran Jun 10 '12 at 08:05
0

Your notify changes should be in the model since that is what you are bound too. Basically your view model should load the list of objects and provide a bound collection to your UI for most things. The implementation you have above appears to be a detailed panel with a single object (such as edit mode).

To sum it up.

ViewModel - should implement INotifyPropertyChangeed interface Model - should implement INotifyPropertyChangeed interface

To enable / disable commands / buttons you should be using command binding. I have a tutorial for WPF and Silverlight that you can reference.

http://tsells.wordpress.com/2010/06/23/command-binding-with-wpf-and-silverlight-net-4-0-with-m-v-vm/

I have also put some tutorials up over the past couple of years surrounding this very thing. Check them out if you like....

http://tsells.wordpress.com/2011/02/08/using-reflection-with-wpf-and-the-inotifypropertychanged-interface/

http://tsells.wordpress.com/2010/06/02/wpf-model-view-viewmodel-m-v-vm-example/

tsells
  • 2,751
  • 1
  • 18
  • 20
0

Ok, I have a conclusion in my code
Here're my inspirations

@tsells -- thanks for 3rd link

@john polvora -- thanks for pointing out the property
IMPORTANT : It should be public property

Guard Clause Not Firing
Error on guard clause with Caliburn.Micro


And here're my codes which I think are way more cleaner

MODEL :

public class StudentModel
{
    public String FirstName { get; set; }
    public Double GradePoint { get; set; }
}

VIEW :

<UserControl x:Class="MVVMWithCMTwo.Views.StudentView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:cal="http://www.caliburnproject.org">
    <Grid Width="525" Height="300" Background="Lavender">
        <DockPanel>
            <TextBlock HorizontalAlignment="Center" Text="Student Data"
                       DockPanel.Dock="Top" FontSize="20" />
            <StackPanel Orientation="Vertical" HorizontalAlignment="Center"
                        VerticalAlignment="Stretch"
                        Margin="0,8" DockPanel.Dock="Top">
                <StackPanel Orientation="Horizontal" Margin="0,5">
                    <TextBlock Text="Name" FontSize="15" Margin="5,0" />
                    <TextBox Name="txtName" Text="{Binding Path=StudentName}" Width="250" />
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="0,5">
                    <TextBlock Text="Grade" FontSize="15" Margin="5,0" />
                    <TextBox Name="txtGrade" Text="{Binding Path=StudentGrade}" Width="250" />
                </StackPanel>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="0,5" HorizontalAlignment="Center"
                        VerticalAlignment="Bottom"
                        DockPanel.Dock="Bottom">
                <Button Name="btnSave" Width="100" Height="40"
                        cal:Message.Attach="SaveStudent">
                    <TextBlock Text="Save" FontSize="15" />
                </Button>
            </StackPanel>
        </DockPanel>
    </Grid>
</UserControl>

VIEWMODEL :

public class StudentViewModel : PropertyChangedBase
{
    public StudentModel Student { get; set; }

    public String StudentName
    {
        get { return Student.FirstName; }
        set
        {
            Student.FirstName = value;
            NotifyOfPropertyChange(() => Student.FirstName);
        }
    }

    public Double StudentGrade
    {
        get { return Student.GradePoint; }
        set
        {
            Student.GradePoint = value;
            NotifyOfPropertyChange(() => Student.GradePoint);
            NotifyOfPropertyChange(() => CanSaveStudent);
        }
    }

    public void SaveStudent()
    {
        MessageBox.Show(String.Format("Saved: {0} - ({1})", Student.FirstName, Student.GradePoint));
    }

    public StudentViewModel()
    {
        Student = new StudentModel { FirstName = "Tom Johnson", GradePoint = 3.7 };
    }

    public Boolean CanSaveStudent
    {
        get { return Student.GradePoint >= 0.0 && Student.GradePoint <= 4.0; }
    }
}
Community
  • 1
  • 1
asakura89
  • 531
  • 1
  • 12
  • 20
  • 1
    Yes, you did what I recommended (merged your StudentModel inside the StudentViewModel). You don't need a public property for StudentModel anymore, it can be private. And in the View you don't need declare explcit Binding {} ... just change the names of controls matching the public properties of viewmodel and Caliburn.Micro will create the bindings. – Jone Polvora Jun 10 '12 at 10:06
  • Yup. I know the convention. Just love the old approach – asakura89 Jun 12 '12 at 09:29