Winforms - get set data

NachitoMax 411 Reputation points
2021-04-11T04:29:32.32+00:00

Hi

I wanted to ask to make sure I'm doing it right or get advice if I'm doing it wrong.

My main form has 4 usercontrols as selectable stages in a process. I have set up my form to hold all of the variable members that need to hold data. I pass my form as the form to the usercontrols for easy parent referencing.

My questions(s)

Should I use properties instead of variable members to hold the data and if so, am I better to have them in a separate class?

I have all of the functions and methods in the form so that I can reference to form controls from the usercontrols. Is this reasonable or should I move them to a class too? I have all my general functions in relavent classes already.

Currently I have variable members in the usercontrols that are populated and upon confirmation of the data entries, the values are then transferred to the main form. Is this good or bad practice and if bad, what would be good practice?

I've learned a lot recently but I've come to realize the more I learn, there is a lot more to learn...

Windows Forms
Windows Forms
A set of .NET Framework managed libraries for developing graphical user interfaces.
1,821 questions
.NET
.NET
Microsoft Technologies based on the .NET software framework.
3,346 questions
{count} votes

Accepted answer
  1. Sam of Simple Samples 5,516 Reputation points
    2021-04-11T21:14:26.437+00:00

    As others have said, there are many ways to do it. Some of them might be intimidating by their complexity but would be more likely to be used professionally. The following would be less professional but hopefully easier to understand and use.

    First, the User Control should not depend (know about) the form. You say you pass your form as the form to the usercontrols. You want to avoid (not do) that.

    There are complicated ways to solve the problem of how to notify the form when something happens. As a beginner you should at least understand how to use delegates.

    So let us say that you have a button in the User Control and you want the form to do something when the button is pressed. You can create a method in the form that is to execute when the button is pressed; something like:

    Private Sub ButtonClicked(ByVal text As String)
      label1.Text = text
    End Sub
    

    In the User Control define and declare a delegate as in:

    Public Delegate Sub ButtonClickedEvent(ByVal text As String)
    Public ButtonClicked As ButtonClickedEvent = Nothing

    Notice how void ButtonClickedEvent(string text) matches the actual method. Note also that ButtonClicked is set to null. Then create an event handler for the button and add:

    ButtonClicked?.Invoke("Button clicked at " & Date.Now.ToString())

    Note that the combination of the "?" and Invoke will check ButtonClicked for null and if it is null then it is not called. So if the form does not use the delegate nothing happens. Then in the form's constructor (after InitializeComponent) set the delegate to the method, as in:

    UserControl11.ButtonClicked = AddressOf ButtonClicked
    

    Assuming you have everything wired properly, when the button is clicked in the User Control, the form will update the label.

    What if you want the form to do something when a text box is changed? You could do something similar except use the Leave event of the text box instead of the click event. Note that the Leave event does not fire when the form is closed so you might need to do something more for situations like that.

    The following is the complete VB.Net code that implements events for both a button clicked and to leave a test box. Note that you will need a button and a text box in the user control and the user control and two labels in the form.

    Public Class UserControl1
        Public Delegate Sub ButtonClickedEvent(ByVal text As String)
        Public ButtonClicked As ButtonClickedEvent = Nothing
        Public Delegate Sub SomethingChangedEvent(ByVal text As String)
        Public SomethingChanged As SomethingChangedEvent = Nothing
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            ButtonClicked?.Invoke("Button clicked at " & Date.Now.ToString())
        End Sub
    
        Private Sub TextBox1_Leave(sender As Object, e As EventArgs) Handles TextBox1.Leave
            SomethingChanged?.Invoke(TextBox1.Text)
        End Sub
    End Class
    
    Public Class Form1
    
        Public Sub New()
            InitializeComponent()
            UserControl11.ButtonClicked = AddressOf ButtonClicked
            UserControl11.SomethingChanged = AddressOf SomethingChanged
        End Sub
    
        Private Sub ButtonClicked(ByVal text As String)
            Label1.Text = text
        End Sub
    
        Private Sub SomethingChanged(ByVal text As String)
            Label2.Text = text
        End Sub
    End Class
    

2 additional answers

Sort by: Most helpful
  1. Karen Payne MVP 35,031 Reputation points
    2021-04-11T10:04:08.343+00:00

    Hello,

    There are several ways to approach this. One idea is to use a singleton class. Having code presented below in a class project

    • Keeps code separated from any single form
    • Allows the code to be used in other projects
    • Is easily testable

    Notes

    • Main class implements INotifyPropertyChanged which is optional
    • Data annotation is used if you need to validate information and is optional.
    • Code is thread safe

    Setup a singleton class as in this project on GitHub

    using System;
    using SingletonStorage.Classes;
    
    namespace SingletonStorage
    {
        public sealed class RegistrationContainer
        {
            private static readonly Lazy<RegistrationContainer> Lazy = 
                new Lazy<RegistrationContainer>(() => new RegistrationContainer());
    
            public static RegistrationContainer Instance => Lazy.Value;
    
            public Person Person { get; set; }
    
            public void CreatePerson()
            {
                Person = new Person();
            }
            private RegistrationContainer()
            {
                CreatePerson();
            }
        }
    }
    

    Then to test functionality works, I created a simple unit test project. The RegistrationContainer class is initialized in a class named TestBase which is triggered from the Init event in UnitTest1 class then validated in RegistrationTestAcrossMethods test method.

    using System.Collections.Generic;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using RegistrationUnitTests.Base;
    using SingletonStorage;
    
    namespace RegistrationUnitTests
    {
        [TestClass]
        public class UnitTest1 : TestBase
        {
            [TestMethod]
            [TestTraits(Trait.Registration)]
            public void RegistrationTestAcrossMethods()
            {
                var expectedFirstName = "Karen";
                var expectedLastName = "Payne";
    
                Assert.IsTrue(
                    RegistrationContainer.Instance.Person.FirstName == expectedFirstName &&
                    RegistrationContainer.Instance.Person.LastName == expectedLastName,
                    $"Expected {expectedFirstName} {expectedLastName} in {nameof(RegistrationTestAcrossMethods)}");
            }
    
            [ClassInitialize()]
            public static void ClassInitialize(TestContext testContext)
            {
                TestResults = new List<TestContext>();
            }
    
            [TestInitialize]
            public void Init()
            {
                if (TestContext.TestName == "RegistrationTestAcrossMethods")
                {
                    IntRegistration();
                }
            }
        }
    }
    

  2. NachitoMax 411 Reputation points
    2021-04-12T14:43:20.85+00:00

    Hi

    Thanks for everyones replies, some great responses and some things to think about. i'll reply individually to each response :)

    0 comments No comments