One option is to pass data from child form to parent form using an event which is invoked when clicking a button on the child form, data is in a validate state invoke the event then set DialogResult to OK.
The following is a conceptual example where the main form opens a child form for adding a new item of type Note.
If all you need is a single property/value this will still work by changing the delegate signature OnAddNote.
Note class
public class Note : INotifyPropertyChanged
{
private string _title;
private string _content;
private int _id;
public int Id
{
get => _id;
set
{
_id = value;
OnPropertyChanged();
}
}
public string Title
{
get => _title;
set
{
_title = value;
OnPropertyChanged();
}
}
public string Content
{
get => _content;
set
{
_content = value;
OnPropertyChanged();
}
}
public override string ToString() => Title;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Operations class
NewNote method is called from the child form Add button when data is validated
public class Operations
{
public delegate void OnAddNote(Note note);
public static event OnAddNote AddNote;
public static List<Note> NotesList = new List<Note>();
/// <summary>
/// Pass new Note to listeners
/// </summary>
/// <param name="note"></param>
public static void NewNote(Note note)
{
AddNote?.Invoke(note);
}
/// <summary>
/// Edit note, do some validation
/// </summary>
/// <param name="note"></param>
/// <returns></returns>
public static Note EditNote(Note note)
{
throw new NotImplementedException();
}
/// <summary>
/// Load mocked data, for a real application if persisting data use <see cref="LoadNotes"/>
/// </summary>
public static void Mocked()
{
NotesList.Add(new Note()
{
Title = "First",
Content = "My note"
});
}
/// <summary>
/// For a real application which persist your notes we would load from
/// - a database
/// - file (xml, json etc)
/// </summary>
/// <returns></returns>
public static List<Note> LoadNotes()
{
throw new NotImplementedException();
}
/// <summary>
/// Delete a note in <see cref="NotesList"/>
/// </summary>
/// <param name="note"></param>
public static void Delete(Note note)
{
throw new NotImplementedException();
}
public static Note FindByTitle(string title)
{
throw new NotImplementedException();
}
/// <summary>
/// Save data to the data source loaded from <see cref="LoadNotes"/>
/// </summary>
/// <returns>
/// Named value tuple
/// success - operation was successful
/// exception - if failed what was the cause
/// </returns>
public static (bool success, Exception exception) Save()
{
throw new NotImplementedException();
}
}
Child form
public partial class AddNoteForm : Form
{
public AddNoteForm()
{
InitializeComponent();
}
private void AddButton_Click(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(TitleTextBox.Text) && !string.IsNullOrWhiteSpace(NoteTextBox.Text))
{
Operations.NewNote(new Note() {Title = TitleTextBox.Text, Content = NoteTextBox.Text});
DialogResult = DialogResult.OK;
}
else
{
DialogResult = DialogResult.Cancel;
}
}
}
Main form
public partial class Form1 : Form
{
private readonly BindingSource _bindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
Shown += OnShown;
}
private void OnShown(object sender, EventArgs e)
{
Operations.AddNote += OperationsOnAddNote;
Operations.Mocked();
_bindingSource.DataSource = Operations.NotesList;
NotesListBox.DataSource = _bindingSource;
ContentsTextBox.DataBindings.Add("Text", _bindingSource, "Content");
}
private void OperationsOnAddNote(Note note)
{
_bindingSource.Add(note);
NotesListBox.SelectedIndex = NotesListBox.Items.Count - 1;
}
private void AddButton_Click(object sender, EventArgs e)
{
var addForm = new AddNoteForm();
try
{
addForm.ShowDialog();
}
finally
{
addForm.Dispose();
}
}
}

Full source