0

I'm new to C# and XAML, and I'm trying to learn how to work with data context and bindings in a MVVM design pattern. I've written a very simple program. The program allows the user to create a new person and displays the person's details in the MainWindow. To create a new person, the user selects Add new person from the MainWindow's menu. A window (PersonView) pops up where the user can enter the name and age for the person. When the user clicks Ok on the PersonView window an event fires that copies the Person's data over to a property in the MainWindow. Once data is copied over the PersonView window closes.

The problem is that the Person's details do not update in the main window. Using debugger, I can see that my PropertyChanged event triggers, and that the eventhandler in the MainWindow runs. I've set the DataContext (in c#) for the MainWindow equal to the Property in the MainWindow that contains the data I want to display. I've set the bindings for the Name and Age TextBlocks in XAML in MainWindow.

I've read this and this, but I still can't get it to work. What am I missing?

Code:

Person.cs:

{
    public class Person
    {
        #region Constructors
        public Person()
        {
            Name = "";
            Age = 0;
        }
        public Person(string name, int age)
        {
            Name = name;
            Age = age;
        }

        #endregion

        #region Properties
        public string Name { get; set; }
        public int Age { get; set; }

        #endregion

        #region Methods
        #endregion
    }
}

PersonViewModel.cs

using System.ComponentModel;

namespace MVVM_Basics.ViewModel
{
    public class PersonViewModel : INotifyPropertyChanged
    {
        #region Constructors
        public PersonViewModel()
        {
            _person.Name = "";
            _person.Age = 0;
        }
        public PersonViewModel(string name, int age)
        {
            _person.Name = name;
            _person.Age = age;
        }

        #endregion

        #region Properties

        Person _person = new Person();
        public Person  Person 
        { get { return _person; }
            set
            {
                _person = value;
                OnPropertyChanged(new PropertyChangedEventArgs("Person"));
            }
        }

        #region Events and EventHandlers
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler handler = PropertyChanged; //Associate event handler with event
            handler?.Invoke(this, e); //invoke the delegate
        }

        #endregion


        #endregion

        #region Methods


        #endregion
    }
}

PersonView.xaml.cs

using System;
using System.Windows;

namespace MVVM_Basics.View
{
    /// <summary>
    /// Interaction logic for PersonView.xaml
    /// </summary>
    public partial class PersonView : Window
    {
        #region Constructors
        public PersonView()
        {
            InitializeComponent();
        }
        #endregion

        #region Properties
        public PersonViewModel pvm { get; set; }
        #endregion

        #region Events and EventHandlers
        public event EventHandler NewPersonCreated;

        protected void OnNewPersonCreated(EventArgs e)
        {
            EventHandler handler = NewPersonCreated; //Link handler to event
            handler?.Invoke(this, e); //Invoke handler
        }

        private void OkButton_Click(object sender, RoutedEventArgs e)
        {
            pvm = new PersonViewModel(this.NameTextBox.Text, Convert.ToInt32(this.AgeTextBox.Text));
            OnNewPersonCreated(new EventArgs()); //Trigger the event
            this.Close();
        }

        private void CancelEventHandler(object sender, RoutedEventArgs e)
        {
            //When the cancel button is clicked...
            this.Close();
        }
        #endregion


        #region Methods


        #endregion
    }
}

PersonView.xaml

        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:MVVM_Basics.View"
        mc:Ignorable="d"
        Title="PersonView" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Text="Name" Margin="30, 30"/>
        <TextBox x:Name="NameTextBox" Grid.Row="0" Grid.Column="1" Margin="30, 30"/>

        <TextBlock Grid.Row="1" Grid.Column="0" Text="Age" Margin="30, 30"/>
        <TextBox x:Name="AgeTextBox" Grid.Row="1" Grid.Column="1" Margin="30, 30"/>

        <Button x:Name="OkButton" Content="Ok" Grid.Row="2" Grid.Column="0" Click="OkButton_Click" Margin="30, 30"/>
        <Button x:Name="CancelButton" Content="Cancel" Grid.Row="2" Grid.Column="1" Click="CancelEventHandler" Margin="30, 30"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using MVVM_Basics.ViewModel;
using System;
using System.Windows;

namespace MVVM_Basics
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Constructors
        public MainWindow()
        {
            InitializeComponent();
            pvm.Person.Name = "Name from constructor";
            this.DataContext = pvm; //Set Window's data context
        }
        #endregion

        #region Properties
        public PersonViewModel pvm { get; set; } = new PersonViewModel();

        #endregion

        #region Events and EventHandlers
        private void AddNewPerson(object sender, RoutedEventArgs e)
        {
            //When user clicks on File>New person...
            PersonView pv = new PersonView();
            pv.NewPersonCreated += Pv_NewPersonCreated; //Subscribe event handler to event
            pv.Show();
        }

        private void Pv_NewPersonCreated(object sender, EventArgs e)
        {
            //This delegate is called when PersonView creates a new PersonViewModel object
            PersonView pv = (PersonView)sender;
            pvm = pv.pvm; //Copy the PersonViewModel object from PersonView to MainWindow
            //NameTextBlock.Text = pvm.Person.Name; //This works, but I want to use bindings, not this hack
            pv.NewPersonCreated -= Pv_NewPersonCreated; //Unsubscribe event handler from event, going to close PersonView shortly
        }
        #endregion

        #region Methods
        #endregion
    }
}

MainWindow.xaml

        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:MVVM_Basics"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="400" >

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="25"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Menu Grid.Row="0">
            <MenuItem Header="_File">
                <MenuItem Header="Add _new person" Click="AddNewPerson"/>
            </MenuItem>
        </Menu>
        
        <Grid Grid.Row="1" Grid.Column="1" >
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto"/>
                <ColumnDefinition Width="auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            
            <!--Subgrid Row 0-->
            <TextBlock Text="Name:" Grid.Row="0" Grid.Column="0"/>
            <TextBlock x:Name="NameTextBlock" Text="{Binding Person.Name}"  Grid.Row="0" Grid.Column="1" MinWidth="50"  />

            <!--Subgrid Row 1-->
            <TextBlock Text="Age:" Grid.Row="1" Grid.Column="0"/>
            <TextBlock x:Name="AgeTextBlock" Text="{Binding Person.Age}" Grid.Row="1" Grid.Column="1"/>
        </Grid>
    </Grid>
</Window>
  • `pvm = pv.pvm;` neither sets the DataContext, not does it fire any kind of change notification. Better set `pvm.Person = pv.pvm.Person;`, which would fire the PropertyChanged event for the Person property and thus update the Bindings. – Clemens Jun 29 '20 at 18:31
  • Thank you very much, this solved my issue. – Donn Pienaar Jun 30 '20 at 05:36

0 Answers0