0

I'm building my first WPF program using a RelayCommand and button click to pass a "User" object as a parameter to edit or create a user. Existing users pass to the onClick fine, but information entered on the blank row for a new user always has NULL properties. A test to check if the object is NULL always indicates the object is not, however. How can the object be not NULL but its contents entered in the window are? Can anyone spot something wrong with the binding on the new object? Many thanks in advance!

UserViewModel class:

public class UserViewModel : INotifyPropertyChanged
{
    private string _FirstName;
    public string FirstName
    {
        get { return _FirstName; }
        set { _FirstName = value; NotifyPropertyChanged("FirstName"); }
    }

    private string _LastName;
    public string LastName
    {
        get { return _LastName; }
        set { _LastName = value; NotifyPropertyChanged("LastName"); }
    }

    private string _EMail;
    public string EMail
    {
        get { return _EMail; }
        set { _EMail = value; NotifyPropertyChanged("EMail"); }
    }

    private int _UserID;
    public int UserID
    {
        get { return _UserID; }
        set { _UserID = value; NotifyPropertyChanged("UserID"); }
    }

    private string _Position;
    public string Position
    {
        get { return _Position; }
        set { _Position = value; NotifyPropertyChanged("Position"); }
    }

    private DateTime? _EndDate;
    public DateTime? EndDate
    {
        get { return _EndDate; }
        set { _EndDate = value; NotifyPropertyChanged("EndDate"); }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

UsersViewModel class:

public partial class UsersViewModel : INotifyPropertyChanged
{
    public RelayCommand<object> editButton_Click_Command { get; set; }

    public UsersViewModel()
    {
        editButton_Click_Command = new RelayCommand<object>(OneditButton_Click_Command);
    }

    private ObservableCollection<UserViewModel> _Users;
    public ObservableCollection<UserViewModel> Users
    {
        get { return _Users; }
        set { _Users = value; NotifyPropertyChanged("Users"); }
    }

    private void OneditButton_Click_Command(object obj)
    {
        var associatedViewModel = obj as UserViewModel;
        string lastName = associatedViewModel.LastName; //Always NULL on new row entered in Window!!!
        if (associatedViewModel == null)
        {
            //Never reached
        }
        if (obj == null)
        {
            //Never reached
        } 
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

The RelayCommand:

public class RelayCommand<T> : ICommand
{
    #region Fields

    private readonly Action<T> _execute = null;
    private readonly Predicate<T> _canExecute = null;

    #endregion

    #region Constructors

    /// <summary>
    /// Creates a new command that can always execute.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    public RelayCommand(Action<T> execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Creates a new command with conditional execution.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    public RelayCommand(Action<T> execute, Predicate<T> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    #endregion

    #region ICommand Members

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute((T)parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add
        {
            if (_canExecute != null)
                CommandManager.RequerySuggested += value;
        }
        remove
        {
            if (_canExecute != null)
                CommandManager.RequerySuggested -= value;
        }
    }

    public void Execute(object parameter)
    {
        try
        {
            _execute((T)parameter);
        }
        catch
        {
            System.Windows.MessageBox.Show("Please enter values for the new entry.");
        }
    }

    #endregion
}

Opening the UsersWindow from the MainWindow:

UsersViewModel Usersvm = new UsersViewModel();
Usersvm.Users = new ObservableCollection<UserViewModel>();
Usersvm.Users.Add(new UserViewModel() { FirstName = "John", LastName = "Doe", EMail = "JohnDoe@yahoo.com", EndDate = new DateTime(2016, 2, 1), Position = "Developer", UserID = 1 });
new UsersWindow(Usersvm).Show();

UsersWindow.xaml.cs:

public partial class UsersWindow : Window
{
    public UsersWindow(UsersViewModel uvm)
    {
        InitializeComponent();
        DataContext = uvm;
    }
}

Finally the XAML:

<Window x:Name="Base_V"
    x:Class="DbEntities.UsersWindow"
    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:DbEntities"
    xmlns:ViewModels="clr-namespace:DbEntities"
    xmlns:staticData="clr-namespace:DbEntities"
    mc:Ignorable="d"
    Title="UsersWindow" Height="Auto" Width="900">
    <Window.Resources>
        <staticData:PositionsList x:Key="PositionsList" />
    </Window.Resources>
    <Window.DataContext>
        <ViewModels:UsersViewModel/>
    </Window.DataContext>
    <Grid>
        <DataGrid Name="DataGrid1" ItemsSource="{Binding Users}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
              ColumnWidth="*" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button Command="{Binding DataContext.editButton_Click_Command, ElementName=Base_V}" CommandParameter="{Binding}" >Edit</Button>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Header="User ID" Binding="{Binding UserID}" IsReadOnly="True" />
                <DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" />
                <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" />
                <DataGridTextColumn Header="E-Mail" Binding="{Binding EMail}" />
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.HeaderTemplate>
                        <DataTemplate>
                            <Label Content="Position" />
                        </DataTemplate>
                    </DataGridTemplateColumn.HeaderTemplate>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{StaticResource PositionsList}" SelectedItem="{Binding Position}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Header="End Date" Binding="{Binding EndDate, StringFormat={}{0:MM/dd/yyyy}}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
jle
  • 269
  • 8
  • 25

2 Answers2

0

Reverse the sequence that you are doing things. First check to determine if obj is null. If (and only if) it is not null then convert (cast) it to an associatedViewModel. Then check if associatedViewModel is null. Then if (and only if) associatedViewModel is not null can you use associatedViewModel.LastName. Issue an error of course if either are null.

Assuming there is something null that should not be, reduce the code to the minimum necessary to recreate the problem.

Sam Hobbs
  • 2,594
  • 3
  • 21
  • 32
0

The answer here was that the DataGrid was always sending the original values to the button click. Directing the user to a new page where the single object can be edited via binded TextBoxes solved the problem.

jle
  • 269
  • 8
  • 25
  • How did you discover that? I hope you at least made the modifications I suggested and will continue to do that kind of thing. I will assume that you discovered the problem **after** making the modification I indicated. – Sam Hobbs Apr 14 '16 at 18:12
  • This problem stemmed from an issue I previously had here http://stackoverflow.com/questions/36555191/wpf-listbox-data-binding/36558782#36558782. I inquired back with one of the posters on that thread and we solved it. Thank you for your response and interest! I made the changes you suggested, which make total sense. – jle Apr 14 '16 at 19:43