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);
}
Related
I want when the project is starting to create a custom event for every click event inside my textbox.
Then when a new textbox will be created, automatically use this click event.
I don't want to create a custom control for this. I want to assign it once from a method.
Or if it is possible to create a default constructor for all my elements without creating a custom control.
For example, I don't want to create this. Cause I need to replace all my controls
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace DXApplication1
{
[ToolboxItem(true)]
public class Class1 : TextBox
{
public Class1()
{
this.Click += Class1_Click;
}
private void Class1_Click(object sender, EventArgs e)
{
Console.WriteLine("Click Event");
}
}
}
I have 1000 textboxes on my project. I don't want to add in each onclick method
Is there any way to add a click event to all my textboxes in the project?
I want to add just one line of code to the program. cs. Is it possible?
I'd suggest that you simple traverse all of the controls on your form after you have placed them and then attach the event.
Try something like this:
IEnumerable<TextBox> AllTextBoxes(ScrollableControl #this)
{
foreach (Control control in #this.Controls)
{
if (control is ScrollableControl sc)
{
foreach (TextBox tb1 in AllTextBoxes(sc))
{
yield return tb1;
}
}
if (control is TextBox tb2)
{
yield return tb2;
}
}
}
foreach (var tb in AllTextBoxes(this))
{
tb.Click += (_, _) => MessageBox.Show("Hello");
}
If you derive your control from an existing control you can override the OnClick method instead of subscribing to the event.
public class TextBoxEx: TextBox
{
public TextBoxEx()
{
}
protected override void OnClick(EventArgs e)
{
Console.WriteLine("Click Event");
base.OnClick(e);
}
}
Then you can replace the type TextBox by TextBoxEx (with find/replace) in the form's .designer.cs file without having to delete and re-insert your textboxes.
Once you have compiled this code, the new textbox appears in the Toolbox window and you can drag and drop it to your form, just as with the standard textbox.
If you have a textBox1_Click method in your form, you can select this same method as event handler for all your textboxes in the properties window: How to: Connect Multiple Events to a Single Event Handler in Windows Forms
As I understand it, you have three requirements:
When the project starts, attach a click event to all TextBox instances already created in the Form designer.
When a new text box is created (programmatically or by user interaction) attach the click event to the new textbox automatically.
Implement this functionality without making a custom class.
This answer shows one way to meet these three objectives.
Utility
First, make a utility that can iterate all of the controls in the Form, but also all the controls of its child controls.
void IterateControlTree(Action<Control> action, Control control = null)
{
if (control == null)
{
control = this;
}
action(control);
foreach (Control child in control.Controls)
{
IterateControlTree(action, child);
}
}
Attach handler to all existing TextBox controls
Using this utility, initialize any textboxes added in design mode to route to the click handler.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
IterateControlTree((control) =>
{
// Attach click handlers to the textboxes
// already added in the Forms designer.
if (control is TextBoxBase)
{
control.Click += onAnyClickTextBox;
}
});
.
.
.
}
// Show the name of the clicked text box.
private void onAnyClickTextBox(object sender, EventArgs e)
{
if(sender is Control control)
{
textBox1.Text = $"Clicked: {control.Name}";
}
}
}
Attach handler automatically to new TextBox controls
Iterate a second time to attach the ControlAdded event to every control. This way, new TextBox instances can be detected in order to attach the Click event.
public MainForm()
{
.
.
.
IterateControlTree((control) =>
{
control.ControlAdded += (sender, e) =>
{
// Get notified when any control collection is changed.
if(e.Control is TextBoxBase textbox)
{
textbox.Click += onAnyClickTextBox;
}
};
});
}
Testing
// FOR TESTING PURPOSES
int _id = 1;
private void onClickNew(object sender, EventArgs e)
{
flowLayoutPanel.Controls.Add(new TextBox
{
Name = $"dynamicTextBox{_id}",
PlaceholderText = $"TextBox{_id}",
});
_id++;
}
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);
}
}
}
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()}
I've made a Windows Forms solution. In the main shell, there is added a MenuStrip, and it's possible to add more Views onto it.
The problem is, that when I add/open a new View, it is opened behind the MenuStrip.
Somehow, I want the MenuStrip to have a border, so it is not possible to drag things behind it, but I have no idea how.
The same case should be with other Views.
You should set the Dock property for the control that you want to add.
OK, I have a solution - I don't totally like it but it works! You will need the usual MDI suspects in terms of flags, etc.
The main form that is the MDI container needs to have something like:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
int BodyCount = 0;
private void fileToolStripMenuItem_Click(object sender, EventArgs e)
{
MDIChildForm child = new MDIChildForm();
child.TitleText = String.Format("Child window {0}", ++BodyCount);
child.MdiParent = this;
child.Show();
}
/*
** This could be fun - shouldn't recurse!
*/
public void ShifTheChild(MDIChildForm spoiltBrat)
{
var m = menuStrip1.Height;
if (spoiltBrat.Location.Y < m)
spoiltBrat.Location = new Point(spoiltBrat.Location.X, 0);
return;
}
}
The child forms need the location changed event hooking:
public partial class MDIChildForm : Form
{
public String TitleText
{
get { return this.Text; }
set { this.Text = value; }
}
MainForm parent = null;
public MDIChildForm()
{
InitializeComponent();
this.ShowIcon = false;
}
private void MDIChildForm_LocationChanged(object sender, EventArgs e)
{
if (parent != null)
parent.ShifTheChild(this);
}
private void MDIChildForm_Load(object sender, EventArgs e)
{
parent = this.MdiParent as MainForm;
}
}
When you move a child into the twilight zone under the menu it will be snapped back out - the method that moves it will cause the event to fire again but the second time nothing should happen (so no recursion).
I don't like this solution simply because I can't get my brain around whether there is a condition that would make it recurse, and I don't like uncertainty.
Good luck.
I have DragDrop and DragEnter events on my SplitContainer.Panel:
splitContainer.Panel.DragDrop += new System.Windows.Forms.DragEventHandler(this.splitContainerPanelDragDrop);
splitContainer.Panel.DragEnter += new System.Windows.Forms.DragEventHandler(this.splitContainerPanelDragEnter);
It works perfect with every control inside SplitContainer.Panel except RichTextBox controls.
How it looks like:
So DragDrop/DragEnter works perfectly in every control inside SplitContainer except controls which is marked yellow color.
What I tried:
1) Set
RichTextBox.AllowDrop = false;
So I even DragEnter is unavailable with "action is not allowed" cursor.
2) Set
RichTextBox.AllowDrop = true;
After this cursor is ok, but it doesnt work because expects additional DragEventHandler in other case it doesnt work.
3) Set
RichTextBox.EnableAutoDragDrop=false;
RichTextBox.AllowDrop=true;
Same result as 2) variant.
I dont want to set DragDrop/DragEnter event for every RichTextBox inside SplitContainer because inside FlowLayoutPanel they are created dynamically.
The question is: is there any method like e.PreventDefault analog in C#? Or what can I do except setting events for every RichTextBox to make it work?
This worked for me
I created 2 custom controls
Custom SplitControl
public partial class SplitControlCustom : SplitContainer
{
public SplitControlCustom()
{
InitializeComponent();
}
public void ForceDrageDrop(DragEventArgs eventArgs)
{
OnDragDrop(eventArgs);
}
public void ForceDragEnter(DragEventArgs eventArgs)
{
OnDragEnter(eventArgs);
}
}
Custom RichTextBox
public partial class RichTextBoxCustom : RichTextBox
{
public RichTextBoxCustom()
{
InitializeComponent();
this.AllowDrop = true;
}
protected override void OnDragEnter(DragEventArgs drgevent)
{
SplitControlCustom parentSplitControl = Parent.Parent as SplitControlCustom;
if (parentSplitControl != null)
{
parentSplitControl.ForceDragEnter(drgevent);
}
}
protected override void OnDragDrop(DragEventArgs drgevent)
{
SplitControlCustom parentSplitControl = Parent.Parent as SplitControlCustom;
if (parentSplitControl != null)
{
parentSplitControl.ForceDrageDrop(drgevent);
}
}
}
Please let me know if it worked
I don't see how you can make this work directly. But then, since you are already willing to add a few lines of code while generating the controls, why not add the necessary events via a few lines of Lambda..:
Let's assume you have just created a RichTextBox and are ready to add it to some Controls collection..:
RichTextBox richTextBox = new RichTextBox ();
...
richTextBox.AllowDrop = true;
richTextBox.DragEnter += (ss, ee) => { ee.Effect = DragDropEffects.Copy; };
richTextBox.DragOver += (ss, ee) => { ee.Effect = DragDropEffects.Copy; };
richTextBox.DragDrop += (ss, ee)
=> { splitContainer.Panel_DragDrop(splitContainer.Panel, ee); };
The first two lambdas set the effect to copy without any checks; of course you will want to add those and pick the appropriate effect.
The third lambda passes the DragEventArgs on the the DragDrop event of the containing panel, so now the RTB is actually 'D&D-through' ..
Just create a custom RichTextBox and override it's DragDrop Events.
public class CustomRichTextBox : RichTextBox
{
#region Methods
#region Overrides
protected override void OnDragEnter(DragEventArgs e)
{
// base.OnDragEnter(e);
}
protected override void OnDragOver(DragEventArgs e)
{
// base.OnDragOver(e);
}
protected override void OnDragLeave(DragEventArgs e)
{
// base.OnDragLeave(e);
}
protected override void OnDrop(DragEventArgs e)
{
// base.OnDrop(e);
}
#endregion
#endregion
}
For some reason RichTextBoxes seem to handle all DragDrop events by default.
In WPF the events will propagate till it gets to the control that expects these events. I'm not sure about WinForms though.
This is what resolved this issue for me.
I had these two events defined, which should have been good enough
MyRichTextBox.DragEnter += MyRichTextBox_DragEnter;
MyRichTextBox.DragDrop += MyRichTextBox_DragDrop;
I found that this one is also apparently needed when using a RichTextBox embedded in
certain controls.
MyRichTextBox.DragOver += MyRichTextBox_DragOver;
private void MyRichTextBox_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}