1

I'm working on a C# WPF project in Visual Studio in which I allow the user to add a user control (with two text boxes in the code below), which creates a class (with data stored within the user control so that multiple user controls can be created with a single button) and adds it to a list stored in the main window file to be written to a csv file later. I'm getting the following error when I hit the button to create a new user control:

An unhandled exception of type 'System.NullReferenceException' occurred in MasterListErrorTest.exe

Additional information: Object reference not set to an instance of an object.

I created a simplified version of my project that contains just the elements needed to reproduce the error. Here's all my code so you can plug it straight in and get the error for yourself. What am I doing wrong?

MainWindow.xaml:

<Window x:Class="MasterListErrorTest.MainWindow"
        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:MasterListErrorTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel x:Name="UserControlContainer" HorizontalAlignment="Left" Height="216" Margin="24,83,0,0" VerticalAlignment="Top" Width="471" Orientation="Horizontal"/>
        <Button x:Name="CreateNewControl" Content="Create New" HorizontalAlignment="Left" Margin="76,37,0,0" VerticalAlignment="Top" Width="75" Click="CreateNewControl_Click"/>
        <Button x:Name="GiveStringFromList" Content="Give String" HorizontalAlignment="Left" Margin="360,37,0,0" VerticalAlignment="Top" Width="75" Click="GiveStringFromList_Click"/>

    </Grid>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MasterListErrorTest
{
    public partial class MainWindow : Window
    {
        int MultipleTextBoxControlID = 1;

        public MainWindow()
        {
            InitializeComponent();
        }

        public static class TextBoxControlList
        {
            public static List <MultipleTextBoxControl.TextBoxData> MasterDataList;
        }

        private void CreateNewControl_Click(object sender, RoutedEventArgs e)
        {
            MultipleTextBoxControl newUserControl = new MultipleTextBoxControl(MultipleTextBoxControlID);
            UserControlContainer.Children.Add(newUserControl);
        }

        private void GiveStringFromList_Click(object sender, RoutedEventArgs e)
        {
            foreach (MultipleTextBoxControl.TextBoxData textBoxPanel in TextBoxControlList.MasterDataList)
            {
                List<string> userControlLine = new List<string>();

                userControlLine.Add(textBoxPanel.Identifier.ToString());
                userControlLine.Add(textBoxPanel.TextBox1Data);
                userControlLine.Add(textBoxPanel.TextBox2Data);

                MessageBox.Show(string.Join(",", userControlLine));
            }
        }
    }
}

MultipleTextBoxControl.xaml:

<UserControl x:Class="MasterListErrorTest.MultipleTextBoxControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MasterListErrorTest"
             mc:Ignorable="d" 
             d:DesignHeight="50" d:DesignWidth="300">
    <Grid>
        <TextBox x:Name="textBox1" HorizontalAlignment="Left" Height="23" Margin="0,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120" TextChanged="textBox1_TextChanged"/>
        <TextBox x:Name="textBox2" HorizontalAlignment="Left" Height="23" Margin="153,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="120" TextChanged="textBox2_TextChanged"/>

    </Grid>
</UserControl>

MultipleTextBoxControl.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MasterListErrorTest
{
    public partial class MultipleTextBoxControl : UserControl
    {
        TextBoxData newTextBoxGroup = new TextBoxData();

        public MultipleTextBoxControl(int identifier)
        {
            InitializeComponent();

            newTextBoxGroup.Identifier = identifier;
            MainWindow.TextBoxControlList.MasterDataList.Add(newTextBoxGroup);
        }

        public class TextBoxData
        {
            public int Identifier { get; set; }
            public string TextBox1Data { get; set; }
            public string TextBox2Data { get; set; }

            public TextBoxData()
            {
                TextBox1Data = "Unchanged Textbox 1";
                TextBox2Data = "Unchanged Textbox 2";
            }
        }

        private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
        {
            newTextBoxGroup.TextBox1Data = textBox1.Text;
        }

        private void textBox2_TextChanged(object sender, TextChangedEventArgs e)
        {
            newTextBoxGroup.TextBox2Data = textBox2.Text;
        }
    }
}
Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
nb5t3uv
  • 37
  • 5
  • possible duplicate of [What is a NullReferenceException and how do I fix it?](http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Steve Sep 17 '15 at 05:32

3 Answers3

1

If you put a try/catch exception block around the code in your CreateNewControl_Click then the caught exception will give you more information about what's going on. The StackTrace says this:

at GuiTest.MultipleTextBoxControl..ctor(Int32 identifier) in c:\Dev\GuiTest\MultipleTextBoxControl.xaml.cs:line 31
at GuiTest.MainWindow25.CreateNewControl_Click(Object sender, RoutedEventArgs e) in c:\Dev\GuiTest\MainWindow25.xaml.cs:line 43

So the most recent item on the list is MultipleTextBoxControl.xaml.cs line 31:

MainWindow25.TextBoxControlList.MasterDataList.Add(newTextBoxGroup);

Placing a breakpoint here and examining the contents reveals that MasterDataList is null, because you're not initializing it in TextBoxControlList:

public static List <MultipleTextBoxControl.TextBoxData> MasterDataList;

So do so:

public static List <MultipleTextBoxControl.TextBoxData> MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();

Personally I would strongly advise against the use of static classes members, especially in cases like this, but either way this is the answer to your question.

EDIT:

I totally agree with the main points that AnjumSKhan is making, although I personally would go about it using Inversion of Control (IoC). What you're really trying to do is get the child controls to register their data in a way that the Main Window code can access later. As AnjumSKhan points out the children shouldn't know anything about their parent, but you should also be able to create and unit test this behaviour in your child class without needing to create a parent. Inversion of control involves passing in an interface to the child that it should use to register itself, a very simple example might be this:

public interface IDataRegistrationService
{
    void Register(MultipleTextBoxControl.TextBoxData data);
}

Your child window can accept a reference to such a service in its constructor and do the registration as it was before but using this service instead:

    public MultipleTextBoxControl(int identifier, IDataRegistrationService service)
    {
        InitializeComponent();
        newTextBoxGroup.Identifier = identifier;
        service.Register(newTextBoxGroup); // <---------
    }

Your MainWindow class can now inherit from this and pass a reference to itself in when the child is created (note I've also made MasterDataList a regular property of MainWindow):

    public static List <MultipleTextBoxControl.TextBoxData> MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();

    public void Register(MultipleTextBoxControl.TextBoxData data)
    {
        MasterDataList.Add(data);
    }

    private void CreateNewControl_Click(object sender, RoutedEventArgs e)
    {
        MultipleTextBoxControl newUserControl = new MultipleTextBoxControl(MultipleTextBoxControlID, this);
        UserControlContainer.Children.Add(newUserControl);
    }

By doing this you've formalized the relationship between the parent and child and prevented yourself or others adding more relationships between the two that might be difficult to untangle or change later. You're also now in a position where you can create an instance of your child and use a mocked object (e.g. using the Moq library) to test that it behaves as expected. And by maintaining good separation of concerns you have the freedom to pass in any service you want...maybe later on you'll decide that you need multiple panels with one server per panel.

The one downside to IoC is that you wind up passing references to service provider all over your project, with middle layers keeping references to objects higher up the hierarchy for the sole purpose of passing them lower down. This is what Dependency Injection frameworks solve (e.g. Ninject). They get rid of all that parameter passing and your final code winds up looking something like this:

public partial class MultipleTextBoxControl : UserControl
{
    // this gets created by the DI framework, with identifier set automatically
    [Inject] private TextBoxData newTextBoxGroup { get; set; }

    // this get injected automatically when the class is create
    [Inject] private IDataRegistrationService DataService {get; set;}

    public MultipleTextBoxControl()
    {
        InitializeComponent();
    }

    // this gets called immediately after the constructor
    public void Initialize()
    {
        // and you do any custom initialization here, using injected components
        this.DataService.Register(newTextBoxGroup);
    }
Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • This worked perfectly as well. I really appreciate the lesson in debugging as well. I'll use the try/catch exception block when I run into these in the future. – nb5t3uv Sep 17 '15 at 14:37
  • Also, is there any chance you'd give me a hint at what you would advise for a case like this instead? Even just a broad statement--I'm trying to pick up as much as I can. – nb5t3uv Sep 17 '15 at 14:44
  • Answer updated. Btw when you hit an exception you can also add "$exception" to the watch window to see the details immediately without having to modify and re-run your code. – Mark Feldman Sep 17 '15 at 22:15
  • This is great stuff. I'll study this and rework my code. Thanks for the explanations! – nb5t3uv Sep 21 '15 at 11:38
1

Change your TextBoxControlList class to :

        public static class TextBoxControlList
        {
            public static List <MultipleTextBoxControl.TextBoxData> MasterDataList;
            static TextBoxControlList() {
                MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();
            }
        }

A better way to do :

Refactor#1

MultipleTextBoxControl.xaml.cs

public partial class MultipleTextBoxControl : UserControl
    {
        TextBoxData _newTextBoxGroup;
        public TextBoxData TextBoxGroup { get { return _newTextBoxGroup; } }

        public MultipleTextBoxControl(int identifier)
        {
            InitializeComponent();

            _newTextBoxGroup = new TextBoxData(identifier);            

        }

        public class TextBoxData
        {
            public int Identifier { get; set; }
            public string TextBox1Data { get; set; }
            public string TextBox2Data { get; set; }

            public TextBoxData(int identifier)
            {
                Identifier = identifier;

                TextBox1Data = "Unchanged Textbox 1";
                TextBox2Data = "Unchanged Textbox 2";
            }
        }

        private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
        {
            _newTextBoxGroup.TextBox1Data = textBox1.Text;
        }

        private void textBox2_TextChanged(object sender, TextChangedEventArgs e)
        {
            _newTextBoxGroup.TextBox2Data = textBox2.Text;
        }
    }

MainWindow.cs

public partial class MainWindow : Window
    {
        int MultipleTextBoxControlID = 1;
        public static List<MultipleTextBoxControl.TextBoxData> MasterDataList;

        static MainWindow() {
            MasterDataList = new List<MultipleTextBoxControl.TextBoxData>();
        }

        public MainWindow()
        {
            InitializeComponent();
        }


        private void CreateNewControl_Click(object sender, RoutedEventArgs e)
        {
            MultipleTextBoxControl newUserControl = new MultipleTextBoxControl(MultipleTextBoxControlID);
            UserControlContainer.Children.Add(newUserControl);

            MasterDataList.Add(newUserControl.TextBoxGroup);
        }

        private void GiveStringFromList_Click(object sender, RoutedEventArgs e)
        {
            foreach (MultipleTextBoxControl.TextBoxData textBoxPanel in MasterDataList)
            {
                List<string> userControlLine = new List<string>();

                userControlLine.Add(textBoxPanel.Identifier.ToString());
                userControlLine.Add(textBoxPanel.TextBox1Data);
                userControlLine.Add(textBoxPanel.TextBox2Data);

                MessageBox.Show(string.Join(",", userControlLine));
            }
        }
    }
AnjumSKhan
  • 9,647
  • 1
  • 26
  • 38
  • Thank you very much! Would you mind expanding on what makes the second version better? – nb5t3uv Sep 17 '15 at 14:35
  • Not a good idea for a UserControl to depend upon Parent control, as it will make it less re-usable. For passing information from Child to Parent and vice-versa, expose properties or use events. Thats what events are meant for. Now our UserControl doesn't dependupon parent window in any way. We have exposed a property which will always be populated. All static fields should be initialized in static ctor of class. – AnjumSKhan Sep 17 '15 at 15:28
  • That's a really good point. I hadn't thought about reusability here. – nb5t3uv Sep 21 '15 at 11:36
0

I used semiglobal reference to MainWindow in user control like this:

public partial class MainWindow : Window
{
        public static MainWindow Me = null;
        public MainWindow()
        {
            Me = this;
            InitializeComponent();
            

Then I used this "Me" in User control:

 public partial class UserControlTable : UserControl
 {
      DataCenterSender m_DataCenterSender = new DataCenterSender(MainWindow.Me.Get_DataCenter());

And had a Null Pointer in XAML Editor.

Looks like constructor of MainWindow has not been called in XAML editor and since MainWindow.Me==null in this case User Control ctor failed.

Danil
  • 701
  • 8
  • 7