First ever post in here, if I made any mistake in the post pls tell me so I can fix it
So I'm trying to make classes to handle most of the code, one of the things I wanted to do was to have one handle all the opening and making of form instances. Doing a .Show(); on instances that already exist was simple as I know the order they are created I can just Form _form = Application.OpenForms[i]; to grab the instance, but when it's not already created I couldn't find a way to deal with it, I read a bit unto it but couldn't find something that really fit what I wanted to do, something something about reflection seemed to be the right path but couldn't get it to work, so some light in the matter would be very appreciated.
In a nutshell I'm trying to make something like:
(I know something similar is not possible but I think this is the easiest way to explain exactly what I seek. A workaround I made was to have the code to generate each of the Forms into a switch and just send their number, so it's what I'm gonna use if I can't find a better solution, but I wanted to learn a "proper/cleaner" way of achieving this)
static public bool MakeForm(string name)
{
name _name = new name();
_name.Show();
}
[Edit: I realized that this is irrelevant for my project cuz I can just ready up every single Form on login, but I still hope to know how to do this if any of yall can show me how to/where to read]
I am not sure If I understand you correctly, but I think you need a factory class.
public static class Factory
{
public static Form Create(string name)
{
switch (name)
{
case "FormA":
return new FormA();
case "FormB":
return new FormB();
}
}
}
Than you can create your forms by name.
Factory.Create("FormA").Show();
Here's a simple example using the Reflection approach:
private void button1_Click(object sender, EventArgs e)
{
Form f2 = TryGetFormByName("Form2");
if (f2 != null)
{
f2.Show();
}
}
public Form TryGetFormByName(string formName)
{
var formType = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(T => (T.BaseType == typeof(Form)) && (T.Name == formName))
.FirstOrDefault();
return formType == null ? null : (Form)Activator.CreateInstance(formType);
}
Here's an alternate version that checks to see if the form is already open:
public Form TryGetFormByName(string formName)
{
// See if it's already open:
foreach (Form frm in Application.OpenForms)
{
if (frm.Name == formName)
{
return frm;
}
}
// It's not, so attempt to create one:
var formType = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Where(T => (T.BaseType == typeof(Form)) && (T.Name == formName))
.FirstOrDefault();
return formType == null ? null : (Form)Activator.CreateInstance(formType);
}
Related
I have a method that returns an ItemCollection, I want to stop the function early if there is no information provided. I would normally do this with a return; however as my method expects an ItemCollection it fails. I have this as a work around but it seems frankly hacky. Is there something I am missing. I tried just return; and I would prefer not throw an exception.
private ItemCollection loadLeft_Click(object sender, RoutedEventArgs e)
{
var leftUser = UsrLeft.Text;
if (leftUser == "")
{
MessageBox.Show("No User Entered");
GroupListLeft.Items.Add("");
var fail = GroupListLeft.Items;
return fail;
}
//Succesful test do stuff
var leftItems = GroupListLeft.Items;
return leftItems;
}
You have few options:
throw a new Exception (maybe even a custom one like NoUserEnteredException("someText")).
return null
return an empty collection or dummy object (see Null-object pattern)
The last one is better choice, in this case you don't need write a null-check or a try-catch section in client code.
You will need to return something that equates to an ItemCollection, depending on how you may want the calling procedures to handle such a return you could use a null return:
if (leftUser == "")
{
MessageBox.Show("No User Entered");
return null;
}
Or return a new ItemCollection e.g.
if (leftUser == "")
{
MessageBox.Show("No User Entered");
return new ItemCollection();
}
You will need to return null, like
private ItemCollection loadLeft_Click(object sender, RoutedEventArgs e)
{
var leftUser = UsrLeft.Text;
if (leftUser == "")
{
MessageBox.Show("No User Entered");
return null;
}
//Succesful test do stuff
var leftItems = GroupListLeft.Items;
return leftItems;
}
You can use "return null" which I do from time to time, but its not a good coding convention. Another option is to throw an exception and then catch it. Both is bad coding conventions and makes the code that uses this button add some rather obscure logic and dependencies.
From studying SOLID principles I would argue a better solution is to make a new type of object to return. f.ex. one that holds a list, but also a status message and then make it react a bit like when you have an HTTP request that depends on a success and otherwise cannot expect to have any content. This way, the object makes it clearer what to expect.
A fourth option, since this sounds UI related, is to possible have the click callback somehow and either update the collection, or update with an error message directly. But this might also be a bad coding convention that has impractical dependencies in the code.
Your code seems to return an object when you click on something.
(I am no expert in WPF, i don't know if this is possible).
I would encapsulate the function that returns the ItemCollection in a separate function and check whether the User is valid BEFORE calling this function.
This way you ensure that the ItemCollection is always valid (because you don't even try to retrieve it with an invalid user). Something like this:
private void loadLeft_Click(object sender, RoutedEventArgs e) {
var leftUser = UsrLeft.Text;
if(leftUser != "") {
ItemCollection coll = getItemCollectionForUser(leftUser);
}else {
//Error Handling
}
}
private ItemCollection getItemCollectionForUser(string user) {
//return ItemCollection here
}
Note how I wrote a separate function that returns the ItemCollection and the Click function returns nothing.
I have a Order form. Once a order is complete, I use a thread to email the order to the supplier. The Thread is use to prevent the system hanging while the order is exported to pdf and sent.
The Problem: I would like to place an message on the MDIParent Toolstripstatuslabel once the threat completes without error to confirm the order was sent. But I get an error: "System.NullReferenceException: Object reference not set to an instance of an object". Which I could be wrong, refers to the Child windows disposed the toolstripstatuslabel reference on the parent form when it closed, so the threat cant access it anymore. I know the easy solution would be to use a MessageBox to confirm all went good and well...but why make it easy if you can do it elegant?
So my question: How can I reference a control in the parent form from the threat? I tried looking at invoke, but not sure how to implement it or if it is actually the correct direction.
EDIT:
My code from the childform
public partial class frm_n_order : Form
{
.
.
private void bProcess_Click(object sender, EventArgs e)
{
.
.
.
new Thread(new ThreadStart(delegate
{
fExportOrder(strOrderNo);
fSendMailv2(strPlant, strSupCode, strOrderNo);
})).Start();
this.close();
}
private void fExportOrder(string strOrderNo)
{
//export order to pdf
}
private void fSendMailv2(string strPlant, string strSupCode, string strOrderNo);
{
// get pdf
// get email address
try
{
// send email
((MDIParent1)MdiParent).tsslMain.Text = "Order No:" + strOrderNo + " was successfully send to " + strEmails; //here I need to find a different way of accessing the Toolstripstatuslabel in the parent form
}
catch
{
MessageBox.Show("Email did not send");
}
}
}
EDIT:
Ok, so after spending more than a day trying to figure out how to use Invoke, I realize while it seems like good practice when working with threads, its not my answer. My problem is directly related to the childform closing disposing all the controls so it looses its reference to the MdiParent. To solve the problem I did the following:
In my child class I added:
public static Form IsFormAlreadyOpen(Type FormType)
{
foreach (Form OpenForm in Application.OpenForms)
{
if (OpenForm.GetType() == FormType)
return OpenForm;
}
return null;
}
I dont think it is the most elegant solution but the theory is that my Parent form will always be open when I need to access the Toolstripstatuslabel. So I basically loop through all the open forms to find the reference to the active MdiParent instance which then gets passed back to the caller. In the thread I then use the following code.
MDIParent1 fm = null;
if ((fm = (MDIParent1)IsFormAlreadyOpen(typeof(MDIParent1))) != null)
{
fm.Toolstripstatuslabel1.Text = "Order No:" + strOrderNo + " was successfully send to " + strEmails;
}
I'm still looking for a better approach, but for now this works.
Ok, so after spending more than a day trying to figure out how to use Invoke, I realize while it seems like good practice when working with threads, its not my answer. My problem is directly related to the childform closing disposing all the controls so it looses its reference to the MdiParent. To solve the problem I did the following:
In my child class I added:
public static Form IsFormAlreadyOpen(Type FormType)
{
foreach (Form OpenForm in Application.OpenForms)
{
if (OpenForm.GetType() == FormType)
return OpenForm;
}
return null;
}
I dont think it is the most elegant solution but the theory is that my Parent form will always be open when I need to access the Toolstripstatuslabel. So I basically loop through all the open forms to find the reference to the active MdiParent instance which then gets passed back to the caller. In the thread I then use the following code.
MDIParent1 fm = null;
if ((fm = (MDIParent1)IsFormAlreadyOpen(typeof(MDIParent1))) != null)
{
fm.Toolstripstatuslabel1.Text = "Order No:" + strOrderNo + " was successfully send to " + strEmails;
}
I'm still looking for a better approach, but for now this works.
It's hard for me to overlook someone saying "but why make it easy if you can do it elegant?"
Awesome!
I figure if we can do something elegantly, then in the future it should be easy.... right?
Anyways, hoping you find the below helpful.
A note: It looked to me like you were declaring your thread as a local variable and not storing it outside the local scope. If we want something to live past the end of the scope, it's a good idea to store a reference to it (which is done using a private Task field in the below example).
Sure, the thread would get added to the threadpool and stored somewhere in the framework even if it's just a local variable, so I think it wouldn't abort due to garbage collection as you have it, but I don't like the idea of instances floating around that I don't have references to.
public class MyChildForm : Form
{
private Task longRunningTask;
private Task closeTask;
public string ResultOfTimeConsumingOperation { get; private set; }
protected override Dispose(bool disposing)
{
if (disposing)
{
longRunningTask?.Dispose();
closeTask?.Dispose();
}
base.Dispose(disposing);
}
private void TimeConsumingOperation1()
{
Thread.Sleep(TimeSpan.FromSeconds(8));
ResultOfTimeConsumingOperation = "Hooray we finished the work lol";
this.closeTask =
Task.Factory.FromAsync(
BeginInvoke(new Action(Close)),
EndInvoke);
}
protected override void OnLoad()
{
base.OnLoad();
this.longRunningTask =
Task.Run(TimeConsumingOperation1);
}
}
public class MyParentForm : Form
{
private List<Form> childForms;
public MyParentForm() : base()
{
childForms = new List<Form>();
}
protected override void OnLoad()
{
base.OnLoad();
RunChildForm();
}
private void RunChildForm()
{
var childForm = new MyChildForm();
childForms = childForms.Append(childForm).ToList();
childForm.FormClosing += ChidlForm_FormClosing;
childForm.Show();
}
private void ChildForm_FormClosing(object sender, FormClosingEventArgs e)
{
var childForm = sender as MyChildForm;
childForm.FormClosing -= ChildForm_FormClosing;
if (childForms.Contains(childForm))
childForms =
childForms.
Except(new Form[] { childForm }).
ToList();
// tada
myStatusLabel.Text = childForm.ResultOfLongRunningProcess;
}
// main window is closing
protected override void OnFormClosing(FormClosingEventArgs e)
{
// let's close any windows we left open
var localForms = childForms.ToList();
childForms = new List<Form>();
foreach (var form in localForms)
form.Close();
}
}
i have treeview which each node tag contain form name , when i click on node i open the form
my code is as follows
private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
NodeClick(Convert.ToString(e.Node.Tag));
}
public void NodeClick(string formName)
{
switch (formName)
{
case "frmPartMaster":
frmPartMaster partMaster = null;
if ((partMaster =
(frmPartMaster)Globale.IsFormAlreadyOpen(typeof(frmPartMaster)))
== null)
{
partMaster = new frmPartMaster();
partMaster.Show(this);
}
else
{
partMaster.Activate();
partMaster.WindowState = FormWindowState.Normal;
partMaster.BringToFront();
}
break;
}
}
this code is working fine but i have 1000's of form , for each for form i have to right case the code.
is it possible if i passed the form it open it open like in single case?
You can create instance of a form class by the call of Activator.CreateInstance
public void OpenOrActivateForm(string formType)
{
var formType = Type.GetType(formType);
var form = Globale.IsFormAlreadyOpen(formType);
if(form == null)
{
form = Activator.CreateInstance(formType);
from.Show(this);
}
else
{
form.Activate();
form.WindowState = FormWindowState.Normal;
form.BringToFront();
}
}
Why don't you just put a reference to a form in the Node's Tag and then use that directly
private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
NodeClick(e.Node.Tag as Form);
}
public void NodeClick(Form theForm)
{
if(theForm == null) return;
if(theForm.Visible == false)
{
theForm .Show(this);
}
theForm .Activate();
theForm .WindowState = FormWindowState.Normal;
theForm .BringToFront();
}
You should be able to add the form to the node, the node should have a field, tag i think it is that is of type object. Add your form there then extract it from the tag. That way you will not have to use a case statement but 1 single statement that works for all forms.
Instead of using switch case for every form, you can use Activator.CreateInstance method.
Refer this MSDN Article.
You can store Fully Qualified Name in the tag and use it to instantiate appropriate form.
You could use this approach:
Define a Dictionary of strings and actions like this
Dictionary<string, Action> dic = new Dictionary<string,Action>();
dic.Add("frmPartMaster", OpenPartMaster);
.....
add the appropriate action
private void OpenPartMaster()
{
frmPartMaster partMaster = null;
if ((partMaster =
(frmPartMaster)Globale.IsFormAlreadyOpen(typeof(frmPartMaster)))
== null)
{
partMaster = new frmPartMaster();
partMaster.Show(this);
}
else
{
partMaster.Activate();
partMaster.WindowState = FormWindowState.Normal;
partMaster.BringToFront();
}
}
and when you need to call that form instead of an infinite switch use
dic[formName].Invoke();
in this way you have a centralized point where you add the specific action to execute when a particular form is requested and you keep all the functionality already written.
Of course you need to refactor the switch cases in separate methods.
This approach is interesting if you have different actions (cases) for your form and not a always the same sequence of repeating code.
I have forms in my application that display a DataGrid bound to a BindingSource. When I double-click on a row I display a detail form.
I have almost 50 of these "browse" forms with their accompanying detail form. I would like to reduce the number of forms (and code) by creating a base browse form and passing the appropriate BindingSource to it. I have this working. However, when I double-click on a row, I want to instantiate the detail form related to the passed BindingSource.
For example, If the user is browsing the Customer table, then a Customer Detail form will be opened, but if he is browsing the Job table, then the Job Detail form will open.
I can pass a generic detail form as a parameter, but how do I cast that passed form to one of the correct type so I can instantiate it?
(I am trying to avoid a large switch statement that cycles through to select the correct passed form. Surely there is a more elegant way to achieve this?)
[EDIT]
My browse form code is instantiated thus:
public BaseBrowse(BindingSource dataClass, DevExpress.XtraEditors.XtraForm crudForm)
{
InitializeComponent();
bs = dataClass;
crud = ((CRUDEquipment)(crudForm));
}
My double-click event is thus:
private void gvw_DoubleClick(object sender, EventArgs e)
{
Int32 nID = Convert.ToInt32(gvw.GetFocusedRowCellValue("ID"));
((CRUDEquipment)(crud)).intID = nID;
((CRUDEquipment)(crud)).Show();
}
I need a way to replace the "CRUDEquipment" with whatever form I need.
Maybe one of these will help
public void Instantiate<T>()
{
var myObject = Activator.CreateInstance<T>();
// Do something with myObject
}
public void Instantiate(Type t)
{
var myObject = Activator.CreateInstance(t);
// Do something with myObject
}
public void Instantiate(string typeName)
{
var detailType = Type.GetType(typeName);
if (detailType == null)
{
throw new InvalidOperationException("Nice try, but type {0} doesn't compute :)");
}
var myObject = Activator.CreateInstance(detailType);
// Do something with myObject
}
I use the following code to Invoke and access properties on my from from a different thread.
public static void PFA(Action<frmain> action)
{
var form = Form.ActiveForm as frmain;
if (form != null)
{
form.PerformAction(action);
}
}
public void PerformAction(Action<frmain> action)
{
if (InvokeRequired)
Invoke(action, this);
else
action(this);
}
My Question:
If I call PFA(form => form.Richbox1.Text = "Test") - I want PFA() to check if the action is (Richbox1.Text) and if so then Add "\n" to the text("Test").
The Idea is to call
PFA(form => form.Richbox1.Text = "Test");
instead of
PFA(form => form.Richbox1.Text = "Test\n");
In other words I want to add a new line automaticly if the action is "Richbox1.Text ="
How I do that? I never worked with actions before.
What if you did:
PFA(form => UpdateTextBox(form.Richbox1,"Test"));
public void UpdateTextBox(RichTextBox box,string text)
{
if (box.Name=="Richbox1")
{
text+="\n";
}
box.AppendText(text);
}
Your not going to be able to look inside the action and determine it's using a specific cotnrol from outside of the action. So either you need to add the \n before you call PFA, or have PFA call a function which will do what you want, or have PFA implement the logic.
You can add an extension method to do the trick. Try the following.
public static void AddNewLineIfMatch(this RichTextBox rtb, string toMatch) {
if ( rtb.Text == toMatch ) {
rtb.AppendText("\n");
}
}
PFDA(() => form.Richbox1.AddNewLineIfMatch("Test"));
EDIT Used the AppendText method instead of Text+=
You can't do this the way you want. PFA function can not check what's is inside your delegate. Think how you can solve your task another way.
[offtopic]
PS. Also, your naming convention is not very good. "PFA" doesn't explain what the function does and as for "frmain" - usually class names start with capital letter.
[/offtopic]
UPDATE:
I would do it a little bit better, than Josh offered:
PFA(form => SetControlText(form.Richbox1, "Test"));
public void SetControlText(Control control, string text)
{
control.Text = text;
// choose control type for which you want to add new line
if(control is RichTextbox || control is TextBox || control is ... )
control.Text += Environment.NewLine;
}
You could accomplish this by moving from Action to Expression>. Then you can look at what's happening in the method like so
public static void PFA(Expression<Action<frmain>> expression) {
// this will give you form.RichTextBox1
var targetFormProperty = (MemberAccessExpression)expression.Body;
// this only works for your example given. this gives you RichTextBox1.Text
var textProperty = (MemberAccessExpression)targetFormProperty.Expression;
// this is how you would run your action like normal
var action = expression.Compile();
action(); // invoke action (you would want to wrap this in the invoke check)
}
This gives you the info you need to figure out the property, but not how to append the \n. I'll leave this up to you to find out. I think this is overkill here, but who knows, maybe this could help you or someone else, or even inspire a nice solution.
A nice solution i could possibly think of where you could reuse this functionality is like a rule engine where you might compare what the property being accessed is and run an action right after it, etc.