Click here to Skip to main content
14,977,701 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hello!

So, I have a WPF-app.
I've tried to set it up using the MVVM design pattern. Keyword: tried.

I've a ViewModel (RidelHubMainViewModel), and several windows/views.

In one of my windows (LicenseHoldersWindow), I've a DataGrid. It's hooked up to Sql-server and bound using an ObservableCollection. So far; all good.

I've been going back and forth trying to implement new functionality, like adding rows to the DataGrid, and removing them. Adding rows is done through opening up a child-window and adding the data into textboxes.

But then I realized my method to add new rows to the Sql-server and update the ObservableCollection was not adhering to the MVVM design pattern. So I tried to move it to the ViewModel.

I did, and now the method can't read my textbox-value references.
So, I tried to create an instance of the child-form, like such:

C#
NewLicenseHolderWindow nlh = Application.Current.Windows.OfType<NewLicenseHolderWindow>().FirstOrDefault();


But when I run my app and try to add the new row to the DataGrid (by clicking a 'Save'-button in the child-window, which also closes the child-window (and returns me to the window with the DataGrid, I get the following error message:

Quote:
System.NullReferenceException: 'Object reference not set to an instance of an object.'

nlh was null.


So, I am obviously not grabbing the current instance of the child-window, right? How can I do that?

For reference, the way I update the DataGrid is by using an ICommand. It looks like this:

C#
private void ExecuteRefreshCommand() {

    NewLicenseHolderWindow nlh = 
    Application.Current.Windows.OfType<NewLicenseHolderWindow>().FirstOrDefault();

    if (LicenseHolders == null) {

        LicenseHolders = new ObservableCollection<LicenseHolder>();

    } else {

        AddToDB();
        LicenseHolders.Clear();
        LicenseHolders.Add(new LicenseHolder() { Foretaksnavn = 
        nlh.txtForetaksnavn.Text.ToString() });
        // more of the same...
        OnLicenseHoldersPropertyChanged();
    }
}

public void AddToDB() {

    string sqlString = "insert into tblLicenseHolder (Foretaksnavn, /* more of the same 
    */) values (@Foretaksnavn, /* more of the same */)";

    NewLicenseHolderWindow nlh = 
    Application.Current.Windows.OfType<NewLicenseHolderWindow>().FirstOrDefault();

    try {

        using (SqlConnection sqlCon = new(ConnectionString.connectionString))
        using (SqlCommand sqlCmd = new(sqlString, sqlCon)) {

            sqlCon.Open();

            if (string.IsNullOrEmpty(nlh.txtForetaksnavn.Text) // checks all other 
            textboxes {

                MessageBox.Show("Vennligst fyll ut alle tekstboksene.");
                sqlCon.Close();

            } else {

                sqlCmd.Parameters.AddWithValue("@Foretaksnavn", 
                nlh.txtForetaksnavn.Text.ToString());
                // more of the same...
                sqlCmd.ExecuteNonQuery();

                MessageBox.Show("Ny løyvehaver lagret. Husk å oppdatere listen.");
            }
        }

    }
    catch (Exception ex) {

        MessageBox.Show(ex.Message, "Message", MessageBoxButton.OK, MessageBoxImage.Information);
    }
}


Sorry for the long read...

Hopefully this makes sense to anyone.

What I have tried:

Honestly, I've moved the code around, tried to adapt it, modify it - in so many ways - that I've lost count.

Thanks in advance!
Posted
Updated 23-Jun-21 1:32am
v2

1 solution

FirstOrDefault will return null if there are no matching elements. Therefore, since you've already closed the form, there won't be any open forms of the correct type, and nlh will be null.

Trying to access the controls on a window is the wrong approach. Create a view-model to represent the data for the new row, and set that as the DataContext on the new window. Bind the controls on the window to the view-model properties. Once the window closes, use that view-model instance to read the data for the new record.
   
Comments
Flidrip 23-Jun-21 9:17am
   
@Richard Deeming, thank you - that makes sense.

As for your suggested solution, I will try to implement it.
I have a ViewModel - do you advice to have multiple, one for each View?

As for reading the data, with that, you mean, f.ex:
Create a new Model, f.ex: "TextBoxValue" (I place my getters/setters in a model holder class, this is MVVM-approved, no?) -> create a new ObservableCollection<TextBoxValue> TextBoxValues { get; set; } in the ViewModel, and then, once the window close, read the data like so:

TextBoxValues.Add(new TextBoxValue() { Foretaksnavn = // how would I reference the TextBox here? (I feel like I'm missing something obvious....)

Appreciate your time spent.
Richard Deeming 23-Jun-21 9:31am
   
You would create a view-model to represent the values you want to enter:
public class NewLicenseHolderViewModel : ViewModelBase
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set { SetValue(ref _name, value); }
    }
    ...
}

Your view then binds the control values to the properties on the view-model:
<TextBox Text="{Binding Path=Name}" />

When the dialog closes, you use the property values from the view-model as appropriate:
NewLicenseHolderViewModel vm = new NewLicenseHolderViewModel();
NewLicenseHolderView view = new NewLicenseHolderView { DataContext = vm };
if (view.ShowDialog() == true)
{
    // Use the view-model properties here...
    string name = vm.Name;
    ...
}
Richard Deeming 23-Jun-21 9:35am
   
If you don't already have a base class for your view-models:
public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    protected bool SetValue<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Equals(storage, value)) return false;
        
        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900