I have a base form (BaseMDIForm) that several forms (Client, Inventory, Orders, etc) inherit from. On this base form is a protected string "windowID". This id gets set by each form as a way to uniquely identify the object currently open in that form (Client.ID, Inventory.ID, Order.ID, etc).
I am successfully using this method on the Client form to find a specific client id on any open Client form:
// method is in the Client form (and is coded to specifically look for Client forms)
private Boolean FindFormWithWindowID(string windowID)
{
foreach (Form form in this.MdiParent.MdiChildren)
{
if (form.GetType() == typeof(frmClient))
{
frmClient clientForm = (frmClient)form;
if (clientForm.windowID == windowID)
{
// close this form because the client is open on an existing form
if (this.windowID == "") // this won't be set yet if we're searching for an existing form
Close();
form.Activate();
return true;
}
}
}
}
The idea behind this concept is that when a user tries to open a client record (or inventory, order, etc) that is already open on an existing form, the existing form is given the focus and brought to the front (and what would have been the newly opened form is then closed).
I'm trying to move this method to the base form so this method doesn't need to be continually created in any inherited forms, just called from those forms, as in:
if (!FindFormWithWindowID(ClientForm, windowID))
{
// continue loading new form
}
Is there a way to pass in the form type (Client, Inventory, Orders, etc), then have this new base method loop through the existing MdiChildern of the specific passed-in form type to find a specific form that contains a given window id (keeping in mind that this method will be called from many different types of inherited forms)? I'm trying to avoid adding a switch statement to determine the form type, as this would force me to update it anytime a completely new form is added to the project.
WindowID doesn't actually exist on a form so I'm assuming your've got an inherited base form that you have added WindowID to.
public InheritedForm : Form
{
public string WindowID { get; set; }
}
If this is the case then you can replace your current FindFormWithWindowID method with:
string windowidtolookfor = "1234";
InheritedForm foundform = Application.OpenForms.OfType<InheritedForm>().Where(f => f.WindowID = windowidtolookfor).FirstOrDefault();
If you want a specific type of form:
MyForm foundform = Application.OpenForms.OfType<MyForm>().Where(f => f.WindowID = windowidtolookfor).FirstOrDefault();
You will need to check that foundform is not null before you attempt to do anthing with it as FirstOrDefault() will return null if it cannot return a matching item.
This can be called from anywhere so you don't need to worry about picking a sensible location for it.
There seems to be a little confusion about what this is doing so I'll break it down:
Application.OpenForms is a property that lists all the Forms that are open within the application.
We call this then we specify the type of Form we're looking for with `OfType() this filters down the OpenForms to only those with a typeof FormX.
After this we add Where(f => f.WindowID = "1234")
This tells the query that we want only those forms of FormX that have a WindowID of "1234".
Finally we add FirstOrDefault(), this will return the first instance it finds where the Form is FormX and WindowID = "1234". If no form exists that matches the criteria then it will return NULL.
It is functionally equivalent of your FindFormWithWindowID but it allows you to change it each time you use it by substituting the OfType with whatever form type you want.
If you only want a boolean result at the end then just do it like this:
if (Application.OpenForms.OfType<FormX>().Where(f => f.WindowID == windowidtolookfor).FirstOrDefault() != null)
{
// Found a window matching the criteria
}
else
{
// Not found a window matching the criteria.
}
For a fixed number of types, you could pass a string representing the type, and then check each windows type to see if it matches.
System.Type getTypeOfForm(string docType) {
switch (docType) {
case "Customer":
return typeof(MyNameSpace.CustomerMDIForm);
case "Invoice":
return typeof(MyNameSpace.InvoiceMDIForm);
case "Order":
return typeof(MyNameSpace.OrderMDIForm);
...
default:
throw new Exception("Unknown Form Type");
}
}
You can stitch the return type into your existing loop by plugging the returned runtime type in where you have '== typeof(frmClient)'. If you passed around the fully qualified class string for your target form type, you can also convert that into a System.Type object by using System.GetType(string).
You can also avoid any string to type mapping shenanigans if you simply pass a variable of System.Type to your search:
if (!FindFormWithWindowID(typeof(MyNameSpace.CustomerMDIForm), windowID))
Related
I am working on a Winforms application that uses base window (e.g. Form1) and many other windows (e.g. Form2, Form3, ...). All of these windows have their property TopLevel set to false, no border and I am placing them in the Form1 when I need them. I am not skilled enough or do not have the brain capacity to solve this issue:
public void ShowForm(string strNewForm) {
var frmNew = Activator.CreateInstance(Type.GetType(strNewForm)) as Form;
frmNew.TopLevel = false;
frmNew.Size = new Size(rctForm.Width, rctForm.Height);
frmNew.Location = new Point(rctForm.X, rctForm.Y);
frmNew.InitializeForm(strActualSection, strActualSubSection);
frmNew.Parent = this;
frmNew.Show();
}
The strNewForm variable contains the name of Form to show. All the methods and properties in each Form can be used as these all are inherited from base class Form. Problem is with InitializeForm() which I use in each form (Form2, Form3, ...) to do various stuff. I know my problem is in this line:
var frmNew = Activator.CreateInstance(Type.GetType(strNewForm)) as Form;
When strNewForm contains string "Form2" and I change as Form to as Form2 it works just fine. I know I need to change it to proper name each time, but I don't know how. I tried to use this:
Type frmType = Type.GetType(strNewForm);
var frmNew = Activator.CreateInstance(Type.GetType(strNewForm)) as frmType;
But it throws an error CS0118: frmType is a variable but is used as a type. I don't know how to solve this, I tried many solutions and I googled for half a day straight and I am still lost. Any insight would be really helpful.
You cannot use a run-time variable to create strongly-typed code.
You have to provide a compile-time type after the as.
If you know it's a Form then all you can do is this:
var frmNew = Activator.CreateInstance(Type.GetType(strNewForm)) as Form;
If you need specific methods or properties then use a custom Form as your base-type and use that in the as.
Why won't you pass a Form object in your ShowForm method?
public void ShowForm(Form newForm)
{
newForm.TopLevel = false;
newForm.Size = new Size(rctForm.Width, rctForm.Height);
newForm.Location = new Point(rctForm.X, rctForm.Y);
newForm.InitializeForm(strActualSection, strActualSubSection);
newForm.Parent = this;
newForm.ShowDialog();
}
I have two Forms in my application. A Form has the following fields: txtPower, txtTension and txtCurrent. I would like to access the values filled in these TextBox through another Form. In the second Form I instantiated an object of the first Form (MotorForm), however I do not have access to the TextBox.
public MacroForm()
{
InitializeComponent();
MotorForm motorForm = new MotorForm();
motorForm.Show();
}
Is there any way?
Please do not expose the controls in your form. Never. (Unless you have a really good reason.)
If the problem is simple enough not to use MVVM (or the like) in your program (which you should consider for every program that's but trivial), you should expose the values of the instantiated form via properties. Think
public string Power
{
get { return txtPower.Text; }
set
{
if(ValidatePower(value))
{
txtPower.Text = value;
}
else
{
// throw ??
}
}
}
If we can make a sensible assumption about the type of the value we could extend this to
public double Power
{
get
{
// parse the value
// validate the value
// throw if not valid ??
// return the value
}
set
{
// validate the value
// set the value in the text box
}
}
If you exposed the txtPower object, you'd make the instantiating class depend on implementation details of the instantiated class, which is virtually never a good thing.
It seems that your problem is a perfect situation for using ShowDialog for opening your form.
To accomplish this, you need to change the Modifiers property of the controls you want to access on MotorForm and set them to Public. And also set the DialogResult property of your form somewhere to a desired value i.e OK. Anyway the easier way to do this is to set it on the button that is supposed to close the form. Suppose OK or CANCEL buttons.
Then you can create your form this way:
MotorForm motorForm = new MotorForm();
if(motorForm.ShowDialog() == DialogResult.OK)
{
string myValue = motorForm.txtPower.Text; //you can access your values this way
}
I have custom controls: CustomControlOne, CustomControlTwo.
My CustomControlOne has a List<CustomControlTwo> showed in your properties panel of my windows form application project:
So when I click in the button, a window to add new items in this collection is opened:
But, I want add existing CustomControlTwo items that are defined in MyForm. ps.: MyForm contains CustomControlOne and multiple CustomControlTwo.
I want this items can be added in design time, like same way that selecting an item in a comboBox (in CustomControlOne properties panel). When I change List<CustomControlTwo> to ICollection<CustomControlTwo> a comboBox is showed but the items of type CustomControlTwo not appears :(
How I can do this ?
How I can say to my CustomControlOne where are the CustomControlTwo items ?
Thx.
UPDATE:
I wrote my own UITypeEditor and I reached my goal with help of #Sefe and with THIS link. Bellow my code:
public class CollectionTypeEditor : UITypeEditor {
private IWindowsFormsEditorService _editorService = null;
private ICollection<Control> mControls = null;
private List<Control> mPickedControls = null;
// Editor like Modal style
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
return UITypeEditorEditStyle.Modal;
}
// Opens modal and get returned data
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
if (provider == null)
return value;
_editorService = (IWindowsFormsEditorService) provider
.GetService(typeof(IWindowsFormsEditorService));
if (_editorService == null)
return value;
mControls = new List<Control>();
// retrieve old data
mPickedControls = value as List<Control>;
if (mPickedControls == null)
mPickedControls = new List<Control>();
// getting existent controls that will be inflated in modal
Control mContext = (Control) context.Instance;
GetControls(mContext);
// open form and get response
CollectionDesign<Control> frmCollections = new CollectionDesign<Control>(mControls, ref mPickedControls);
var response = _editorService.ShowDialog(frmCollections);
// returning data from editor
return response == DialogResult.OK ? mPickedControls : value;
}
Everything works well here. Now, my variable in properties panel:
[Editor(typeof(CollectionTypeEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ActionButtonConverter))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<Control> ActionButtons { get; set; }
The serialization attribute was added because the file couldn't be saved. When form is closed and reopened, all data are lost.
The stranger thing is that I wrote other UITypeEditor like the same way, just changing type of data to string and I can close or reopen my form and all works fine, the data are saved.
I already added a TypeConverter but I think that isn't case here. what is wrong with my code?
My basic setup:
In this setup, BaseForm extends Form. With string works, but with List data are lost on close form or build project...
Resumed workflow:
1 - Open MyForm.
2 - Click at ActionButtons ellipsis [...] (inherited by BaseForm) on MyForm properties panel.
3 - A custom form is opened with inflated objects that I want pick.
4 - Objects that I want are picked and I close form. So now, data is ok cause I can reopen that form and see objects that I picked.
5 - Now when close the MyForm and reopen it, all data are lost. The same thing happens when build the project. But if I do all this steps with a string, all are Ok (data are saved).
Thanks all, and sorry for bad language :P
If you want to add multiple CustomControlTwo items to your control, a drop down list will do you no good, since you can only select one value there.
If you are able to change the property to accept a single instance of CustomControlTwo (or are OK that the list you can create in your property browser has only one item), you can create a new System.ComponentModel.TypeConverter that will create the list to select from. A type converter is an abstract class and you will have to implement a couple of abstract methods. The methods that are valuable to you are GetStandardValuesSupported, GetStandardValuesExclusive and GetStandardValues. Here is the part that is interesting to you (you have to add the other methods):
public class CustomControlOneConverter : TypeConverter {
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
//By returning true, you tell the property designer to add a drop down list
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
//By returning true, you tell the property designer to not allow the user to enter his own text
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
//Get all objects of type CustomControlTwo from the container
CustomControlTwo[] controlsToList =
context.Container.Components.OfType<CustomControlTwo>().ToArray;
//Return a collection of the controls
return new StandardValuesCollection(controlsToList);
}
//implement the other abstract methods
}
What you do in GetStandardValues is to search in the container of CustomControlOne, if there are instances of CustomControlTwo. Those are then added to the list of standard values to select from in the property browser.
If you are not able to change your property to select only one CustomControlTwo, you will need to create your own UI to display when the ellipsis (...) is clicked in your property window. For that you need to create your own UITypeEditor. That, however is a more complex undertaking and is not easily explained in a nutshell.
Working on a project where I am storing my data using a GenericList, based off a class I created which contains a handful of string members. This works great, but things get troublesome when I try to pass this list to a second form. I initially setup a get/set block in the second form to accept the list and then return it, but the destination is always null. As a workaround I changed the local list on the second form to public and am able to access it that way, but then instead of getting a copy of the list, I get ByRef passing so any changes made are reflected in the master list. Since I want to be able to not accept these changes, I really want this to be a pass by value. These two problems have to be linked...
Here is the setup. In form1 I have this definition at the class level, making this somewhat of a global variable (yeah its bad, but it works for me):
private List<ServerList> theServerList = new List<ServerList>();
Later on I create the new form and (try to) pass my data into it using:
frmEditor theEditor = new frmEditor();
theEditor.updatedServerList = theServerList;
DialogResult res = theEditor.ShowDialog();
On the second from, I have this to receive the data:
private List<ServerList> myServerList = new List<ServerList>();
public List<ServerList> updatedServerList
{
get { return myServerList; }
set { myServerList = updatedServerList; }
}
This results a list on form2 (the myServerList) always being empty. Since this was not working, I commented out all these lines and changed the myServerList definition on form2 to public. Now after instantiating form2 I am able to get the data over by doing this:
theEditor.myServerList = theServerList;
This works in that the data nicely shows up form2, but this kind of an assignment just copies the pointer of the data block in memory from one variable to the other (basically a ByRef passing), so any changes I make on form2 change "both" lists since they are the same. I would like to have a local copy in form2 so I can make changes and then accept them or not depending of if the user clicks Cancel (drop all changes), or OK (copy the local data from form2 back to form1).
Your original does not work because your setter should be:
public List<ServerList> updatedServerList
{
get { return myServerList; }
set { myServerList = value; }
}
If you want to copy the list, you can use ToList on the incoming value:
public List<ServerList> updatedServerList
{
get { return myServerList; }
set { myServerList = value.ToList(); }
}
This is not to do with 'by value' vs 'by reference' since List<T> is a reference type, so the value of a variable of type List<ServerList> is a reference which is copied into the setter method. This reference will point to the same object in both the caller and receiver classes. If you want to copy the contents of the list you need to do it manually using ToList or similar.
I want to transfer all data from HomeAddressUC to PermanentAddressUC with checkBox SameAsPrevious
Each UserControl has same Type(AddressUserControl)
DataSource to Fill HomeAddressUC is code is like this
private void SetTabPageDetails(string tabPageName, CustomerDetails customerDetailsCache)
{
customerDetailsCache = // calling stored procedure
PermanentAddressUC.SetDetails(customerDetailsCache.Addresses[0]);
}
Scope of that DataSource is upto it method SetTabPageDetails() only
logic I was trying to implement is on checkbox changed event is
if (chkSameAsPervious.Checked)
{
foreach (var addressCtl in from Control ctl in this.ADDRESS_TAB.Controls select ctl as BankSys24.UI.UserControls.AddressUserControl)
{
if (addressCtl.GroupBoxText == "Mailing Address")
{
// want to do something here
}
}
}
I try to follow the relevant link
Best practice when you need two user controls (winforms) to communicate
it says to use the Third Common User Control a Container or interface
What is the optimized way to do it?
Why you need to different user controls for home and mailing address if all the fields same?
you can use one user control for both and create property for set the group name as "Home address" or "Mailing address"
You can have two public methods to set address fields by passing address object and get address object by reading fields of the form.
On check change event of the checkbox you can get address object by calling home address user control instant get address method and then you can set that details on mailing address user control instant by calling set address method by passing address object.
Possible Shortest Solution I Found is like this :
if (chkSameAsPervious.Checked)
{
MailingAddressUC.SetDetails(PermanentAddressUC.GetDetails() as BankSys24.DTO.Customer.Address);
}
where
public object GetDetails()
{
return new BankSys24.DTO.Customer.Address { //data };
}
and
public void SetDetails(object input)
{
//intermediate Object
if (details != null)
{
//Mapping
}
Getdetails maps the all data as Container Class Object which can be Directly pass to SetDetails() ...