This question already has answers here:
Virtual member call in a constructor
(18 answers)
Closed 9 years ago.
In my application I am running the same winform in different contexts to control visibility of buttons, enabeling of text fields and the winform header text.
The way I decided to do this is simply by passing a string to the form constructor and check it with a couple of if statements that in turn contain the desired winform tweaks.
if (formContext == "add")
{
Text = "Add member";
}
if (formContext == "edit")
{
Text = "Change role";
userTextBox.Enabled = false;
searchButton.Visible = false;
}
This works fine, however the "Text" keywords get a blue squigly line added by ReSharper with the following message: Viritual member call in constructor.
Is this a potential problem or just some sort of overly enthusiastic ReSharper message.
Any clarification or suggestions for improvement of my implementation would be much appreciated.
A virtual member call in the base class ctor could cause some logic to run in the subclass before the subclass' ctor is called (and thus before the object gets a chance to initialize itself to a consistent state).
It's just a nice reminder so you know you are doing something that could potentially cause some nasty unexpected behavior.
In addition to the existing answers, for forms you could add a Load event handler:
Load += delegate
{
if (formContext == "add")
{
Text = "Add member";
}
if (formContext == "edit")
{
Text = "Change role";
userTextBox.Enabled = false;
searchkButton.Visible = false;
}
};
Just seal your class.
I would suggest rewriting you class as follows:
public partial class Form1 : Form
{
public enum FormContextMode
{
Add,
Edit
}
private FormContextMode m_mode = FormContextMode.Add;
public Form1( FormContextMode mode )
{
InitializeComponent();
m_mode = mode;
Load += delegate { UpdateForm(); };
}
private void UpdateForm()
{
if( m_mode == FormContextMode.Add )
{
Text = "Add member";
}
else if( m_mode == FormContextMode.Edit )
{
Text = "Change role";
userTextBox.Enabled = false;
searchkButton.Visible = false;
}
}
}
Related
I am trying to remake my C#/WPF take on the Simon game by making it more clean and use classes now that I finally understand classes. Before I ask my question - here is some sample code from my old version as a reference.
public MainWindow()
{
InitializeComponent();
RedButton.IsEnabled = false;
BlueButton.IsEnabled = false;
GreenButton.IsEnabled = false;
YellowButton.IsEnabled = false;
}
//Use TextBlock clearing to say "Watch the pattern", "Your Turn" and then an empty box between games
private void StartButton_Click(object sender, RoutedEventArgs e)
{
_IsError = false;
RedButton.IsEnabled = true;
BlueButton.IsEnabled = true;
GreenButton.IsEnabled = true;
YellowButton.IsEnabled = true;
StartButton.IsEnabled = false;
if (randomPattern.Count == 0)
{
randomPattern.Add(random.Next(0, 4));
randomPattern.Add(random.Next(0, 4));
}
StatusBox.Text = "Watch the Pattern.";
ShowPattern();
}
So, my question is: Would it be good practice for me to use IsElement as a property/attribute of class "Buttons" instead of writing all of their statements out individually? And if I should add them to the class, I think my next question comes down to - how would I implement it into the class? I know simply typing only "IsEnabled = <T/F>" won't do anything.
EDIT:
To show you what I mean:
Should I have IsEnabled as a class property like such:
class Button {
IsEnabled = false;
}
var greenButton = new Button()
//Some code that lets the player start the game by clicking a button
greenButton.IsEnabled = true;
Or should I keep my IsEnabled statements outside of a class like in my old version of the code? Note - my old version of the code, there are no classes that I made - only functions.
IsEnabled should be placed into Button class as it is property or feature of button.
public class Button {
public IsEnabled { get; set; }
}
The above class has high cohesion as it is focused on what it should be doing. It has only methods/properties, fields relating to the intention of the class.
Wierd behaviour when passing values to and from second form.
ParameterForm pf = new ParameterForm(testString);
works
ParameterForm pf = new ParameterForm();
pf.testString="test";
doesn't (testString defined as public string)
maybe i'm missing something? Anyway I'd like to make 2nd variant work properly, as for now - it returns null object reference error.
Thanks for help.
Posting more code here:
calling
Button ParametersButton = new Button();
ParametersButton.Click += delegate
{
ParameterForm pf = new ParameterForm(doc.GetElementById(ParametersButton.Tag.ToString()));
pf.ShowDialog(this);
pf.test = "test";
pf.Submit += new ParameterForm.ParameterSubmitResult(pf_Submit);
};
definition and use
public partial class ParameterForm : Form
{
public string test;
public XmlElement node;
public delegate void ParameterSubmitResult(object sender, XmlElement e);
public event ParameterSubmitResult Submit;
public void SubmitButton_Click(object sender, EventArgs e)
{
Submit(this,this.node);
Debug.WriteLine(test);
}
}
result:
Submit - null object reference
test - null object reference
pf.ShowDialog(this); is a blocking call, so pf.Submit += new ParameterForm.ParameterSubmitResult(pf_Submit); is never reached: switch the order.
Submit(this,this.node); throws a null object reference because no event is assigned to it (see above). Generally, you should always check first: if (Submit != null) Submit(this,this.node);
You should change ``pf.ShowDialog(this);topf.Show(this);` so that your main form isn't disabled while your dialog box is open, if that's what you want, or use the model below (typical for dialog boxes.)
I'm not sure what pf_Submit is supposed to do, so this might not be the best way to go about it in your application, but it's how general "Proceed? Yes/No" questions work.
Button ParametersButton = new Button();
ParametersButton.Click += delegate
{
ParameterForm pf = new ParameterForm(testString);
pf.ShowDialog(this); // Blocks until user submits
// Do whatever pf_Submit did here.
};
public partial class ParameterForm : Form
{
public string test; // Generally, encapsulate these
public XmlElement node; // in properties
public void SubmitButton_Click(object sender, EventArgs e)
{
Debug.WriteLine(test);
this.Close(); // Returns from ShowDialog()
}
}
When you want to use your second variant, you have to use a getString()-Method, where you can put the e.g. "testString". The way you wrote it, "testString" should be a method (and got brackets).
EDIT (a bit more precise):
You could write:
pf.getString(testString);
, if "pf" is an instance of your own class, otherwise you had to look up, whether you can retrieve a String in this class.
the thing was in line order :)
pf.Submit += new ParameterForm.ParameterSubmitResult(pf_Submit);
and
pf.Test = "test";
should have been set before
pf.ShowDialog(this);
my mistake thingking that parameter can be passed after 2nd form was displayed
thnx for answers
I am trying to store the state of my program, in particular whether it's in online/offline mode as well as what type of search was last run. Based on those two factors I want to modify parts of my program (e.g. the GUI).
To me it makes the most sense to store these states as enums. The setter can then call a method when the values get changed.
Edit: Fixed my solution based on the answers I got from #Icemanind and #Rahul:
namespace Foo
{
public partial class MainWindow : Window
{
// define enum:
public enum OnlineState
{
Online,
Offline
};
// variable that the OnlineState is stored in:
public OnlineState appOnlineState;
// custom setter/getter:
public OnlineState AppOnlineState
{
get
{
return appOnlineState;
}
set
{
OnlineStateValueChanged(value);
appOnlineState = value;
}
}
// ValueChanged-method:
void OnlineStateValueChanged(OnlineState OS)
{
if (OS == OnlineState.Online)
{
// do stuff here
// I'm leaving some of my code as an example
if (Settings.Default.ColorblindMode == true)
{
Label_Status.Foreground = Brushes.Blue;
StatusBar_Grid.Background = Brushes.LightBlue;
}
else if (Settings.Default.ColorblindMode == false)
{
Label_Status.Foreground = Brushes.Green;
StatusBar_Grid.Background = Brushes.Green;
}
Label_Status.Content = "Online";
// activate certain MenuItems
ActivateMenuItem(Menu_Intranet);
ActivateMenuItem(Menu_SAP);
}
else if (OS == OnlineState.Offline)
{
// do stuff here
// I'm leaving some of my code as an example
if (Settings.Default.ColorblindMode == true)
{
Label_Status.Foreground = Brushes.Yellow;
StatusBar_Grid.Background = Brushes.Yellow;
}
else if (Settings.Default.ColorblindMode == false)
{
Label_Status.Foreground = Brushes.Red;
StatusBar_Grid.Background = Brushes.Red;
}
Label_Status.Content = "Offline";
// deactivate MenuItems
DeactivateMenuItem(Menu_Intranet);
DeactivateMenuItem(Menu_SAP);
}
else
{
// this should not happen
MessageBox.Show("There has been a problem with the online/offline check.","Warning",MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
}
}
Now I can set the OnlineState via AppOnlineState = OnlineState.Online; and AppOnlineState = OnlineState.Offline;.
To check ("get") the OnlineState I can check the value of AppOnlineState which is going to return Online or Offline.
Your issue is because OnlineState really isn't part of State. However OS is a class that is part of State and OnlineState is part of OS. I think this is what you want:
State.OS.AppOnlineState = State.OnlineState.Online;
Also, as a side note, I don't know what it is you're creating, but usually on/off states are part of the instance, not static. Think of it as a real world thing. When I turn my computer off, it doesn't shut off every computer in the world. It only shuts off my computer. My instance of a computer.
The below line
static State.OnlineState _os = new State.OnlineState();
Should be
State.OnlineState _os = State.OnlineState.online;
After hunting the internet for two days and not finding a solution that I can understand properly I have to ask on here for an answer.
I have a windows forms application that was written in vb.net and works fine. I have decided to rewrite this in c# which I thought wouldn't be too much of a problem but ...
I have two classes in the project :
FormJobs & AppJobs
FormJobs contains methods and functions that modify the forms in some way.
AppJobs contains methods and functions for everything else (Checks,Scanning and so forth).
On my main form (FrmStart) the On_load event uses a function from AppJobs to check that the network is up (public bool CheckNetConnection) and then Checks to make sure that the root save folder exists (public void CheckRoot).
If CheckNetConnection is false or CheckRoot does not exist then a method in the FormJobs class sets some buttons to disabled, some labels to display information as to what has gone wrong and also sets the height of the form.
The above works in VB.net but I keep getting a StackOverflowException or NullReferenceException with the C# code.
I know the reason for the Exceptions is because the two classes and the form all keep calling each other so I know that I need to remove this code but I am not sure how to let each class and the form access each other. It is obviously bad design as I`m just starting to learn C# so any help on this would be much appreciated.
But my main questions are:-How do I get a form to access multiple classes?
Allow the classes to access each other?
Let the classes make changes to a form?
FrmStart Code
AppJobs Appjobs = new AppJobs();
private void FrmStart_Load(object sender, EventArgs e)
{
KeyPreview = true;
if (Appjobs.CheckNetConnection(this) == true)
{
Appjobs.CheckRoot(this);
}
AppJobs Code
public class AppJobs
{
FormJobs Formjobs = new FormJobs();
public string AppRoot = Properties.Settings.Default.DefaultFolder;
public string DefaultDevice = Properties.Settings.Default.DefaultScanner;
public bool NoDirectory = false;
DialogResult MsgBoxQuestion;
public bool CheckNetConnection(Form StartForm)
{
IPHostEntry ServerIP = new IPHostEntry();
bool ConnectedToServer = false;
string CurrentRoot = "MyServer";
if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
{
try
{
IPHostEntry DNSTest = Dns.GetHostEntry(CurrentRoot);
if (DNSTest.AddressList.Length > 0)
{
ConnectedToServer = true;
}
else
{
ConnectedToServer = false;
}
}
catch (System.Net.Sockets.SocketException ex)
{
ConnectedToServer = false;
}
}
return ConnectedToServer;
}
public void CheckRoot(Form StartForm)
{
if (string.IsNullOrEmpty(AppRoot))
{
Formjobs.SetHeight(StartForm);
return;
}else if(AppRoot == "0")
{
Formjobs.SetHeight(StartForm);
return;
}
else
{
if ((!Directory.Exists(AppRoot)))
{
NoDirectory = true;
MsgBoxQuestion = MessageBox.Show(AppRoot + " is set, but the directory does not exist." + Environment.NewLine
+ Environment.NewLine + "Would you like to create the folder now?", "Root folder missing", MessageBoxButtons.YesNo);
if (MsgBoxQuestion == DialogResult.Yes)
{
Directory.CreateDirectory(AppRoot);
NoDirectory = false;
}
else
{
MessageBox.Show("You will not be able to use this program until you create a root folder.", "No root folder selected",MessageBoxButtons.OK);
}
}
}
}
}
FormJobs Code
public class FormJobs
{
AppJobs Appjobs = new AppJobs();
public void SetHeight(Form StartForm)
{
if (Appjobs.AppRoot == null | Appjobs.AppRoot == "0") {
if (Appjobs.DefaultDevice == null | Appjobs.DefaultDevice == "0") {
if (StartForm.Controls["MenuStrip1"].Visible == true) {
StartForm.Height = 167;
StartForm.Controls["LblNoRoot"].Visible = true;
StartForm.Controls["LblNoRoot"].Location = new Point(0, 24);
StartForm.Controls["LblNoRoot"].Text = "There is no root folder selected. Please select a root folder to continue.";
StartForm.Controls["LblNoDevice"].Visible = true;
StartForm.Controls["LblNoDevice"].Location = new Point(0, 48);
StartForm.Controls["LblNoDevice"].Text = "There is no default device selected. Please select a default device to continue.";
StartForm.Controls["BtnOkTickets"].Enabled = false;
StartForm.Controls["BtnQueryTickets"].Enabled = false;
StartForm.Controls["BtnSearch"].Enabled = false;
}else
{
StartForm.Height = 147;
StartForm.Controls["LblNoRoot"].Visible = true;
StartForm.Controls["LblNoRoot"].Location = new Point(0, 9);
StartForm.Controls["LblNoRoot"].Text = "There is no root folder selected. Please select a root folder to continue.";
StartForm.Controls["LblNoDevice"].Visible = true;
StartForm.Controls["LblNoDevice"].Location = new Point(0, 33);
StartForm.Controls["LblNoDevice"].Text = "There is no default device selected. Please select a default device to continue.";
StartForm.Controls["BtnOkTickets"].Enabled = false;
StartForm.Controls["BtnQueryTickets"].Enabled = false;
StartForm.Controls["BtnSearch"].Enabled = false;
}
}
One of the causes of your problems is that everyone is changing your StartForm. Apart from that this spaghetti makes it difficult to understand, it certainly doesn't help to make your classes reusable and maintainable if your Startform changes.
It seems to me, that AppJobs is meant to decide what the form should look like (for instance it decides that the StartForm should change height), while FormJobs performs the calculations needed to change this height. StartForm apparently is just allowing to let everyone make changes to him.
A better design would be that StartForm would not ask AppJobs to change its size, and to ask the operator whether a folder should be generated. Instead if ought to ask appJobs for advise: "Which height do you think I should have". after that it could ask FormJobs: "Please adjust my height according to this specification"
FormJobs should trust StartForm that it has gathered the correct information about how a StartForm ought to look like. FormJobs should not ask AppJobs for any information: "Hey AppJobs, StartForm asked me to change its appearance to certain specifications, but I'm not certain whether StartForm has done its job correctly. Please tell me if these specifications are correct, and give me some missing information")
The correct division into tasks would be:
AppJobs specifies the format of any StartForm according to its internal state (a.o. AppRoot, and existence of certain folders)
StartForm is the one who displays all items. He decides who to ask for specifications, and what to do with the returned specifications. He is also the only one who communicates with operators
FormJobs is a class that apparently knows all elements from a StartForm. If you will only have one type of StartForm, then Appjobs should be part of the Startform class. If you think there might be several different Startform classes, all with the same elements that ought to be manipulated similarly, shouldn't all these StartForm classes be derived from a FormJobs class?
Anyway, redesign without everyone causing to manipulate StartForm
Apparently there are a limited number of StartForm layouts depending on AppRoot, defaultDevice etc. You seem to be missing some "else" after your if, so this list might not be accurate. Still you will get the idea:
enum StartFormLayouts
{
DefaultDevice0,
AppRoot0,
Other,
}
// class that specifies the layout of any startform
class AppJobs
{
private bool IsAppRoot0
{
get{return Appjobs.AppRoot == null || Appjobs.AppRoot == "0";}
}
private bool IsDefaultDevice0
{
get{return Appjobs.DefaultDevice == null || Appjobs.DefaultDevice == "0";}
}
public StartFormLayoug GetPreferredLayout()
{
if (this.IsAppRoot0)
{
if (this.IsDefaultDevice)
{
return StartFormLayout.DefaultDevice0;
}
else
return StartFormLayout.AppRoot0;
}
else
{
return StartFormLayout.Other;
}
}
public bool ShouldAskDirectoryCreation()
{
return (!this.IsAppRoot0 && !Directory.Exists(AppRoot));
}
}
Note that this class doesn't need StartForm, nor AppJobs. It could work with any class that wants to know if it should ask for DirectoryCreation. Since it also does not speak any language, even a Chinese StartForm could use it. After all, the StartForm is the only one who knows what language it speaks and what to do if a certain layout is requested.
Furthermore, did you notice that I used a double || to use a Boolean OR instead of a bitwise or?
And I use statements like if (a) instead of if(a=true) a C# Boolean is a real Boolean, in contradiction to Booleans in C and C++.
The class of all kinds of forms that should be able to be layout according to the requested layout contains the functions similar to your
It depends a bit of whether you decide to let it be a base class of StartForm or StartForm itself. If you want it to handle every form class that has the required controls, consider of using an interface:
public Interface IStartForm
{
public int Height {get; set;}
public Label LabelNoRoot {get;}
public Label LabelNoDevice {get; }
public Button BtnTickets {get;}
...
This way you can set the size of any form that has these labels and buttons, even if they have different names than those strings you use.
But again: if you ever only want to size StartForm, then this should be a function in StartForm.
public SetHeight(StartFormLayout layout, IStartForm startForm)
{
switch (layout)
{
case StartFormLayout.DefaultDevice0:
if (startForm.MenuStrip.Visible)
{
startForm.Height = ...;
startForm.LabelNoRoot.Location = ...
// etc
}
else
{
...
Noticed that because of this separation of concerns the AppJobs and FormJobs don't have to know each other. AppJobs and FormJobs also don't really have to know what 'StartForm` is, only that it has the labels and buttons etc that it needs to change.
class StartForm : Form, IStartForm
{
public Label LabelNoRoot {get{return this.label1; } }
...
private void FrmStart_Load(object sender, EventArgs e)
{
AppJobs layoutdesigner = new AppJobs(...);
StartFormLayout layoutdesigner = layouter.GetPreferredLayout();
FormJobs layouter = new FormJobjs();
layouter.SetHeight(this)
}
Noticed that my form didn't have a label named "LabelNoRoot", but a Label1 that should function as a LabelNoRoot. Also: because I used types instead of string, You can be certain that I can't handle a label as if it was a button. I can't by accident try to press the label. Something that could easily been done when you were using strings to identify the items you want to layout.
Extending the comments: you just remove the new part in your FormJobs and AppJobs classes.
leaving the code in i.e. in the FormJobs class like : AppJobs appObj;
Then in your main form create at some point a FormJobs obj and an AppJobs obj and set its property.
I.e. in main Form:
AppJobs appObj = new AppJobs();
FormJobs formObj = new FormJobs();
formObj.appObj = appObj;
Tho I must say i dont like that approach you are taking with this...
You should think of another way or at least refactor your code that FormJobs does not need AppJobs methods and vice versa in a way that all calls to FormJobs and AppJobs come from your main form.
Say I have a list called listOfFruits in my main form. In a second form I've made I want the user to be able to remove items from that list to a second list called removedFruits. Currently I know I can access these lists in my second form simply passing them as parameters in the form constructor. However c# can't do pointers (correct?) so how can I effect the main form's copy of these lists from my second form? Because currently any changes to those lists in my second form don't effect the main form's original copy of the lists. If I were to remove 5 fruits from the listOfFruits passed to my second form then after finishing my work the main form would still still have a full listOfFruits and an empty removedFruits. Is there a simple fix to this? Maybe a get/set or a way to add/remove items from the original lists from the second form? Maybe the answer is in some sort of accessor stuff?
EDIT: To clarify; I want to add to one list, and remove from another. Not add/remove to the same list. Not sure if this matters entirely but I figured I'd be specific here in case it does.
EDIT2: I think the issue is I'm copying the original list from the first form and not editing it directly. Can someone fix my code so I can access the original list from my second form instead of making a copy of the list?
public partial class ListSelector : Form
{
private string windowName = Form1.typeOfModuleAdded;
public List<IOModule> innerIOList;
IOModule cardAdded = null;
public ListSelector(List<IOModule> cardList)
{
this.Text = windowName;
innerIOList = cardList;
InitializeComponent();
InitializeList();
}
private void InitializeList()
{
if (windowName == "Drive")
{
string[] listDrives = { "ACS880", "test" };
listBox1.Items.AddRange(listDrives);
}
else if (windowName == "IOBlock")
{
if (!innerIOList.Any())
{
MessageBox.Show("No cards loaded! Please import cards from IO List.", "Error Empty Data", MessageBoxButtons.OK, MessageBoxIcon.Error);
this.Close();
}
foreach (IOModule card in innerIOList)
{
cardAdded = card;
listBox1.Items.Add(card.name);
}
}
else if (windowName == "Local Card")
{
string[] listLocals = { "1756-EN2T", "test" };
listBox1.Items.AddRange(listLocals);
}
else if (windowName == "Processor")
{
string[] listProcessors = { "1756-L71S", "test" };
listBox1.Items.AddRange(listProcessors);
}
}
private void addBtn_Click(object sender, EventArgs e)
{
if (listBox1.SelectedItem != null)
{
Form1.SetModule(listBox1.SelectedItem.ToString());
Form1.confirmedAdd = true;
this.Close();
}
else if (cardAdded != null)
{
innerIOList.Remove(cardAdded);
}
else
{
MessageBox.Show("No module selected!");
}
}
and here's how I pass the list to that form from my first form:
ListSelector test = new ListSelector(ioList);
test.ShowDialog();
where ListSelector is the name of my second form, and ioList is the list im passing to it.
EDIT3: added more code
"However c# can't do pointers (correct?) so how can I effect the main form's copy of these lists from my second form?"
No, not correct. Any object reference (for instance, of a List<Fruit>) is still very much a pointer to a place in memory, and if you pass the same List<Fruit> object to both Forms, they share the same List.
I don't know why your changes to your listOfFruits don't chow up in your first Form. I would check the following things:
Are you 100% sure you use the same List<Fruit> object in both Forms. (If you create a new List like this: new List<Fruit>(listOfFruits) it is NOT the same List)
Does the first Form have any way of finding out, that the List has changed? Possible using a Timer with recurring checks, or (my favorite) triggering an event when you change something, and subscribe an EventHandler in the first Form to the event.
I assume that you have created a second list in your second form that is filled with the items of the first form's list. Then changes on the second list aren't reflected in the first list. You have to use the same reference of the list.
public Form2(List<Fruit> listOfFruits)
{
this._listOfFruits = listOfFruits;
}
private List<Fruit> _listOfFruits;
Instead using a public field, try to use property and on creating your new ListSelector pass the list to the property.
public partial class ListSelector : Form
{
private string windowName = Form1.typeOfModuleAdded;
private List<IOModule> innerIOList;
IOModule cardAdded = null;
public List<IOModule> CardList
{
get
{
return innerIOList;
}
set
{
innerIOList = value;
InitializeList();
}
}
public ListSelector()
{
this.Text = windowName;
InitializeComponent();
}
When creating your new ListSelector object
ListSelector ls = new ListSelector();
ls.CardList = your mainform list of IOModule here
ls.ShowDialog();