C# WinForms - Cannot access a control in a handler method - c#

I have a form containing two flow layout panels (FLP), which dynamically have buttons added to them. These buttons are actually a class called tagButton which inherits from Button and I have added a handler in the constructor for the click() method. On click, I want to remove the button from the FLP it is currently in then add it to the other FLP.
Below is a trimmed down version of my code for the tagButton class. Note that the tagButton class is defined inside the of the form class both FLPs are in:
class tagButton : Button
{
public string tag = "";
public bool useTag = false; //tells you which FLP the button is in
public tagButton(String tag, Boolean useTag)
{
this.tag = tag;
this.Text = tag;
this.useTag = useTag;
this.Click += TagButton_Click;
}
private void TagButton_Click(object sender, EventArgs e)
{
tagButton tagButton = (tagButton)sender;
tagButton.useTag = !tagButton.useTag;
if (tagButton.useTag)
{
flowLayoutPanel.Controls.Remove(tagButton);
}
}
}
I'm having problems with the last line:
flowLayoutPanel.Controls.Remove(tagButton);
I can switch it to the following and it works, however there is no way for me to add it to the other FLP. Or at least, not without doing Parent.Parent.Parent.Controls[1]... etc which is clearly a bad idea.
tagButton.Parent.Controls.Remove(tagButton);
I've tried switching different classes and methods to static but nothing I tried worked, the this keyword doesn't seem to work either.

I would recommend having a separate class overriding a parent control that's aware of both FlowLayoutPanels. Then, when your button wants to switch, it can find that custom control in its parents and invoke a custom "switch" function that would move the invoking button from the list it's in to the list it wasn't in.

One of many ways to achieve this outcome is to have MainForm expose a static array of the FlowLayoutPanel candidates as Panels property:
public partial class MainForm : Form
{
public static Control[] Panels { get; private set; }
char _id = (char)64;
public MainForm()
{
InitializeComponent();
Panels = new Control[]{ flowLayoutPanelLeft, flowLayoutPanelRight, };
buttonAddLeft.Click += (sender, e) =>
{
flowLayoutPanelLeft.Controls.Add(new tagButton
{
Height= 50, Width=150,
Name = $"tagButton{++_id}",
Text = $"Button {_id}",
});
};
buttonAddRight.Click += (sender, e) =>
{
flowLayoutPanelRight.Controls.Add(new tagButton
{
Height= 50, Width=150,
Name = $"tagButton{++_id}",
Text = $"Button {_id}",
});
};
}
}
Then, suppose you want to swap between panels when a tagButton gets a right-click (for example).
class tagButton : Button
{
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (MouseButtons.Equals(MouseButtons.Right))
{
Control dest;
if(Parent.Name.Contains("Left"))
{
dest = MainForm.Panels.First(_=>_.Name.Contains("Right"));
}
else
{
dest = MainForm.Panels.First(_ => _.Name.Contains("Left"));
}
Parent.Controls.Remove(this);
dest.Controls.Add(this);
}
}
}

Related

How to add functions and properties to multiple controls in C #

I have classes called ButtonDesign, TextBoxDesign, and a few more. Everyone’s realization is exactly the same. I add a number of properties and functions to each control, the question is how can this be achieved without duplicate code, i.e .: is there a way to create only one class with the same attributes - and these attributes will be added to all the controls I want? .
If I create a primary class that inherits from the Control class - then these attributes will only be in the Control class and not in all the controls I want.
This is the code I'm trying to:
class ButtonDesign : Button
{
private Control save_properties = new Control();
public Color OnMouseHoverColor { get; set; }
public ButtonDesign()
{
this.MouseEnter += ButtonDesign_MouseEnter;
this.MouseLeave += ButtonDesign_MouseLeave;
}
private void ButtonDesign_MouseEnter(object sender, EventArgs e)
{
save_properties.BackColor = this.BackColor;
this.BackColor = this.OnMouseHvetColor;
}
private void ButtonDesign_MouseLeave(object sender, EventArgs e)
{
this.BackColor = save_properties.BackColor;
}
}
class TextBoxDesign : TextBox
{
private Control save_properties = new Control();
public Color OnMouseHoverColor { get; set; }
public TextBoxDesign()
{
this.MouseEnter += ButtonDesign_MouseEnter;
this.MouseLeave += ButtonDesign_MouseLeave;
}
private void TextBoxDesign_MouseEnter(object sender, EventArgs e)
{
save_properties.BackColor = this.BackColor;
this.BackColor = this.OnMouseHvetColor;
}
private void TextBoxDesign_MouseLeave(object sender, EventArgs e)
{
this.BackColor = save_properties.BackColor;
}
}
Congratulations, you've encountered the diamond inheritance problem. C# doesn't support this scenario, so what you're attempting to do is unfortunately not possible.
Your only options are:
Create a subclass of Control and make your custom controls inherit from it. This means you have to recreate the behaviour of the standard WinForms controls in your subclasses, which is painful at best.
public class MyControl : Control {}
public class MyButton : MyControl {}
Create an interface that your common controls implement. Have the interface implementations delegate to a shared library that does what needs to be done:
public interface IMyControl
{
void MySharedOperation();
}
public class MyButton : Button, IMyControl
{
public void MySharedOperation()
=> MySharedOperationHandler.MySharedOperation(this);
}
public static class MySharedOperationHandler
{
public static void MySharedOperation(Control control) {}
}
You'll end up with a fair amount of method implementations that do nothing more than delegate, but IMO this is far better than reinventing the control wheel as in the previous option.
If you want this, your class ButtonDesign is not a Button, it has a Button and a Layout. Similarly your class TextBoxDesign has a TextBox and a Layout.
In other words: don't use inheritance, use aggregation!
Every property that are both in Buttons, TextBoxes, and other Controls that have the properties that you want to change for all items in one statement, create a class that contains all these properties, with the proper events.
For every property you need a private member, a public get/set property and an event that will be raised when the property changes. Something like this:
class Layout
{
private Color backColor; // TODO: give proper initial values
private Color foreColor;
... // other properties that you want to change for all Controls
// Events:
public event EventHander BackColorChanged;
public event EventHandler ForeColorChanged;
... // etc
// Event raisers:
protected virtual void OnBackColorChanged()
{
this.BackColorChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnForColorChanged()
{
this.ForeColorChanged?.Invoke(this, EventArgs.Empty);
}
...
// Properties:
public Color BackColor
{
get => this.backColor;
set => if (this.BackColor != value) this.OnBackColorChanged();
}
public Color ForeColor ...
public Size Size ...
}
Your DesignButton will be a UserControl that has a Docked Button and a Layout. The constructor subscribes to the events. When raise the corresponding property on the button is set.
Use the visual studio designer to create the UserControl. Code will be similar to the following:
class MyButton : UserControl
{
private Button button; // visual studio designer will create this
private Layout layout;
public Layout Layout
{
get => this.layout;
set => if (this.Layout != value) this.ChangeLayout;
}
// you can't set the design properties. Only get.
protected Button Button => this.button;
Color BackColor
{
public get => this.Button.BackColor;
private set => this.Button.BackColor = value;
}
// etc for ForeColor, Text, ...
protected virtual void ChangeLayout(Layout newLayout)
{
// TODO: if there is an old Layout: desubscribe from all events from old Layout
this.layout = newLayout;
// subscribe to all events:
this.layout.BackColorChanged += this.BackColor_Changed;
this.layout.forColorChanged += this.ForColor_Changed;
...
this.BackColor = this.BackColor;
// TODO: if desired, for completeness add an event: LayoutChanged
}
Now whenever Layout raises event BackColorChanged you handle this event and assign the value to the Button. You'll get the gist.
Do something similar for TextBoxes, ComboBoxes, etc
Usage:
Layout commonLayout = new Layout
{
BackColor = Color.Yellow,
ForeColor = color.Black,
...
};
MyButton button1 = new MyButton
{
Layout = commonLayout,
};
MyTextBox textBox1 = new MyTextBox
{
Layout = commonLayout,
}
MyComboBox comboBox1 = ...
// Change the backgroundColor for all items:
commonLayout.BackColor = Color.Red;
If you have a lot of properties, consider to use generic classes
class LayoutProperty<T>
{
private T propertyValue;
public event eventHandler PropertyValueChanged
protected void OnPropertyValueChanged()
{
this.PropertyValueChanged?.Invoke(this, EventArgs.Empty);
}
public T PropertyValue
{
get => this.propertyValue;
set => if (this.PropertyValue != value) this.OnPropertyValueChanged();
}
}
class Layout
{
private PropertyValue<Color> backColor;
private PropertyValue<Color> foreColor;
// etc, see above for subscribtion and raising events.
And for all Controls:
public MyControl
{
private Control control;
private Layout layout;
// etc, see above for the event handling.
}
public MyButton : MyControl {Control = new Button()}
public MyTextBox : MyControl {Control = new Textbox()}

make a tab from a user control

I'm having problems. I get a lot of errors and this is annoying me when I'm trying to add tabs from a user control.
Here is the code
public Form1 f1 { get; private set; }
private void button1_Click(object sender, EventArgs e)
{
TabPage tp = new TabPage { };
tp.Text = "NewTab";
tp.Controls.Add(new b());
f1.tabControl1.TabPages.Add(tp); //>>> errors here
}
Image for more details: code no showing errors
Run-time errors
Your code isn't assigning f1 which is why you are getting a null reference exception at run time.
Depending on the architecture of your project ...
A. If UserControl is instantiated in the parent Form code behind then simply inject f1 into the constructor of the UserControl:
private readonly MyForm _f1;
public b(MyForm f1){
InitializeComponents();
_f1 = f1;
}
private void button1_Click(object sender, EventArgs e) {
TabPage tp = new TabPage { };
tp.Text = "NewTab";
tp.Controls.Add(new b());
// assumes 'tabControl1' exists as a publicly accessible control
_f1.tabControl1.TabPages.Add(tp);
}
B. If using M-V-P then, using the code you have, the Presenter can assign the f1 variable at initialization but make the setter public or internal.
f1 is null because no form has been assigned to it. Drop this property and instead write:
TabPage tp = new TabPage { };
tp.Text = "NewTab";
tp.Controls.Add(new b());
FindForm().Controls.OfType<TabControl>().Single().TabPages.Add(tp);
This assumes that the form contains exactly one TabControl and that it is a top level control. If it can be inside another container control, you will have to loop the controls recursively. This question might help: Loop through all controls on a form, even those in groupboxes
Note, that your approach has yet another issue: f1 is typed as Form, but this general type has no tabControl1. You would have to type it with a specific form type MyForm f { get; set; }.
Maybe an easier way to access the TabControl is to let the form implement an interface defining just a property returning this TabControl:
public interface ITabControlProvider
{
TabControl MainTabControl { get; }
}
Then let your form implement it
public partial class MyForm : Form, ITabControlProvider
{
...
TabControl MainTabControl { get { return tabControl1; } }
}
Now your UserControl can find the TabControl like this
var frm = FindForm() as ITabControlProvider;
if (frm != null) {
frm.MainTabControl.TabPages.Add(tp);
}
As this code looks like it is all happening from inside a form, you can try this :
public Form1 f1 { get; private set; }
private void button1_Click(object sender, EventArgs e)
{
if(f1 == null) { f1 = this; }
TabPage tp = new TabPage { };
tp.Text = "NewTab";
tp.Controls.Add(new b());
f1.tabControl1.TabPages.Add(tp); //>>> errors here
}
If it is happening inside a UserControl, then you can try this :
public Foo : UserControl {
public TabControl tabControl { get; set; }
private void button1_Click(object sender, EventArgs e)
{
if(tabControl == null) {
// do nothing - ignore button click.
} else {
TabPage tp = new TabPage { };
tp.Text = "NewTab";
tp.Controls.Add(new b());
tabControl.TabPages.Add(tp); //>>> errors here
}
}
}
The on the form you have this user control added to, you can either choose the TabControl it is bound to from the drop-down menu in the property editor, or you can assign it in code like this :
foo1.tabControl = tabControl1;

Different parents for new Winform with a single constructor

Im running into a bit of an issue regarding Children and parents.
I have 2 forms which have the same dropdown menus, both of which have the ability to add additional options to them. When the "(add new)" option is selected in any of the combo boxes my third form is loaded which enables the addition of a new option.
This is the code for that third window (as it stands)
public partial class taskNewDropdownEntry : Form
{
taskWindow _owner;
applianceWindow _owner2;
int windowType;
int manufacturer_id;
sqlMod data = new sqlMod();
public int setManufacturerID {get { return manufacturer_id; } set { manufacturer_id = value; } }
public taskNewDropdownEntry(taskWindow owner, int type)
{
InitializeComponent();
this._owner = owner;
this.windowType = type;
}
public taskNewDropdownEntry(applianceWindow owner, int type)
{
InitializeComponent();
this._owner2 = owner;
this.windowType = type;
}
private void taskNewDropdownEntry_Load(object sender, EventArgs e)
{
if (windowType == 1)
{
instructionLabel.Text = "Input the new appliance type below";
}
else if (windowType == 2)
{
instructionLabel.Text = "Input the new manufacturer below";
}
else if (windowType == 3)
{
instructionLabel.Text = "Input the new model below";
}
}
private void btnOK_Click(object sender, EventArgs e)
{
if (windowType == 1)
{
data.insertApplianceType(textField.Text);
_owner.refreshTypeCombo();
}
else if (windowType == 2)
{
data.insertManufacturerSimple(textField.Text);
_owner.refreshManuCombo();
}
else if (windowType == 3)
{
data.insertModelSimple(manufacturer_id, textField.Text);
_owner.refreshModelCombo();
}
this.Close();
}
private void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
}
Now, my issue is that the 2 forms that call this third form are different - thus my only thought of how to solve this would be to duplicate some of the code and modify the methods (you can see the second constructor already added).
Instead of having multiple constructors, and duplicated methods (in this class, or in a seperate one) is there a way whereby I can use the same constructor but different owners depending on the form that calls it?
You have too much implementation in your child form. The way I would tackle this is to
Add a property to your child form:
public string InstructionLabel { get; set; }
This allows your parent forms to individually set the label text when instantiating the form, and also set up an event handler for when the form is closing. So your parent form would have code something like
var newItemForm = new taskNewDropdownEntry();
newItemForm.InstructionLabel = "Input the new appliance type below";
newItemForm.FormClosing += new FormClosingEventHandler(ChildFormClosing);
Then somewhere early in your child form's life cycle (FormLoading event) set
instructionLabel.Text = InstructionLabel;
Then also add a property in the child form for
public string NewItem { get; set; }
your child form should set this public property in the btnOK_Click event
private void btnOK_Click(object sender, EventArgs e)
{
this.NewItem =textField.Text;
}
Then your parent form listens for a FormClosing event, and when it hits that event it takes the NewItem text, adds it to the relevant combo and refreshes it. So in the parent form, the handler looks like
private void ChildFormClosing(object sender, FormClosingEventArgs e)
{
sqlMod data = new sqlMod();
data.insertApplianceType(textField.Text);
refreshTypeCombo();
}
Pretty hard to understand the question but code speaks for all.
There are 2 options, worse (because keeping the parent reference is not a good practice first of all):
create an interface that both classes taskWindow and applianceWindow (where is the naming convention for god's sake!) implement, ex
intrerface IRefreshable {
void refreshManuCombo();
}
then constructor and your poperty can have type of IRefreshable
IRefreshable _owner;
public taskNewDropdownEntry(IRefreshable owner, int type)
{
InitializeComponent();
this._owner = owner;
}
better option, use child form events like Closed to implement refreshing logic in parent. You just need to register event handler before showing the form and voila. Check examples here:
https://msdn.microsoft.com/en-us/library/system.windows.forms.form.closed(v=vs.110).aspx
You can also implement your own public form event for more custom usage (ex. DataChanged, ResultGenerated).

I need to access a form control from another class (C#)

On my form, I have one Panel container, named "panelShowList".
On my project, i added a new class, which look like this:
class myNewClass
{
private int newPanelPos = 30;
private const int spaceBetweenElements = 30;
private const int panelWidth = 90;
private const int panelHeight = 40;
private int elementPos = 0;
private ArrayList myPanels = new ArrayList() { };
// some irelevant methods
public void addElementPanels(Panel dataPanel, Panel nextPanel)
{
myPanels.Add(dataPanel);
myPanels.Add(nextPanel);
}
public void displayPanels()
{
foreach (Panel tmp in myPanels)
{
// here i'm stuck
// i need to do something like this :
// myMainForm.panelShowList.Controls.Add(tmp);
// of course this is wrong! but i need a method to acces that control
}
}
}
Basically, I need a way to add all Panels from my ArrayList on "panelShowList" control from my form.
I tried something like this:
public void displayPanels()
{
frmMain f = new frmMain();
foreach (Panel tmp in myPanels)
{
f.display(tmp);
// where display(Panel tmp) is a function in my Form, who access
// "panelShowList" control and add a new Panel
}
}
But it only works if i do this:
f.ShowDialog();
and another form is open.
Any suggestions will be appreciated.
Maybe a bit late, but by all means, here is another approach, that's still more clean than David's approach:
You should add an EventHandler in your MyNewClass. Then you can subscribe to that event from within your form.
public partial class Form1 : Form
{
private readonly MyNewClass _myNewClass;
public Form1()
{
InitializeComponent();
_myNewClass = new MyNewClass();
_myNewClass.DisplayPanelsInvoked += DisplayPanelsInvoked;
}
private void DisplayPanelsInvoked(object sender, DisplayPanelsEventArgs e)
{
var panels = e.Panels; // Add the panels somewhere on the UI ;)
}
}
internal class MyNewClass
{
private IList<Panel> _panels = new List<Panel>();
public void AddPanel(Panel panel)
{
_panels.Add(panel);
}
public void DisplayPanels()
{
OnDisplayPanels(new DisplayPanelsEventArgs(_panels));
}
protected virtual void OnDisplayPanels(DisplayPanelsEventArgs e)
{
EventHandler<DisplayPanelsEventArgs> handler = DisplayPanelsInvoked;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<DisplayPanelsEventArgs> DisplayPanelsInvoked;
}
internal class DisplayPanelsEventArgs : EventArgs
{
public DisplayPanelsEventArgs(IList<Panel> panels)
{
Panels = panels;
}
public IList<Panel> Panels { get; private set; }
}
In my opinion it's a better solution, because you don't need to provide a reference of the form to the MyNewClass instance. So this approach reduces coupling, because only the form has a dependency to the MyNewClass.
If you always want to "update" the form whenever a panel is added, you could remove the DisplayPanels-method and shorten the code to this:
public partial class Form1 : Form
{
private readonly MyNewClass _myNewClass;
public Form1()
{
InitializeComponent();
_myNewClass = new MyNewClass();
_myNewClass.PanelAdded += PanelAdded;
}
private void PanelAdded(object sender, DisplayPanelsEventArgs e)
{
var panels = e.AllPanels; // Add the panels somewhere on the UI ;)
}
}
internal class MyNewClass
{
private IList<Panel> _panels = new List<Panel>();
public void AddPanel(Panel panel)
{
_panels.Add(panel);
OnPanelAdded(new DisplayPanelsEventArgs(_panels, panel)); // raise event, everytime a panel is added
}
protected virtual void OnPanelAdded(DisplayPanelsEventArgs e)
{
EventHandler<DisplayPanelsEventArgs> handler = PanelAdded;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<DisplayPanelsEventArgs> PanelAdded;
}
internal class DisplayPanelsEventArgs : EventArgs
{
public DisplayPanelsEventArgs(IList<Panel> allPanels, Panel panelAddedLast)
{
AllPanels = allPanels;
PanelAddedLast = panelAddedLast;
}
public IList<Panel> AllPanels { get; private set; }
public Panel PanelAddedLast { get; private set; }
}
and another form is open
That's because you're creating an entirely new form:
frmMain f = new frmMain();
If you want to modify the state of an existing form, that code will need a reference to that form. There are a number of ways to do this. One could be to simply pass a reference to that method:
public void displayPanels(frmMain myMainForm)
{
foreach (Panel tmp in myPanels)
{
// myMainForm.panelShowList.Controls.Add(tmp);
// etc.
}
}
Then when your main form invokes that method, it supplies a reference to itself:
instanceOfNewClass.displayPanels(this);
Though, to be honest, it's not really clear what sort of structure you're going for here. If code is modifying a form then I imagine that code should be on that form. It can certainly be organized into a class, but perhaps that can be an inner class of that form since nothing else needs to know about it.
I'm also concerned that your implementation of myNewClass requires methods to be invoked in a specific order. Any given operation on an object should fully encapsulate the logic to complete that operation. Some of that initialization logic may belong in the constructor if the object isn't in a valid state until that logic is completed.
This is all a bit conjecture though, since the object structure isn't clear here.

Changing the properties of the active control automatically

Please consider that im a newcomer to c#. After scanning about 700 posts i decided to post one more question:
On my windows form (c#) I have some controls including textboxes, checkboxes and so on.
I want to change the backcolor whenever the controls become active.
I know i could raise 'enter' and 'leave' events for each control to change the corresponding properties but there should be another way.
Simply hook Enter and Leave events - toggling the color in each. Save the last color saved in OnEnter to use in OnLeave
public Form1()
{
InitializeComponent();
var lastColorSaved = Color.Empty;
foreach(Control child in this.Controls)
{
child.Enter += (s, e) =>
{
var control = (Control)s;
lastColorSaved = control.BackColor;
control.BackColor = Color.Red;
};
child.Leave += (s, e) =>
{
((Control)s).BackColor = lastColorSaved;
};
}
}
You customize control classes just like you customize any class, you derive your own class and override the virtual methods. Arbitrarily:
using System;
using System.Drawing;
using System.Windows.Forms;
class MyTextBox : TextBox {
protected override void OnEnter(EventArgs e) {
prevColor = this.BackColor;
this.BackColor = Color.Cornsilk;
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e) {
this.BackColor = prevColor;
base.OnLeave(e);
}
private Color prevColor;
}
Now any MyTextBox you drop on the form will have this behavior without having to implement events. Although there's certainly nothing wrong with using events.
Create a class (eg. ControlColorizer) and in its constructor pass:
1) The backcolor for the 'active control' and save to a internal Color variable
2) a variable length Control array
In the contructor add the same event handler for OnEnter and OnLeave on each control
In the OnEnter event set the backcolor
In the OnLeave event set the standard background color
The advantage is all in the use of the class:
1) Declare a global instance in your form class
2) Initialize in the form contructor after the InitializeComponent.
3) Forget everything else. No other code required
So let me explain everything with code:
This will go in a file called ControlColorizer.cs
public class ControlColorizer
{
private Color _setBColor = SystemColors.Window;
public ControlColor(Color bkg, params Control[] ctls)
{
_setBColor = bkg;
foreach (Control o in ctls)
{
o.Enter += new EventHandler(o_Enter);
o.Leave += new EventHandler(o_Leave);
}
}
private void o_Enter(object sender, EventArgs e)
{
if (sender is Control)
{
Control c = (Control)sender;
c.BackColor = _setBColor;
}
}
private void o_Leave(object sender, EventArgs e)
{
Control c = sender as Control;
c.BackColor = SystemColors.Window;
}
Now, in every form contructor where you need the functionality you have this
ControlColirizer _ccz;
public Form1()
{
InitializeComponent();
// Create an instance of ControlColorizer, pass the background color
// the list of Controls and that's all
_ccz = new ControlColorizer(Color.LightYellow, this.TextBox1,
this.TextBox2, this.TextBox3, this.TextBox4);
}

Categories