Question

I tried searching, but could not find anything that gave me quite what I was looking for. I am creating a WPF, desktop application which will end up having four or five forms. Each form will collect data to be sent via email. I also created a separate class (SendMail), which has the code to send the email. I want to be able to access the text from the text boxes of the various forms and send them via a method in the SendMail class.

Currently, I only have two basic forms set up with a few fields and next page, submit, and exit buttons. I want to be able to submit the data from any page, if none of the following pages need to be filled out. I am currently able to access each form from the SendMail class via internal properties and when i hit the Submit button on the first form, the email sends properly. However, if I go to the next form and hit the Submit button, I receive an "Object reference not set to an instance of an object" error for a property referencing the text of a text box on the first form. I'm assuming that by going to the second form, the instance of the first form no longer exists.

I took a few programming classes back in college several years ago, but I've now decided to study it more seriously on my own. I've read several books, but have only been studying for a few months, so I'm probably approaching this the wrong way. Any help is appreciated.

EDIT- Below are some samples of the code, as requested. I removed email addresses/passwords from the SendMail class.

First window

public partial class MainWindow : Window
{
    SendMail page1;

    // Properties to allow access to SendMail.
    internal string CmbEmail
    {
        get { return this.cmbEmail.Text; }
    }

    internal string DateWritten
    {
        get { return this.dateWritten.Text; }
    }

    public MainWindow()
    {
        InitializeComponent();
        page1 = new SendMail(this);
    }

    private void btnSubmit_Click_1(object sender, RoutedEventArgs e)
    {
        page1.Email();
    }

    private void btnNextPage_Click(object sender, RoutedEventArgs e)
    {
        Window1 nextPage = new Window1(this);
        nextPage.Show();
        this.Close();
    }
}

Second window

public partial class Window1 : Window
{
    SendMail page2;

    public Window1(MainWindow parent)
    {
        InitializeComponent();
        page2 = new SendMail(this);
    }

    private void btnExit_Click(object sender, RoutedEventArgs e)
    {
        this.Close();
    }

    private void btnSubmit_Click(object sender, RoutedEventArgs e)
    {
        page2.Email();
    }
}

SendMail class

class SendMail
{
    MainWindow page1;
    Window1 page2;

    public SendMail(MainWindow form)
    {
        page1 = form;
    }

    public SendMail(Window1 form)
    {
        page2 = form;
    }

    public void Email()
    {
        NetworkCredential cred = new NetworkCredential("", "");
        MailMessage msg = new MailMessage();
        msg.To.Add("");

        // Send an email to address in the Email field, if not empty.
        if (page1.CmbEmail != "") // This is the line throwing the error, but only when submitting from the second window.
        {
            msg.To.Add(page1.CmbEmail);
        }

        msg.From = new MailAddress("");
        msg.Subject = "Garment Order " + page1.DateWritten.ToString();
        msg.Body = "Test email";

        SmtpClient client = new SmtpClient("smtp.gmail.com", 587);
        client.Credentials = cred;
        client.EnableSsl = true;
        client.Send(msg);
    }
}
Was it helpful?

Solution

I think you're correct that the TextBox on the first form no longer exists, which is why you're getting the error. This is because WPF unloads controls that are no longer visible, and all data from them is forgotten unless they're bound to something in the DataContext.

I would highly recommend using the MVVM design pattern if you're working with WPF. It's perfectly suited to the technology, and I find it keeps the code clean and easy to maintain.

In your situation, I would have a single ViewModel for your entire application, and have it contain the SubmitCommand and 5 data objects, each representing the data from one "Form"

For example, your MainViewModel might look like this:

public MainViewModel : INotifyPropertyChanged
{
    // Should be full properties that implement INotifyPropertyChanged, 
    // but leaving that out for simplicity right now
    public ObservableCollection<object> Forms { get; set; }
    public object CurrentForm { get; set; }

    public ICommand SubmitCommand { get; set; }
    // Could also add ICommands for Back and Next buttons as well

    public MainViewModel()
    {
        Forms = new ObservableCollection()
        {
            new Form1Data(),
            new Form2Data(),
            new Form3Data(),
            new Form4Data(),
            new Form5Data()
        };

        CurrentForm = Forms.FirstOrDefault();
    }
}

And your XAML would look something like this:

<Window>
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:Form1Data}">
            <local:Form1UserControl /> 
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Form2Data}">
            <local:Form2UserControl /> 
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Form3Data}">
            <local:Form3UserControl /> 
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Form4Data}">
            <local:Form4UserControl /> 
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Form5Data}">
            <local:Form5UserControl /> 
        </DataTemplate>
    </Window.Resources>

    <DockPanel>
        <Button Command="{Binding SubmitCommand}" 
                Content="Submit" DockPanel.Dock="Bottom" />

        <ContentControl Content="{Binding CurrentForm}" />
    </DockPanel>
</Window>

(You could also write out form controls in XAML in the DataTemplates instead of using a UserControl)

If you're new to WPF and the MVVM design pattern, I have a very simple example on my blog that you may be interested in reading to get started.

Edit

I just saw your updated question with the code, and your problem here is that you have two copies of your SendMail class, one with your first window and one with your second window. You need to have a single copy of the SendMail class, and have it reference both windows.

Although like I said in the beginning of my answer, I don't think that will work because WPF unloads UI objects that aren't visible, so it's likely that any data in your Window will be lost when you .Close() it.

OTHER TIPS

Look at the constructors.

This will use the 2nd ctor (and not assign page1)

page2 = new SendMail(this);

The above this is a Window1

public SendMail(MainWindow form)
{
    page1 = form;
}

public SendMail(Window1 form)
{
    page2 = form;
}

The Window1 ctor does not assign page1.
So page1.CmbEmail is going to throw a "Object reference not set to an instance of an object".

I think you could fix this with

   public SendMail(Window1 form, MainWindow mainWindow)
    {
        page2 = form;
        page1 = mainWindow;
    }

Then when you call it

page2 = new SendMail(this, parent);

But that breaks if the parent Window gets closed.
But since the parent Windows is the MainWindow that would close everything anyway.
So there would not longer be a Page1 submitt button that would fail.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top