I'm writing some user controls for the first time and I'm wondering if there's a way I can clean up some of my code. (If you'd like more background on what I'm working on, see this question.)
I have a BaseControl class that, basically, parses some XML data and then, depending on what is contained in that data, calls the appropriate UserControl and sends the data on its way. Here's an example:
public partial class BaseControl : User Control
{
protected void Page_Load(object sender, EventArgs e)
{
... //code that parses the data
var renewalDef = effort.Attributes["renewal_def"].Value;
var effortNumber = effort.Attributes["renewal_effort_number"].Value;
if (effortNumber == "1")
{
var effortControl = (NAVLEffort1) Page.LoadControl("~/NAVLSeriesControls/NAVLEffort1.ascx");
effortControl.transactionData = transaction; //'transaction' is a Hashtable object
HtmlContent.Controls.Add(effortControl); //'HtmlContent' is a PlaceHolder control on BaseControl.ascx page
}
if (effortNumber == "2")
{
var effortControl = (NAVLEffort2) Page.LoadControl("~/NAVLSeriesControls/NAVLEffort2.ascx");
effortControl.transactionData = transaction; //'transaction' is a Hashtable object
HtmlContent.Controls.Add(effortControl); //'HtmlContent' is a PlaceHolder control on BaseControl.ascx page
}
if (effortNumber == "3")
{
var effortControl = (NAVLEffort3) Page.LoadControl("~/NAVLSeriesControls/NAVLEffort3.ascx");
effortControl.transactionData = transaction; //'transaction' is a Hashtable object
HtmlContent.Controls.Add(effortControl); //'HtmlContent' is a PlaceHolder control on BaseControl.ascx page
}
// and so on...
}
}
This isn't the actual code I've written, it's just an example of where I could be headed. What I'd like to do is something more like this:
...
var effortControlFileString = string.Format("~/NAVLSeriesControls/{0}Effort{1}.ascx", renewalDef, effortNumber);
var effortControl = (renewalDef + "Effort" + effortNumber) Page.LoadControl(effortControlFileString);
effortControl.transactionData = transaction;
HtmlContent.Controls.Add(effortControl)
...
Any ideas how I can clean this mess up?
Interface
You could have all controls implement a common interface and cast to that.
public interface IMyInterface
{
object TransactionData
{
get;
set;
}
}
Control effortControl = Page.LoadControl(path);
HtmlContent.Controls.Add(effortControl);
IMyInterface obj = (IMyInterface)effortControl;
obj.TransactionData = transaction;
See this working example in an online IDE.
Base Class
You could also use an abstract base class and cast to that type with the same results. You will need to use a base class which inherits from UserControl. This would avoid having two object references (as in my example above) because it can be cast to UserControl.
The example above becomes:
MyCustomControlType c = (MyCustomControlType)Page.LoadControl(path);
HtmlContent.Controls.Add(c);
c.TransactionData = transaction;
If the logic for each control type is different, then you will probably need to cast to each specific type (basically a big if/else block) and deal with each control individually. In other words, if you need to perform different actions based on the type of the control, you will need logic that is type-aware.
For completeness sake I will mention that you could also use the DLR but I would suggest against it. You would be giving up compile-time type safety and performance to reduce a little code.
you can create an interface and add your control to html page.
ex:
private Control _contentControl;
_contentControl = Page.LoadControl("~/Administration/Projects/UserControls/" + controlName);
((IEditProjectControl)_contentControl).ProjectId = ProjectId;
plhContent.Controls.Clear();
plhContent.Controls.Add( _contentControl );
_contentControl.ID = "ctlContent";
Image2.Visible = ((IEditProjectControl)_contentControl).ShowSaveButton;
SaveButton.Visible = ((IEditProjectControl)_contentControl).ShowSaveButton;
((IEditProjectControl)_contentControl).Initialize();
Related
Sorry for my verry badly written title, I'm a beginner programmer and I just started on a c# winforms app. In one function I create an object of some type and then in other functions I iterate through a list of that type of objects, however I'm switching the type of control I'm using and when I do, I have to change the type declaration of my object in over twenty places. Is there a way to create a variable that holds that type and than define all my objects off that variable so I only have to specify the type once and then change that variable. Because I'm using winforms controls as my class types all the functions I call are all then same no matter what type my objects are, so all I need to do is change the type declaration and that's it, sorry if this is a stupid question and any help would be appreciated.
Here is a snippet of my code for context:
private void function1(object sender, EventArgs e) //not my actual function because the real function has lots of other unrelated code
{
ListView PlaceType = new ListView(); // these ListView types i would like to replace with a placeholder if possible
ListView listview = new ListView();
int count2 = autolayoutGroups.Controls.OfType<ListView>().ToList().Count();
listview.Size = new System.Drawing.Size(150, 100);
listview.BackColor = normalColor;
listview.BorderStyle = BorderStyle.Fixed3D;
listview.ForeColor = System.Drawing.Color.Black;
listview.Name = "Group" + count2;
listview.MouseDown += Select;
}
private void function2(object sender, EventArgs e)
{
List<ListView> list_of_groups = autolayoutGroups.Controls.OfType<ListView>().ToList(); // a place where I need some type of placeholder
foreach (ListView l in list_of_groups)
{
// do something here
}
}
private void function3(object sender, EventArgs e) // I have several functions like this
//and if I change the control I'm using I have to change the types in every function
{
List<ListView> list_of_groups = autolayoutGroups.Controls.OfType<ListView>().ToList(); // a place where I need some type of placeholder
foreach (ListView l in list_of_groups)
{
// do something here
}
}
If I understand your problem correctly, you currently have some code where you use a ListView, and you want the same code, but instead of ListView you want some other class, for instance a DataGridView, or a ComboBox. Of course this other class must also have the methods that you used on the ListView.
In C# this concept is called a generic. You have generic classes and generic methods.
You define the generic by typing an identifier instead of the part that you want to replace with another type.
In your case: you want to replace ListView by DataGridView. In function1 you create a ListView, and set some properties. First we'll put this creation in a separate method, you will have something like this:
private ListView CreateListView()
{
ListView listview = new ListView();
int count2 = autolayoutGroups.Controls.OfType<ListView>().Count();
listview.Size = new System.Drawing.Size(150, 100);
listview.BackColor = normalColor;
listview.BorderStyle = BorderStyle.Fixed3D;
listview.ForeColor = System.Drawing.Color.Black;
listview.Name = "Group" + count2;
listview.MouseDown += Select;
return listView;
}
private void function1(object sender, EventArgs e)
{
ListView createdListView = this.CreateListView();
// TODO: do something with the created ListView
}
(Small optimization, out of scope of the question): to calculate count2, don't create a List of all ListViews, and then Count them; use Count() on the IEnumerable<ListView>.
To change CreateListView such that it can create anything of type TMyType, define the generic method like this:
private TMyType Create<TMyType>()
{
TMyType createdObject = new TMyType();
int count2 = autolayoutGroups.Controls
.OfType<TMyType>()
.Count();
createdObject.Size = new System.Drawing.Size(150, 100);
createdObject.BackColor = normalColor;
createdObject.BorderStyle = BorderStyle.Fixed3D;
createdObject.ForeColor = System.Drawing.Color.Black;
createdObject.Name = "Group" + count2;
createdObject.MouseDown += Select;
return createdObject ;
}
So all I did was, that whenever I save ListView, I replaced it with TMyType, the type that should be created.
Usage:
ListView createdListView = this.Create<ListView>();
DataGridView createdDataGridView = this.Create<DataGridView>();
ComboBox createdComboBox = this.Create<ComboBox>();
There is only one problem. You'll have to tell the compiler that TMyType has a default constructor (you want to do new TMyControl()), and that is has methods like Size, BackColor, ForeColor, etc.
If TMyType would be a class derived from Control, then you would be certain that it has the desired constructor and knows all methods that you need to use.
To say that a generic type is derived from a certain type, you use the following structure:
private TMyType Create<TMyType>() where TMyType: Control
{
// because you are certain the TMyType is derived from Control
// you can use all methods of class Control
}
This answers your question: create a generic method
Some other things about generics
Another example: If you want to inform the compiler that the generic type implements IComparable:
private T Process<T>(T input) where T: IComparable {...}
Or multiple:
private T Process<T>(T input) where T: IComparable, IEquatable {...}
And finally if you want to require that the generic type has a default constructor:
private T Process<T> () where T: new
It's not really clear what the goal is but I suppose you can make a property that returns your commonly used list:
private List<ListView> AutolayoutGroupControls =>
autolayoutGroups.Controls.OfType<ListView>().ToList()
Then you can
foreach(var lv in AutolayoutGroupControls)}
...
}
But it doesn't offer much; if you change that prop to return something else you still have a stack of changes to make. If your loops always do the same thing put it into a method and call it from N event handlers
The usage of question is a bit odd.
I am trying to return one of the TextBox, Button or even Label object from a function when I give string name of the object, like code below:
public [I don't know the type] getObjectClass(string type) {
switch(type) {
case "textbox":
return new TextBox();
break;
case "label":
return new Label();
break;
}
and finally I can access the object:
var obj = getObjectClass("label").Content = "I am new label!..";
You probably want dynamic for your return type.
public dynamic getObjectClass(string type)
Using Control, the lowest common ancestor to both Label and TextBox, would not let you access properties of the specific class that you create.
Note that this approach has risks, because the code is no longer type-safe. Another approach would be to use generics, but then you would have to specify the type along with a matching string with the type name, defeating the purpose of getObjectClass method.
Short answer: Find some class that is a superclass of all possible return types. For example, consider System.Windows.Controls.Control.
Additional info: You're trying to create a dynamic way of creating objects. This could be fine, for example for allowing your user to create their own interfaces, but I feel it is a potential code smell. For example, if you want to create a textbox and then set its text, you might do something like this:
Control myTextbox = CreateObjectFromTypename("textbox");
((TextBox)myTextbox).Text = "Hello, world!";
But now that you know the control will be cast into a textbox, why do you need to create it dynamically? Why couldn't you have used TextBox myTextbox = new TextBox()?
Again, I can't say for sure that your approach is bad, but I advice that you take care, and ensure that you really do need this dynamic creation.
I am guessing you want to add controls to the UI dynamically.
Since you have to set a property in the Control i.e. for TextBox you have to set the Text property, for Label you have you set the Content property. I suggest the following approach.
In the below sample I add a textbox and label to the UI dynamically.
The important piece in the below code is the Dictionary<Type, Action<Control, String>> property. In this Dictionary I define how to set the content for each Control depending on its Type.
Note:
I would suggest you to design in such a way that you don't separate the instance creation and property assignment into two different method. Do it in one single go. Check the new method with signature getObjectClass(string type, String content, Thickness margin).
XAML:
<Window x:Class="SplashScreenDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="800">
<StackPanel Name="StackPanelObj">
</StackPanel>
</Window>
Codebehind:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace SplashScreenDemo
{
public partial class MainWindow : Window
{
Dictionary<Type, Action<Control, String>> SetContent = new Dictionary<Type, Action<Control, String>>
{
{ typeof(TextBox), (control, content) => (control as TextBox).Text = content},
{ typeof(Label), (control, content) => (control as Label).Content = content}
};
public MainWindow()
{
InitializeComponent();
Control control = getObjectClass("textbox");
SetContent[control.GetType()](control, "This is a textbox");
Control control2 = getObjectClass("label");
SetContent[control2.GetType()](control2, "This is a label");
StackPanelObj.Children.Add(control);
StackPanelObj.Children.Add(control2);
}
public Control getObjectClass(string type)
{
switch (type)
{
case "textbox":
return new TextBox();
case "label":
return new Label();
default:
return null;
}
}
public Control getObjectClass(string type, String content, Thickness margin)
{
switch (type)
{
case "textbox":
var textBox = new TextBox();
textBox.Text = content;
textBox.Margin = margin;
return textBox;
case "label":
return new Label();
default:
return null;
}
}
}
}
First you need have full name of the type. Since you want to create WPF controls, concat control type name with 'System.Windows.Controls'. Then you need to use Type.GetType to create an instance of underlying type, and finally use Activator.CreateInstance to create an instance of the Control.
public Control CreateControlByTypeName(string typeName) {
string fullName = string.Format("System.Windows.Controls.{0}", typeName);
Type type = Type.GetType(fullName);
return (Control)Activator.CreateInstance(type);
}
Using dynamic keyword does the job
dynamic T;
switch (xnode.Type)
{
case "text":
T = new TextBox();
T.Text = "Enter your name";
Grid.SetColumn(T, 1);
Grid.SetRow(T, row);
break;
}
But I am not sure logically and overhead issues.
So based on your current function:
public [I don't know the type] getObjectClass(string type) {
The type will have to be something more generic like Control or Object, because you don't know the specific return type. If you are going to return multiple different types likes (Label, and TextBox), you have to return a parent class which they both inherit from.
This gets a little trickier
var obj = getObjectClass("label").Content = "I am new label!..";
as your base class (the return type) needs to have the Content property associated with it to be able access the property without casting. The easiest way around (although not my favorite) is to cast it ((Label) getObjectClass("label")).Content. This doesn't give you any compile time assurances though that the object you are returning actually has this property.
Personally I would write the method something like
public T getObjectClass<T> () where T: new() where T : Control {
return new T();
}
This way you can specify what you want to return and the generic automatically makes it the correct type. You also don't run into the problem of doing something like "lable" when you pass it in as a string and have it fail at runtime.
I have a project to which I delegate the function of creating a library of (static)? classes used in all my other projects. It is referenced via solution in these cases.
For instance, I have an extension which creates checkboxes within a given GroupBox's panel, and that works great:
public static void PreencheCheckboxesPanel(this Panel p, List<CheckBox> listaCheckBoxs) {
var count = 0;
listaCheckBoxs.ForEach(
i => {
i.Location = new Point(10, 10 + ((count) * 25)); //"dynamic" and not-so-effective resizing here
i.AutoSize = true;
count++;
});
p.Controls.AddRange(listaCheckBoxs.ToArray());
}}
Problem is, I need to insert a static checkbox on the top of the list, which will receive a method to (un)?check all the checkboxes below.
So my code will become
internal static CheckBox CKB_ancora = new CheckBox(){};
public static void PreencheCheckboxesPanel(this Panel p, List<CheckBox> listaCheckBoxs) {
var count = 0;
if (adicionaAncora) {
CKB_ancora.Text = textoAncora;
CKB_ancora.CheckedChanged += (sender, args) => {
ChecaCheckBoxes(p, CKB_ancora.Checked);
};
listaCheckBoxs.Insert(0, CKB_ancora);
}
listaCheckBoxs.ForEach(
i => {
i.Location = new Point(10, 10 + ((count) * 25)); //"dynamic" and not-so-effective resizing here
i.AutoSize = true;
count++;
});
p.Controls.AddRange(listaCheckBoxs.ToArray());
}}
where ChecaCheckBoxes is another
public static void ChecaCheckBoxes(this Panel b, bool checkStatus = true) {
var listaCheckBoxs = (from Control c in b.Controls where c is CheckBox select (CheckBox)c).ToList();
listaCheckBoxs.ForEach(
i => {
i.Checked = checkStatus;
});
}
and CKB_ancora needs to be a solution-wide recognized object.
The reason? I have another extension named GetSelectedCheckBoxes which will be used to return all the checked ... ah... checkboxes within the groupbox. And, in order to make sure that the "anchor" (I call it like this, since I don't have a name to a (un)?check-all checkbox) won't be returned as well.
If I run this code, it will compile, but... will run accross an InvalidOperationException at Application.SetCompatibleTextRenderingDefault, right at Main(); Apparently, a control cannot be created/instantied before this method is run at mainpoint, which is the exact definition of "static".
Question: Knowing that I NEED a way to keep this particular check solution-wide visible... How do I do it?
Unfortunately, you have not provided a good, minimal, complete code example, and lacking enough context it is very hard to provide good, specific advice.
That said, it seems that somewhere in this static class of yours, you have a field named CKB_ancora, and you are probably initializing it using a field initializer, possibly like this:
private CheckBox CKB_ancora = new Checkbox();
And having done this, you are finding that when the class is initialized (typically on the first time something in the class is accessed at runtime), that happens too soon and an exception is thrown.
Assuming that's correct, then it seems to me that most obvious and simplest "fix" is to initialize the object lazily. For example:
private Lazy<CheckBox> _ckb_ancora =
new Lazy<CheckBox>(() => new CheckBox());
private CheckBox CKB_ancora { get { return _ckb_ancora.Value; } }
That will wrap the object storage in a property, which in turn uses an instance of Lazy<T> to defer initialization until the first time any code actually tries to access it.
Now, that said, I'm not very enamored of your approach here, with a static member that is used in some instantiated object. What if someone using your library wants to use the code with two or more Panel instances? A Control (including a CheckBox) can't be a child of more than one other Control at a time, so having a single static instance of the Control is just not going to work.
IMHO, it would probably be better to instead use some identifying feature such as the Name or Tag property of the CheckBox to handle the control appropriately (e.g. such as filtering it out of enumerations).
For example:
public static void PreencheCheckboxesPanel(this Panel p, List<CheckBox> listaCheckBoxs) {
var count = 0;
if (adicionaAncora) {
CheckBox CKB_ancora = new CheckBox();
CKB_ancora.Text = textoAncora;
CKB_ancora.Name = "CKB_ancora";
CKB_ancora.CheckedChanged += (sender, args) => {
ChecaCheckBoxes(p, CKB_ancora.Checked);
};
listaCheckBoxs.Insert(0, CKB_ancora);
}
listaCheckBoxs.ForEach(
i => {
i.Location = new Point(10, 10 + ((count) * 25)); //"dynamic" and not-so-effective resizing here
i.AutoSize = true;
count++;
});
p.Controls.AddRange(listaCheckBoxs.ToArray());
}}
And, for example:
public static void ChecaCheckBoxes(this Panel b, bool checkStatus = true) {
var listaCheckBoxs = b.Controls
.OfType<CheckBox>().Where(c => c.Name != "CKB_ancora").ToList();
listaCheckBoxs.ForEach(
i => {
i.Checked = checkStatus;
});
}
That way, when you go to retrieve the list of CheckBox controls, the special one you added at the beginning is ignored.
Even with that, I think the code is still pretty fragile. The above would work better, but IMHO it would probably be even better if you weren't using static members for all of this in the first place. I.e. instead you should design some mechanism that allows you to relate an instance of the helper class to the Panel object it's helping with, so that you can in fact initialize and store per-instance information, without running into problems of order of execution, as well as of limitations of use of the code with just a single client.
Without a better code example, I don't see any good way to offer any specific advice along those lines though.
Problem: Program flow is not going to the child class implementation of ValidateDynData when I call ValidateDynData in my parent class.
I create my instance of my class using reflection. When I invoke a method in the child class from another project, it winds up in the correct method in the child class (and not parent's same-name method), so it seems like that is set up correctly.
This is what the reflection part looks like in my other project/class:
**Note 3/7/2013: I added more info so you can get the general feel for this. It gets the number of boxes, loops thru the number of boxes, and for each box, creates a control and adds a tab to the form. This is the main CTool visual studio project and is a class in the project, which is a form. When I press a button on the form, with the info (selected) on which child class I'm going to be creating later, it goes to this method , CreatTabs():
cb = new CB();
int errValue = cb.FindUsbHid(ref HWndBoxID); //see how many boxes there are
if (errValue == 0 && HWndBoxID[0, 1] != 0)
{
for (int i = 0; i < cb.cbInfos.Length; i++)
{
if (controls[i] == null)
{
CB cb1 = new CB(); //need one for each box found or concurrent programming will fail
errValue = cb1.FindUsbHid(ref HWndBoxID); //need to do for each box to get all info
/////////////////////////////////////////
if (errValue == 0)
{
_assembly = Assembly.LoadFrom(programDll);
_type = _assembly.GetType("CrWriter.PC");
_objectInstance = Activator.CreateInstance(_type);
_parameters = new Object[] { cb1, programDll, templateArr, itsDll, cert, i, cb1.cbInfos[i].boxID };
controls[i] = new Control();
//The following lands in my child's GetPC method
//My parent also has a method called GetPC and that is called from the child.
//Then, most of the program flow is in the parent until I need to call ValidateDynData,
//discussed below
controls[i] = (Control)_type.InvokeMember("GetPC", BindingFlags.Default | BindingFlags.InvokeMethod, null, _objectInstance, _parameters);
controls[i].Dock = DockStyle.None;
this.Controls.Add(controls[i]);
TabPage newPage = new TabPage(string.Format("{0}:{1}", cb1.cbInfos[i].usbHandle, cb1.cbInfos[i].boxID));
Console.WriteLine("frmUserForm::CreateTabs - Making new tab with cb.cbInfos[i].usbHandle:" + cb1.cbInfos[i].usbHandle + " and cb.cbInfos[i].boxID:" + cb1.cbInfos[i].boxID);
InitializeControls(controls[i]);
tabCtrlMain.Size = controls[i].Size;
tabCtrlMain.Width += 20;
tabCtrlMain.Height += 100;
this.Width = tabCtrlMain.Width + 20;
this.Height = tabCtrlMain.Height + 50;
newPage.Controls.Add(controls[i]);
tabCtrlMain.TabPages.Add(newPage);
} //no err for this cb
} //controls not null
} //for all cbInfo's
}//if no err in general finding out how many cb's
this.ResumeLayout();
}
Since my Invocation of GetPC lands in the child class and not the parent, it must be created correctly. So I'm not sure why it's not landing in the correct ValidateDynData method. Maybe I need to cast my object to the programDll somehow. When I run the program and inspect the _objectInstance it could be a problem:
variable..................................................value
base: {GenericCrWriter.GenericPC} ......CrWriter.PC
baseInst: ................................................GenericCrWriter.GenericPC
But then, the _assembly is referring to Ko/PC and not Generic/GenericPC.
Also, my _assembly.GetType looks good. My Generic/parent doesn't have anything named CrWriter.PC
I'm trying to use the child class method instead of the parent class for some child class cases. For some reason, I get to the parent class method, but it never gets to the override in the child. Any ideas why? I've been referring to Calling child class method from parent
but it's not getting to the child's method.
In my PC.cs of the child class (Ko):
**Note 3/8/2013: PC.cs is in the Ko visual studio project. **this contains a form that is displayed
**Note 3/7/2013: This is a separate visual studio project named after the child, let's call it Ko. The important class here is PC.cs. It doesn't do much except pass data to the parent, provide it's custom textBoxes and their names, validate data entered later in the parent's form. Most of the flow is in the parent, otherwise. I'm adding GetPC, setProgramName, setDTF methods.
public partial class PC : GenericPC
{
String childDllName = ""; //I just added this recently but it doesn't seem useful
GenericPC baseInst = new GenericPC();
public Control GetPC(USB_Comm.CB cbInst, string dllSel, TemplateHApp.Templates.TEMPL[] templ, string dll, SC.SC.SITE c0, int slaveIndex, int BoxID)
{
childDllName = dll;
//call parent class methods
setProgramName();
setDTF();
ProcessDynData();
return baseInst.GetPC(cbInst, dllSel, templ, dll, cert0, slaveIndex, BoxID);
}
public void setProgramName()
{
Console.WriteLine("Ko did stuff");
//Update label on form
var f = new F(); //F is a class in child class containing more info on it
string temp = f.GetProgramName();
baseInst.setProgramName(temp); //this is displayed on parent's form
}
public void setDTF()
{
var f = new F();
string temp = f.DTF();
baseInst.setDTF(temp); //this is displayed on parent's form
}
private void ProcessDynamicData()
{
Console.WriteLine("Ko PC::ProcessDynamicData");
Label lbl_dynData0 = new Label();
Label lbl_dynData1 = new Label();
lbl_dynData0.Text = "AT .";
lbl_dynData1.Text = "VL .";
lbl_dynData0.Location = new Point(57, 25);
lbl_dynData1.Location = new Point(57, 45);
Label[] lbl_dynData_Arr = new Label[4];
lbl_dynData_Arr[0] = lbl_dynData0;
lbl_dynData_Arr[1] = lbl_dynData1;
TextBox tb_dynData0 = new TextBox();
TextBox tb_dynData1 = new TextBox();
tb_dynData0.Location = new Point(67, 25);
tb_dynData1.Location = new Point(67, 45);
tb_dynData0.Size = new Size(151,22);
tb_dynData1.Size = new Size(151, 22);
TextBox[] tb_dynData_Array = new TextBox[4];
tb_dynData_Array[0] = tb_dynData0;
tb_dynData_Array[1] = tb_dynData1;
PC pc = this; //Tried adding this to get past problem but it's not turned out useful
//I think the way I access parent class from child is the problem of why flow in
//parent class isn't reaching overridden method in child when called:
baseInst.addDynamicDataTextBoxes(tb_dynData_Array, lbl_dynData_Arr, childDllName, pc);
}
public override void ValidateDynData(TextBox[] tb_dynData_Array, ref int result)
{ //I added more info here, but it's probably too much info 3/7/2013
Console.WriteLine("Ko PC::ValidateDynData");
result = -610;
//AT
if ((Convert.ToInt16(tb_dynData_Array[0].Text) >= 1) && (Convert.ToInt16(tb_dynData_Array[0].Text) <= 99))
result = 0;
//VL
if (result == 0)
if ((Convert.ToInt16(tb_dynData_Array[1].Text) >= 69) && (Convert.ToInt16(tb_dynData_Array[1].Text) <= 100))
result = 0;
else
result = -610;
}
In my GenericPC.cs of the parent class:
**Note 3/8/2013: GenericPC is in the Generic visual studio project.
**Note 3/7/2013 When the child class calls the parent class to initialize important data, the parent class shows it's form and fields (I think resume layout shows it). Next, we enter data on the form, including Ko's custom data, then we hit a button on the form (btn_Lock_Config_Click) and it needs to process and validate it's data. I added more methods to get the feel for flow. There are a ton more methods in parent than child (not shown), including try/catch, etc.
//base
public partial class GenericPC : UserControl
{
//class variables (wave your hands..too much info)
public Control GetPC(USB_Comm.CB cbInstance, string dllSelected, TemplateHApp.Templates.TEMPL[] template, string dll, SC.SC.SITE c0, int slaveIndex, int boxID)
{
cb = cbInstance;
SlaveIndex = slaveIndex;
createControls();
itsDll = dll;
templateArr = template;
return this; //return the control for the control array
}
//called from child class
public void setProgramName(string name)
{
Console.WriteLine("Generic setProgramName slaveIndex:" + SlaveIndex);
lbl_Program_Name.Text = name;
}
//called from child class
public void setDTF(string theDTF)
{
Console.WriteLine("Generic setDTF slaveIndex:" + SlaveIndex);
lbl_Program_Name.Text += " ";
lbl_Program_Name.Text += theDTF;
lbl_Program_Name.Refresh();
}
public void addDynamicDataTextBoxes(TextBox [] tb_dynData, Label [] lblTitle, String childName, Object child)
{
childHasDynamicData = true; //somebody's knocking
itsChildName = childName; //child name isn't turning out to be useful here
itsChild = child; //child isn't turning out to be useful here
Console.WriteLine("Generic addDynamicDataTextBoxes slaveIndex:" + SlaveIndex);
//Display what child wants
for (int i = 0; i < tb_dynData.Length; i++)
{
//assumes calling code knows real estate and planned for it
gb_dynamicData.Controls.Add(lblTitle[i]);
gb_dynamicData.Controls.Add(tb_dynData[i]);
}
itsEnteredDynamicData = tb_dynData; //nothing entered yet
}
private void btn_Lock_Config_Click(object sender, EventArgs e)
{
int status = 1;
Console.WriteLine("Generic btn_Lock slaveIndex:" + SlaveIndex);
//it does some flagging and data checking, etc.
status = processDynamicData();
}
private int processDynData()
{
int returnCode = 0; //I'm setting it to desired value for example
//processes data, puts it into data arrays, etc,
if ((returnCode >= 0) && childHasDynamicData)
ValidateDynData(itsEnteredDynamicData, ref returnCode);
//start here for problem...it never calls child method, as intended
}
public virtual void ValidateDynData(TextBox[] tb_dynData_Array, ref int result)
{
Console.WriteLine("Generic::ValidateDynData passing off to child to validate special data");
}
Any ideas why it's not going to the child class implementation of ValidateDynData when I call ValidateDynData in my parent class? This is the only area in my code where I am trying to have a child class override a parent implementation, so I'm not sure if I'm doing something wrong?
I checked the correct version of Generic.dll is referenced in the child's project/class. I did a clean build in the child class. Any other binaries that should be checked? Is there something wrong with my reflection? Is there something wrong with my virtual/override use for ValidateDynData?
Update:
I've been looking at the code some more, and I get flow into the parent class by creating an instance of the parent/base class. So I think that's why I'm not getting into ValidateDynData that is overridden in the child class when I call it in parent. Is there another way to get to the parent's method without creating an instance of the parent?
GenericPC baseInst = new GenericPC();
return baseInst.GetPC(cbInst, dllSel, templ, dll, cert0, slaveIndex, BoxID);
**Update 3/7/13:
It's also possible that the problem is that I press a button on the parent's form which starts a new thread and by doing this, it doesn't know about child class, so that's why flow doesn't get to child when I call ValidateDynData method.
Short answer: Just delete all that awful code and start over.
Longer answer:
// (1)
public partial class PC : GenericPC
{
// (2)
GenericPC baseInst = new GenericPC();
public Control GetPC(…)
{
…
// (3)
return baseInst.GetPC(cbInst, dllSel, templ, dll, cert0, slaveIndex, BoxID);
}
public override void ValidateDynData(TextBox[] tb_dynData_Array, ref int result)
{
// (4)
…
}
}
Comments to marked lines of code:
At this point you declare PC as a descendant of GenericPC. So far so good.
Here you declare and instantiate a completely disparate instance of GenericPC which has nothing to do with the instance of PC you are working with.
You call GetPC, a method of an instance of PC, which in turns call GetPC in that completely disparate instance of GenericPC; nothing in common with the original PC instance!
Finally, you expect control flow to end up the original PC instance; but that won't ever happen when, effectively, you all the time call methods of some silly GenericPC instance!
My recommendation is reading a book about object-oriented programming, that provides samples in C#. It seems you are even missing the point of inheritance, one of the basic concepts in OOP.
To fix it, you need to remove the declaration of baseInst, and replace all calls to baseInst's methods with the base keyword. Then your code will actually call methods declared in the ancestor class within the same instance. Also most methods shall be declared as virtual in GenericPC and you have to override them in PC.
public partial class PC : GenericPC
{
public override Control GetPC(…)
{
…
return base.GetPC(cbInst, dllSel, templ, dll, cert0, slaveIndex, BoxID);
}
public override void ValidateDynData(TextBox[] tb_dynData_Array, ref int result)
{
…
}
}
In some obscure way a derived class which doesn't add new functionality (yet) behaves different from it's base class. The derived class:
public class MyCheckButton : CheckButton
{
public MyCheckButton(string label) : base(label)
{
}
}
MyCheckButton inherits from a (GTK#, part of the Mono project) CheckButton. However in the following code snippet they behave differently:
var button1 = new CheckButton("_foo");
var button2 = new MyCheckButton("_foo");
// code omitted
The underscore in the label makes sure that the label gets a mnemonic. For button1 this works in my testcode: I get "foo" where the f is underlined. However for button2 this fails. I just get "_foo" as a label in my dialog.
Can anyone explain how the derived class in this example could behave differently or is there some magic going on behind the screen that maybe checks the type of the actual class?
[I]s there some magic going on behind the screen that maybe checks the
type of the actual class?
Actually, there is:
public CheckButton(string label) : base(IntPtr.Zero)
{
if (base.GetType() != typeof(CheckButton))
{
ArrayList arrayList = new ArrayList();
ArrayList arrayList2 = new ArrayList();
arrayList2.Add("label");
arrayList.Add(new Value(label));
this.CreateNativeObject((string[])arrayList2.ToArray(typeof(string)), (Value[])arrayList.ToArray(typeof(Value)));
}
else
{
IntPtr intPtr = Marshaller.StringToPtrGStrdup(label);
this.Raw = CheckButton.gtk_check_button_new_with_mnemonic(intPtr);
Marshaller.Free(intPtr);
}
}
It looks like your subclass will be going the former route. Not sure why that would mess up the mnemonic, though; the latter method is a P/Invoke on the native gtk library. It's possible that wrapping label in a Value object is mucking up the mnemonic stuff.
Let that be a lesson (to the GTK# designers): don't violate the Liskov Substitution Principle. It's confusing!
Here's why, look at the source for the CheckButton ctor:
public CheckMenuItem (string label) : base (IntPtr.Zero)
{
if (GetType() != typeof (CheckMenuItem)) {
CreateNativeObject (new string [0], new GLib.Value [0]);
AccelLabel al = new AccelLabel ("");
al.TextWithMnemonic = label;
al.SetAlignment (0.0f, 0.5f);
Add (al);
al.AccelWidget = this;
return;
}
IntPtr native = GLib.Marshaller.StringToPtrGStrdup (label);
Raw = gtk_check_menu_item_new_with_mnemonic (native);
GLib.Marshaller.Free (native);
}
Derived types do not follow the same code path as CheckButton in the .ctor