question

nachoshaw-9496 avatar image
0 Votes"
nachoshaw-9496 asked nachoshaw-9496 commented

Winforms - get set data

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-formsdotnet-standard
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

There are no hard and fast answers to these questions - as always, it depends!
If you see an advantage to doing things one way or another, whether that's just making your software neater/more easily understood/more maintainable, then go ahead if you can't see a reason not to. Even if there's only yourself who has to maintain the software, you will forget things, and you should benefit in the future from attempts to make things clearer now.

1 Vote 1 ·

Hi,

it is only myself maintaining the software. My thought process on construction was mainly because i have 1 mainform that will invoke a wizard type UC. On that wizard, based on a decision of next steps it will instantiate a product form with a collection of steps as UCs. Each one will be different but they all share the same data collection. I thought that by keweping everything in the Parent, any sub could then just refer back to set / get etc.

Thanks for your reply

0 Votes 0 ·
SimpleSamples avatar image
0 Votes"
SimpleSamples answered nachoshaw-9496 commented

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
· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi

Im not quite getting this to work. Im using VB.Net, not C#-

When i add the line UC11.ButtonClicked = ButtonClicked to the Form constructor under InitializeComponent, i get an error 'Argument not specified which would be because ButtonClicked has an argument (ByVal Text As String). So
i tried it the other way because C# tends to be the opposite structure to VB.Net - ButtonClicked(UC11.ButtonClicked) which would call the ButtonClicked method with an argument from UC11.ButtonClicked but that threw the same error

i had a much better reply that included my code but there is a 1000 char max limit on responses? bit odd that.....

0 Votes 0 ·

Form

  Public Class Form1 
              Public Sub New()
                  ' This call is required by the designer.
                  InitializeComponent()
                
                  ' Add any initialization after the InitializeComponent() call.
                  UC11.ButtonClicked = ButtonClicked() 
              End Sub
                
                
              Private Sub ButtonClicked(ByVal Text As String)
                  TextBox1.Text = Text
              End Sub
          End Class

UserControl

  Public Class UC1
      Public Delegate Sub ButtonClickedEvent(ByVal text As String)
      Public ButtonClicked As ButtonClickedEvent = Nothing
        
      Private Sub TextBox1_Leave(sender As Object, e As EventArgs) Handles TextBox1.Leave
          ButtonClicked?.Invoke("Button clicked at " + DateTime.Now.ToString())
      End Sub
  End Class








0 Votes 0 ·

Sorry, I did not see anything saying VB.Net. The important difference for VB.Net is you need to add AddressOf in the assignment of the ButtonClicked handler in the Form constructor. Also, you are using a TextBox Leave event when it should be a Button Click event.

0 Votes 0 ·

actually my bad, i should have knows about AddressOf and AddHandler as i have that already in other handlers...

AddHandler NavEditFrame.TransitionManager.AfterTransitionEnds, AddressOf TransitionManager_AfterTransitionEnds

appreciate your time taken to help me out :)


0 Votes 0 ·
karenpayneoregon avatar image
0 Votes"
karenpayneoregon answered nachoshaw-9496 commented

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();
             }
         }
     }
 }










· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi Karen

As always, I appreciate your reply. It may be too complicated for what i need but i will certainly be looking at the code to fully understand the processes :)

Thanks

0 Votes 0 ·
nachoshaw-9496 avatar image
0 Votes"
nachoshaw-9496 answered

Hi

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


5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.