Rob King
Rob King

Reputation: 1201

Updating a MVVM View when model property is set to new instance

I have a ViewModel and a View in a WPF application. On the screen there are a selection of inputs (date picker, text box and combobox).

The inputs are bound to the NewItem property of the ViewModel, the DataGrid is bound to the WorkLog collection property.

When the user clicks on the Add button I want the NewItem to be added to the WorkLog collection, and the NewItem property reset in order to allow the user to add more items. The problem is that when I add the item, if I reinstantiate NewItem then the controls are still populated but in the background the VM values are all defaults (or nulls) so it doesn't work.

How can I reset the NewItem property and update the UI to reflect this? I tried INotifyPropertyChanged to no avail (as I am setting to new instance rather than changing values).

I have trimmed the code for brevity

Model

public class WorkLogItem : INotifyPropertyChanged
{
    public WorkLogItem()
    {
        this.Datestamp = DateTime.Today;
        this.Staff = new Lookup();
        this.WorkItem = new Lookup();
    }

    #region ID

    private Int32 _ID;

    public Int32 ID
    {
        get { return this._ID; }
        set
        {
            this._ID = value;
            FirePropertyChanged("ID");
        }
    }

    #endregion

    #region Datestamp

    private DateTime? _Datestamp;

    public DateTime? Datestamp
    {
        get { return this._Datestamp; }
        set
        {
            this._Datestamp = value;
            FirePropertyChanged("Datestamp");
        }
    }

    #endregion

    #region Staff

    private Model.Lookup _Staff;

    public Model.Lookup Staff
    {
        get { return this._Staff; }
        set
        {
            this._Staff = value;
            FirePropertyChanged("Staff");
        }
    }

    #endregion

    #region WorkItem

    private Model.Lookup _WorkItem;

    public Model.Lookup WorkItem
    {
        get { return this._WorkItem; }
        set
        {
            this._WorkItem = value;
            FirePropertyChanged("WorkItem");
        }
    }

    #endregion

    #region Hours

    private Decimal _Hours;

    public Decimal Hours
    {
        get { return this._Hours; }
        set
        {
            this._Hours = value;
            FirePropertyChanged("Hours");
        }
    }

    #endregion

    public event PropertyChangedEventHandler PropertyChanged;

    // Create the OnPropertyChanged method to raise the event
    protected void FirePropertyChanged(String name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
            handler(this, new PropertyChangedEventArgs(name));
    }
}

View Model

public Model.WorkLogItem NewItem { get; set; }

public ObservableCollection<Model.WorkLogItem> WorkLog { get; set; }

View

<Label Content="Date " />
<DatePicker SelectedDate="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.NewItem.Datestamp, NotifyOnSourceUpdated=True}" />

<Label Content="Work Item " />
<ComboBox Grid.Column="1" Grid.Row="2" DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.WorkItems}" ItemsSource="{Binding}" DisplayMemberPath="Value" SelectedValuePath="ID" SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.WorkLogItem.Type, NotifyOnSourceUpdated=True}" IsSynchronizedWithCurrentItem="True" />

<Label Grid.Row="3" Content="Hours " />
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.NewItem.Hours, NotifyOnSourceUpdated=True}" />

C#

In Window_Loaded:

this.DataContext = this.VM;

In Add_Click

this.VM.WorkLog.Add(this.VM.NewItem);

this.VM.NewItem = new Model.WorkLogItem();

Upvotes: 3

Views: 5028

Answers (2)

Ed Chapel
Ed Chapel

Reputation: 6932

Your ViewModel must also implement INotifyPropertyChanged

public class ViewModel : INotifyPropertyChanged
{
    private Model.WorkLogItem _newItem;

    public ViewModel()
    {
        NewItem = new Model.WorkLogItem();
        WorkLog  = new ObservableCollection<Model.WorkLogItem>();
    }

    public Model.WorkLogItem NewItem
    { 
        get { return _newItem; }
        set
        {
            _newItem = value;
            FirePropertyChanged("NewItem");
        }
    }

    public ObservableCollection<Model.WorkLogItem> WorkLog { get; set; }

    // INotifyPropertyChanged implementation here...
}

When binding to your ComboBox, be sure to use Mode=TwoWay:

<ComboBox ... SelectedItem="{Binding ... Mode=TwoWay}" />

Upvotes: 3

Sheridan
Sheridan

Reputation: 69989

The way that I do this is to set up an empty constructor in my data object that sets all of its properties to their default values. Then when I want to 'reset' a view to all empty fields, I just set my data bound property to a new item:

NewItem = new YourDataType();

This updates all of the data bound control properties as you would expect. Please note that my data type classes all implement the INotifyPropertyChanged interface and that this method of clearing the UI controls will only work if they do implement it.

Upvotes: 0

Related Questions