Retain values before validating UserControl - c#

I have a class which is a UserControl:
public partial class MyObjectView : System.Windows.Forms.UserControl
This interface has various components for user input. To show the issue I'm having, only one is needed to show, so, in MyObjectView.Designer.cs:
internal System.Windows.Forms.TextBox txtMyNumber;
In the MyObjectView constructor, I have:
this.Validating += new System.ComponentModel.CancelEventHandler(this_Validating);
and then:
private void this_Validating(object sender, System.ComponentModel.CancelEventArgs cancelEventArgs)
{
// MyObject here already contains the data entered in the control by the user
}
So the user enters data and clicks the close X button in the upper right hand corner. I want to see the difference between what MyObject originally contained (when the UserControl was first displayed) and what the user entered on the form. However, in this_Validating, MyObject already has been updated to what the user entered, so I no longer have the 'before'
How do I do this?

Why don't you clone the original object in your constructor so you have a copy of it's initial state:
class MyClass
{
public string Name { get; set; }
public MyClass ShallowCopy()
{
return (MyClass)this.MemberwiseClone();
}
}
//Copy of original state of object
private MyClass _orig;
public Form1()
{
InitializeComponent();
//In your case this comes in via the constructor?
MyClass o = new MyClass();
o.Name = "hi";
_orig = o.ShallowCopy();
...

Related

How to store a reference to a struct inside a class

I have a main form which opens a second form as a dialog.
On the dialog form close, I would like to edit an external struct so all the form settings are captured on close.
I pass a struct into the dialog form constructor, but it seems it is copied when I save it to a private property inside the class.
How can I maintain a link to the external struct?
Below is the relevant code for the dialog form.
public partial class MASettingsForm : Form {
private MovingAverageSettings _settings;
public MASettingsForm(ref MovingAverageSettings settings) {
InitializeComponent();
_settings = settings;
SetUI(_settings);
}
public void SetUI(MovingAverageSettings settings) {
maTypeComboBox.SelectedIndex = (int)settings.type;
maCalcMethodComboBox.SelectedIndex = (int)settings.calcMethod;
maCalcValueUpDown.Value = settings.calcPeriod;
maOffsetUpDown.Value = settings.offset;
applyToHeikenAshicheckBox.Checked = settings.useHeikenAshi;
OnTypeChange();
}
// User clicks ok to apply the dialog form settings
private void okBtn_Click(object sender, EventArgs e) {
DialogResult = DialogResult.OK;
_settings.type = (enum_Ma_Type)maTypeComboBox.SelectedIndex;
_settings.calcMethod = (enum_Calculation_Method)maCalcMethodComboBox.SelectedIndex;
_settings.calcPeriod = (int) maCalcValueUpDown.Value;
_settings.offset = (int)maOffsetUpDown.Value;
SetExtendedValues(_settings);
Close();
}
The best you can do is is to introduce a wrapper around the struct:
public class Ref<T>
{
public T Value { get; set; }
public Ref(T value) => Value = value;
}
And instead of trying to pass a ref to the class constructor - pass a Ref<MovingAverageSettings> object.
That way your struct can be immutable, and you would just replace the entire struct when it needs to change. You can also add an event that is raised when the value is set, to let any form know if it needs to update itself to show the updated value.
But I would change your struct to a class, there is a lot of limitations and pitfalls with structs in c#, you can get some information in the article from MS: https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct?redirectedfrom=MSDN

Using a constructor to fill a list overwrites previous information

I'm still pretty new to C# so I do not know about all the possibilities that it has.
My code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Test ctest = new Test();
private void button1_Click(object sender, EventArgs e)
{
ctest.name = Convert.ToString(textBox1.Text);
MessageBox.Show(ctest.name);
ctest.namelist.Add(ctest);
listBox1.DataSource = null;
listBox1.DataSource = ctest.namelist;
}
}
class Test
{
private string Name;
public string name
{
get { return Name; }
set { Name = value; }
}
public List<Test> namelist = new List<Test>();
public override string ToString()
{
string spaceinbetween = new string(' ', 3);
return name + spaceinbetween + name;
}
What I'm trying to do:
So I want to use my constructor to fill up my listbox with information (name in this case). However it overwrites previous values after adding more then 2 names.
I assume this goes wrong because the class object name gets overwritten and is used in both of the lines that where added.
Question: Is there anyway that adding a new name does not change the previous list values or is there any way to make a new instance of the constructor anytime I wanne create a new ctest?
If I didn't misunderstand your problem, there are a lot of things to change to make your form work properly:
public partial class Form1 : Form
{
// BindingList is perfect here... istead of List. It automatically refreshes upon change.
private BindingList<Test> m_Tests;
public Form1()
{
InitializeComponent();
m_Tests = new BindingList<Test>();
listBox1.DataSource = m_Tests;
// DisplayMember and ValueMember should be defined too here.
}
private void button1_Click(object sender, EventArgs e)
{
// Create a new instance every time the button is clicked.
// Since textBox1.Text is already a String... no need to convert it!
// Thanks to BindingList, you don't need to refresh your binding...
m_Tests.Add(new Test(textBox1.Text));
}
}
public class Test
{
private String m_Name;
public String Name
{
get { return m_Name; }
set { m_Name = value; }
}
public Test(String name)
{
m_Name = name;
}
public override String ToString()
{
if (String.IsNullOrWhiteSpace(m_Name))
return "NO NAME";
return (m_Name + " " + m_Name);
}
}
Let's summarize my changes:
The list should not be part of your Test class, but of your Form1. This is where new instances should be properly added and managed, on a OOP point of view.
Since you are binding the list to a ListBox control, it's better to use BindingList (reference here). It was made exactly for this purpose and it can automatically handle changes.
The BindingList can be initialized when the Form1 constructor is called and its linkage to the ListBox control must be defined as well within the same context. Don't forget to valorize listBox1.DisplayMember and listBox1.ValueMember properties too in order to set up a proper display of your underlying data.
When the button is clicked, all you have to do is to create a new instance of your Test class, with its name defined by textBox1.Text, and add it to the BindingList.
With your previous approach, since a single Test class instance was created within the Form1 class, you were attempting to add the exact same instance every time your button was clicked... and that was producing basically nothing.
You can define a custom constructor for your Test class that accepts the name as argument, to facilitate the creation of new instances.
Properly handle your Test.ToString() override in order to avoid problems.
Overall, use the correct naming conventions to improve your code readability.

How do I get my class variables properties into Winform controls?

Currently I have multiple classes, one of which is called the 'Variable class' where I
{get;set;}
my values obtained from other classes. Accessing these values in voids is simply:
Private void (Variables vb)
{
}
However in the 'Load' part of Winforms,
private void Form1_Load(object sender, EventArgs e)
{
}
From the Variables Class:
public class Variables
{
public int Node { get; set; }
}
The object sender, EventArgs e part is occupying the space where I place the arguments. Is there any way that I could obtain Node from the class Variableson the winform?
Your method Form1_Load is an event handler (because it usually gets called as a result of some event occurring). The "Load" event is defined by WinForms, so you cannot change the fact that the arguments are object sender and EventArgs e.
WinForms creates one instance of your Form1 class before it displays your form. Whenever an event happens on your form, the event handler on that same object is called.
So, you can store values in fields and properties of your Form1 class:
public class Form1 : Form
{
Variables _myVariables;
public Form1()
{
InitializeComponent();
_myVariables = new Variables() { Node = 10 }
}
private void Form1_Load(object sender, EventArgs e)
{
MessageBox.Show("The current value of _myVariables.Node is: " + _myVariables.Node);
}
}
If your Variables object is created outside your form, then you can pass it into your Form1 constructor:
public class Form1 : Form
{
Variables _myVariables;
public Form1(Variables variables)
{
InitializeComponent();
_myVariables = variables;
}
// ...
}
// Then, somewhere else:
var variables = new Variables() { Node = 10 };
var myForm = new Form1(variables);
myForm.Show();
// or: Application.Run(myForm);
I'm not 100% sure if this is what you are looking for, but I think I can help.
namespace Example
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//class which can be used anywhere in the namespace
public class exampleClass
{
public const string SETSTATE = "0";
public const string GETSTATE = "1";
public const string SETVALUE = "2";
public const string GETVALUE = "3";
public const string SENDFILE = "4";
public const string STATE_RP = "129";
public const string VALUE_RP = "131";
public const string STATUS_RP = "128";
}
}
}
you can use exampleClass, and any of its enclosed members, anywhere within Form1 here. You do not need to pass it anywhere within the form to use it. You could add a function later that uses it directly like:
void exampleF()
{
//note that when you are accessing properties of UI elements from
//a non-UI thread, you must use a background worker or delegate
//to ensure you are doing so in a threadsafe way. thats a different problem though.
this.txtYourTextBox.txt = exampleClass.GETSTATE;
}
Maybe your attempt is it actually to use MVP-Pattern in WinForms. Very good idea.
Then you can use DataBinding to Bind your Forms-Controls to your "Variables classes" properties. Your variable class takes the presenter role, your form is the view, and your model(s) are the sources of your variable classes data.
Unfortunately, the pattern uses some advanced mechanisms you have to deal with.
For more information, you might take a first look here:
Databinding in MVP winforms

How can I make my Form Control variables acessible to classes other than my Form class?

For example after creating a new Windows Form project I have my class called Form1.cs and from that form I can simply start typing the name of a form control and it will auto populate the form control variable names and I am able to use them in the class. However I have other classes that need to be able to access these form control variables as well, but they are not accessible.
Make them public if they are going to be used in another assembly, or internal if they are going to be used in the same project. Making them static means you don't have to pass your Form1 into the other classes.
Example... Say your Form1 has a string that contains the text you display in the title bar. Making it internal static, like this:
internal static readonly string MsgBox_Title = " Best Application Evar!";
lets you access it from other classes like this:
Form1.MsgBox_Title
It doesn't have to be readonly; that's just an example I pulled from an old app...
If you don't want static variables, you'll have to pass in an instance of Form1.
public class SomeClass
{
private Form1 m_Form1;
public SomeClass(Form1 form1)
{
m_Form1 = form1;
}
private void someMethod()
{
string localValue = m_Form1.SomeMemberStringVariable;
}
}
It's a very contrived example, but hopefully you get the idea.
If you want to call the Refresh method from a class instantiated from Form1, you could use an event in the child class to notify Form1.
Example:
This Form1 has a button that I use to show a secondary form.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnShowPopup_Click(object sender, EventArgs e)
{
PopupForm f = new PopupForm();
f.CallRefreshHandler += PopupForm_CallRefreshHandler;
f.ShowDialog();
}
private void PopupForm_CallRefreshHandler(object sender, EventArgs e)
{
Refresh();
}
}
The secondary form, "PopupForm", has a button that I use to raise an event that the Form1 is subscribed to, and lets Form1 know to call Refresh.
public partial class PopupForm : Form
{
public event EventHandler CallRefreshHandler;
public PopupForm()
{
InitializeComponent();
}
private void btnRaiseEvent_Click(object sender, EventArgs e)
{
EventHandler handler = CallRefreshHandler;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
Hope this helps.
Create an object of that class & start using those variables like this
Form1 fm = new Form1();
string abc = fm.VAR;
Define a public property in your form.
public string MyProp { get; set; }
Form1 frm = new Form1();
frm.MyProp = "Value";
Or define the property as static to avoid having to instantiate Form1:
public static string MyProp { get; set; }
Form1.MyProp = "Value";
I ran into this issue recently. I was keeping some methods in a separate class. Maybe not a good design decision in my case, I'm not sure yet. And these methods sometimes needed to communicate with controls in the main Form1. For example, to write to textBox1.
Turns out easy enough. Just write your method signature to include a TextBox instance. For example you pass textBox1 in and inside the method you refer to it as tb. Then when you call that method (even though it is in another class) you set the tb.Text property to whatever you like and it will show on textBox1.
This makes sense when you consider that control is just a special kind of object, graphically represented in the Form. When you pass it as an argument to a method in another class or the same class, you are actually passing the reference. So writing text to it in the method call will write text to the original control.

Sending string from class to Form1

Although there are some similar questions I’m having difficulties finding an answer on how to receive data in my form from a class.
I have been trying to read about instantiation and its actually one of the few things that does make sense to me :) but if I were to instantiate my form, would I not have two form objects?
To simplify things, lets say I have a some data in Class1 and I would like to pass a string into a label on Form1. Is it legal to instantiate another form1? When trying to do so it looks like I can then access label1.Text but the label isn’t updating. The only thing I can think of is that the form needs to be redrawn or there is some threading issue that I’m unaware of.
Any insight you could provide would be greatly appreciated.
EDIT: Added some code to help
class Class1
{
public int number { get; set; }
public void Counter()
{
for (int i = 0; i < 10; i++)
{
number = i;
System.Threading.Thread.Sleep(5000);
}
}
}
and the form:
Class1 myClass = new Class1();
public Form1()
{
InitializeComponent();
label1.Text = myClass.number.ToString();
}
NO,
I think your form needs to access a reference to Class1 to use the data available in that, and not the other way around.
VERY seldomly a data class should instanciate a display form to display what it has to offer.
If the class is a data access layer of singleton, the form should reference that class, and not the other way around.
Based on the comments in your original post, here are a few ways to do it:
(this code is based on a Winform app in VS2010, there is 1 form, with a Label named "ValueLabel", a textbox named "NewValueTextBox", and a button.
Also, there is a class "MyClass" that represents the class with the changes that you want to publish.
Here is the code for the class. Note that I implement INotifyPropertyChanged and I have an event. You don't have to implement INotifyPropertyChanged, and I have an event that I raise whenever the property changes.
You should be able to run the form, type something into the textbox, click the button, and see the value of the label change.
public class MyClass: System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
public string AValue
{
get
{
return this._AValue;
}
set
{
if (value != this._AValue)
{
this._AValue = value;
this.OnPropertyChanged("AValue");
}
}
}
private string _AValue;
protected virtual void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
Example 1: simple databinding between the label and the value:
So this time, we will just bind the label to an instance of your class. We have a textbox and button that you can use to change the value of the property in MyClass. When it changes, the databinding will cause the label to be update automatically:
NOTE: Make sure to hook up Form1_Load as the load event for hte form, and UpdateValueButton_Click as the click handler for the button, or nothing will work!
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private MyClass TheClass;
private void Form1_Load(object sender, EventArgs e)
{
this.TheClass = new MyClass();
this.ValueLabel.DataBindings.Add("Text", this.TheClass, "AValue");
}
private void UpdateValueButton_Click(object sender, EventArgs e)
{
// simulate a modification to the value of the class
this.TheClass.AValue = this.NewValueTextBox.Text;
}
}
Example 2
Now, lets bind the value of class directly to the textbox. We've commented out the code in the button click hander, and we have bound both the textbox and the label to the object value. Now if you just tab away from the textbox, you will see your changes...
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private MyClass TheClass;
private void Form1_Load(object sender, EventArgs e)
{
this.TheClass = new MyClass();
// bind the Text property on the label to the AValue property on the object instance.
this.ValueLabel.DataBindings.Add("Text", this.TheClass, "AValue");
// bind the textbox to the same value...
this.NewValueTextBox.DataBindings.Add("Text", this.TheClass, "AValue");
}
private void UpdateValueButton_Click(object sender, EventArgs e)
{
//// simulate a modification to the value of the class
//this.TheClass.AValue = this.NewValueTextBox.Text;
}
Example 3
In this example, we won't use databinding at all. Instead, we will hook the property change event on MyClass and update manually.
Note, in real life, you might have a more specific event than Property changed -- you might have an AValue changed that is only raised when that property changes.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private MyClass TheClass;
private void Form1_Load(object sender, EventArgs e)
{
this.TheClass = new MyClass();
this.TheClass.PropertyChanged += this.TheClass_PropertyChanged;
}
void TheClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "AValue")
this.ValueLabel.Text = this.TheClass.AValue;
}
private void UpdateValueButton_Click(object sender, EventArgs e)
{
// simulate a modification to the value of the class
this.TheClass.AValue = this.NewValueTextBox.Text;
}
}
There's no reason why you can't have multiple instances of a form, but in this case you want to pass the Class1 instance to the form you have. The easiest way is to add a property to Form1 and have that update the label text:
public class Form1 : Form
{
public Class1 Data
{
set
{
this.label.Text = value.LabelText;
}
}
}
Events and delegates are what you want to use here. Since you tagged this as beginner, I will leave the implementation as an exercise :), but here is the general idea:
Declare an event/delegate pair in your data class.
Create a method in your form to bind to the event.
When "something" happens in your data class, fire the event
The form's method will be invoked to handle the event and can update widgets appropriately
Good luck.

Categories