How to remove and re-attatch EventHandler to controls in c#? - c#

I've read this answer.
It just tell me how to remove the click event from a button control. I want to know how to change the code (especially the GetField("EventClick"... part!) so I can do the same thing with other controls. For example, I want to remove the TextChanged event of a TextBox.
And I also want to know how to re-attach the event handler.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (textBox1.Text.Length < 10) return;
MessageBox.Show("do something");
}
private void Form1_Load(object sender, EventArgs e)
{
Tools.mkTextBoxWithPlaceholder(textBox1, "hi, input here...");
}
}
class Tools
{
public static void mkTextBoxWithPlaceholder(TextBox tb, string placeholder)
{
tb.Tag = placeholder;
tb.GotFocus += new EventHandler(tb_GotFocus);
tb.LostFocus += new EventHandler(tb_LostFocus);
}
private static void tb_GotFocus(object sender, EventArgs e)
{
TextBox tb = sender as TextBox;
tb.Clear();
}
private static void tb_LostFocus(object sender, EventArgs e)
{
TextBox tb = sender as TextBox;
//TODO Remove the TextChanged event handler here.
tb.Text = tb.Tag as string;
//TODO Reattach the TextChanged event handler here.
}
}
With the code above, the textBox1 will have a function like placeholder.Maybe you can just give me some help on how to add the placeholder to a textbox. That's what I want.

Remove
Mybutton.event -= methodname;
Reattach
Mybutton.event += methodname;

Here is a solution to your problem, according to this post of mine.
These helper methods let you manipulate any event for a specific control:
// Also searches up the inheritance hierarchy
private static FieldInfo GetStaticNonPublicFieldInfo(Type type, string name)
{
FieldInfo fi;
do
{
fi = type.GetField(name, BindingFlags.Static | BindingFlags.NonPublic);
type = type.BaseType;
} while (fi == null && type != null);
return fi;
}
private static object GetControlEventKey(Control c, string eventName)
{
Type type = c.GetType();
FieldInfo eventKeyField = GetStaticNonPublicFieldInfo(type, "Event" + eventName);
if (eventKeyField == null)
{
if (eventName.EndsWith("Changed"))
eventKeyField = GetStaticNonPublicFieldInfo(type, "Event" + eventName.Remove(eventName.Length - 7)); // remove "Changed"
else
eventKeyField = GetStaticNonPublicFieldInfo(type, "EVENT_" + eventName.ToUpper());
if (eventKeyField == null)
{
// Not all events in the WinForms controls use this pattern.
// Other methods can be used to search for the event handlers if required.
return null;
}
}
return eventKeyField.GetValue(c);
}
private static EventHandlerList GetControlEventHandlerList(Control c)
{
Type type = c.GetType();
PropertyInfo pi = type.GetProperty("Events",
BindingFlags.NonPublic | BindingFlags.Instance);
return (EventHandlerList)pi.GetValue(c, null);
}
and then you can use them to temporarily detach an event handler:
private static void tb_LostFocus(object sender, EventArgs e)
{
TextBox tb = (TextBox)sender;
var eventList = GetControlEventHandlerList(tb);
var eventKey = GetControlEventKey(tb, "TextChanged");
// Remove the handlers
var handlers = eventList[eventKey];
eventList.RemoveHandler(eventKey, handlers);
// ... perform your task
// Reattach the handlers
eventList.AddHandler(eventKey, handlers);
}
If you want to know what is really happening here, read on.
Windows Forms uses the EventHandlerList class to maintain control events. Every event is accessed using a simple key of type object. Keys are stored in private fields of the control. The only way to access this data is using reflection, but we should know the name of the event key field. By decompiling the the Control class and it's descendants, we can see that the keys use different names. I extracted three common naming pattern among the keys, and used them in the GetControlEventKey method.
This is the mechanism that WinForms controls use to hold the event handlers. There is no special thing about it. It is just a design choice.

Related

Filtering inner mechanism

I have Checkbox:
<CheckBox Name="Filtering" Grid.Row="1" Grid.Column="1"
Checked="AddFiltering" Unchecked="RemoveFiltering"
Margin="8" Style="{StaticResource checkBoxStyle}">Show only bargains</CheckBox>
I have AddFiltering method:
private void AddFiltering(object sender, RoutedEventArgs args)
{
listingDataView.Filter += new FilterEventHandler(ShowOnlyBargainsFilter);
}
I have ShowOnlyBargains method:
private void ShowOnlyBargainsFilter(object sender, FilterEventArgs e)
{
AuctionItem product = e.Item as AuctionItem;
if (product != null)
{
// Filter out products with price 25 or above
if (product.CurrentPrice < 25)
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
}
And I also have a problem here :-)
ShowOnlyBargainsFilter returns void.
All it changes really is argument value i.e:
e.Accepted = true;
I guess that this change is sent back to some kind of a caller?
How does it work?
Thank you!
You obviously got the code from MSDN, so I will try to explain how this works. Note that if you use Reflector on the CollectionViewSource, the code will likely be different, as this is just a rough guess at what they are doing.
So, since .NET (C#) passes objects by reference, when the event is raised, you will be modifying the same object that the original CollectionViewSource sent in the event. This means that it can read the state after all EventHandlers have completed. That means it can have filtering code something like this:
private void ApplyFilter()
{
List<object> acceptedItems = new List<object>();
foreach (object o in this.innerCollection)
{
FilterEventArgs e = new FilterEventArgs(o);
Filter(this, e); // raise the Filter event
if (e.Accepted)
acceptedItems.Add(o);
}
this.filteredItems = acceptedItems;
}
When the CollectionViewSource raises the Filter event, any event handler registered with the event will be called. Events in .NET are a concept that is described on MSDN also. That link is from the first version of .NET, but is still applicable. If you want more information, you can also look up multicast delegates, as the event is a special case of a multicast delegate.
C# is a reference based system. So you change the obejct "e" by reference. So the object which is given as the Parameter will be changed direct.
Hiere a short example:
namespace Streamtest
{
class Program
{
static void Main(string[] args)
{
Test cTest = new Test();
cTest.Name = "Hello!";
Do(cTest);
Console.WriteLine(cTest.Name);
Console.ReadLine();
}
static void Do(Test Test)
{
Test.Name = Test.Name + " " + Test.Name;
}
}
public class Test
{
public string Name
{
get;
set;
}
}
}

Form Validation based on both Combo boxes and TextBoxes

Currently I am using a class as follows to check if yhe TextBoxes on the form that I register to it, all have a non-blank text or not and it work fine, But now I want to also add a ComboBox to this validation so that validation should be done when none of the registered textboxes AND Combobxes on the form are blank.
So if I want to add a Combobx to this class, how should it look like? what is the best pracitce to do it?
public class InputValidator
{
public delegate void ValidationDoneDelegate(bool enable);
public event ValidationDoneDelegate ValidationDone;
public void RegisterTextBox(TextBox tb)
{
tb.TextChanged += (s, e) => this.Validate(s);
}
private void Validate(object sender)
{
var t = sender as TextBox;
if (t == null)
{
return;
}
var validationDone = ValidationDone;
if (validationDone != null)
{
validationDone(!string.IsNullOrEmpty(t.Text));
}
}
}
I have two lists setup which will hold all the TextBox and ComboBox references. When it is time to validate, we will check all of the registered controls and if ANY of them are empty, we will be invalid. I think you will also be able to see how this can easily be extended to support additional control types.
public class InputValidator
{
public delegate void ValidationDoneDelegate(bool enable);
public event ValidationDoneDelegate ValidationDone;
private List<TextBox> textBoxes = new List<TextBox>();
private List<ComboBox> comboBoxes = new List<ComboBox>();
public void RegisterTextBox(TextBox tb)
{
tb.TextChanged += (s, e) => this.Validate();
textBoxes.Add(tb);
}
public void RegisterComboBox(ComboBox cb)
{
cb.SelectedValueChanged += (s, e) => this.Validate();
comboBoxes.Add(cb);
}
private void Validate()
{
bool isValid = true;
foreach (var tb in textBoxes)
{
if (string.IsNullOrEmpty(tb.Text))
isValid = false;
}
if (isValid)
{
foreach (var cb in comboBoxes)
{
if (cb.SelectedItem == null)
isValid = false;
}
}
var validationDone = ValidationDone;
if (validationDone != null)
{
validationDone(isValid);
}
}
}
Now I'm not sure exactly what you consider to be invalid input for the ComboBox. So you may need to tweak this line to meet your needs: isValid = cb.SelectedItem != null;. I have assemed that as long as something is selected that the selection is valid.
EDIT: I had forgotten to switch the last line to validationDone(isValid);

How Dependency Property works?

I have next dependency property:
public static DependencyProperty RequestObjectProperty = DependencyProperty.Register("RequestObject", typeof(RegistrationCardSearch), typeof(RegCardSearchForm),new UIPropertyMetadata(Changed));
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("Property Changed!!!");
}
public RegistrationCardSearch RequestObject
{
get
{
return (RegistrationCardSearch)GetValue(RequestObjectProperty);
}
set
{
SetValue(RequestObjectProperty, value);
}
}
and "Changed" Method which have to fire when my dependency property changes. My property type is RegistrashionCardSearch (class) . When I change property values of class in dependency property, Property changed call back not fired. Why?? My RegistrashionCardSearch class implement INotifePropertyChanged interface
The changed event is fired only when the property itself changes, not when you change values inside this property. To given an example that will cause the changed event to fire:
var requestObject = myObject.RequestObject;
myObject.RequestObject = new RegistrationCardSearch() { ... };
A changed event will fire for the last line of this example because the property itself changes to another value.
However, when you do something like this:
myObject.RequestObject.SomeProperty = newPropertyValue;
the changed event will not fire because you haven't changed the RequestObject property itself, only some value inside the property.
Ronald already nicely explained why your approach doesn't work. To get it to work, you need to subscribe to the PropertyChanged event of your RequestObject:
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var form = (RegCardSearchForm)d;
if (e.OldValue != null)
((RegistrationCardSearch)e.OldValue).PropertyChanged -= form.RequestObject_PropertyChanged;
if (e.NewValue != null)
((RegistrationCardSearch)e.NewValue).PropertyChanged += form.RequestObject_PropertyChanged;
}
private void RequestObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
MessageBox.Show("Property " + e.PropertyName + " changed!");
}

How to add validation to PropertyGrid's CollectionEditor?

I'm using PropertyGrid to edit an object containing a collection.
Collection is edited using the CollectionEditor.
I have to make sure elements in collection are unique.
How can I add validation to CollectionEditor:
By either overloading CollectionEditor's OnFormClosing
Or adding validation for creating/editing items?
You can create your own collection editor, and hook into events on the default editor's controls. You can use these events to, say, disable the OK button. Something like:
public class MyCollectionEditor : CollectionEditor
{
private static Dictionary<CollectionForm, Button> okayButtons
= new Dictionary<CollectionForm, Button>();
// Inherit the default constructor from CollectionEditor
public MyCollectionEditor(Type type)
: base(type)
{
}
// Override this method in order to access the containing user controls
// from the default Collection Editor form or to add new ones...
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
collectionForm.FormClosed +=
new FormClosedEventHandler(collectionForm_FormClosed);
collectionForm.Load += new EventHandler(collectionForm_Load);
if (collectionForm.Controls.Count > 0)
{
TableLayoutPanel mainPanel = collectionForm.Controls[0]
as TableLayoutPanel;
if ((mainPanel != null) && (mainPanel.Controls.Count > 7))
{
// Get a reference to the inner PropertyGrid and hook
// an event handler to it.
PropertyGrid propertyGrid = mainPanel.Controls[5]
as PropertyGrid;
if (propertyGrid != null)
{
propertyGrid.PropertyValueChanged +=
new PropertyValueChangedEventHandler(
propertyGrid_PropertyValueChanged);
}
// Also hook to the Add/Remove
TableLayoutPanel buttonPanel = mainPanel.Controls[1]
as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button addButton = buttonPanel.Controls[0] as Button;
if (addButton != null)
{
addButton.Click += new EventHandler(addButton_Click);
}
Button removeButton = buttonPanel.Controls[1] as Button;
if (removeButton != null)
{
removeButton.Click +=
new EventHandler(removeButton_Click);
}
}
// Find the OK button, and hold onto it.
buttonPanel = mainPanel.Controls[6] as TableLayoutPanel;
if ((buttonPanel != null) && (buttonPanel.Controls.Count > 1))
{
Button okayButton = buttonPanel.Controls[0] as Button;
if (okayButton != null)
{
okayButtons[collectionForm] = okayButton;
}
}
}
}
return collectionForm;
}
private static void collectionForm_FormClosed(object sender,
FormClosedEventArgs e)
{
CollectionForm collectionForm = (CollectionForm)sender;
if (okayButtons.ContainsKey(collectionForm))
{
okayButtons.Remove(collectionForm);
}
}
private static void collectionForm_Load(object sender, EventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void propertyGrid_PropertyValueChanged(object sender,
PropertyValueChangedEventArgs e)
{
ValidateEditValue((CollectionForm)sender);
}
private static void addButton_Click(object sender, EventArgs e)
{
Button addButton = (Button)sender;
ValidateEditValue((CollectionForm)addButton.Parent.Parent.Parent);
}
private static void removeButton_Click(object sender, EventArgs e)
{
Button removeButton = (Button)sender;
ValidateEditValue((CollectionForm)removeButton.Parent.Parent.Parent);
}
private static void ValidateEditValue(CollectionForm collectionForm)
{
if (okayButtons.ContainsKey(collectionForm))
{
Button okayButton = okayButtons[collectionForm];
IList<MyClass> items = collectionForm.EditValue as IList<MyClass>;
okayButton.Enabled = MyCollectionIsValid(items);
}
}
private static bool MyCollectionIsValid(IList<MyClass> items)
{
// Perform validation here.
return (items.Count == 2);
}
}
You will also need to add an Editor attribute to you collection:
class MyClass
{
[Editor(typeof(MyCollectionEditor),
typeof(System.Drawing.Design.UITypeEditor))]
List<Foo> MyCollection
{
get; set;
}
}
NOTE: I found that the value of items in removeButton_Click was not correct - so some tweaking may need to take place.
Try collectionForm.Context.Instance and typecast it to your data type this should do the trick.

Programmatically click on a CheckBox

Is there a way to programmatically generate a click event on a CheckBox? I am looking for an equivalent to Button.PerformClick();
Why do you need to simulate a click, doesn't this line of code fits your need?
myCheckBox.Checked = !myCheckBox.Checked;
If you need to execute logic when the state of the CheckBox changes, you should use CheckedChanged event instead of Click.
private void CheckBox1_CheckedChanged(Object sender, EventArgs e)
{
MessageBox.Show("You are in the CheckBox.CheckedChanged event.");
}
Those solutions above calls Checkbox.CheckedChanged event.
If you want to explicitly call Click event you can this:
checkBox1_Click(checkBox1, null);
Why do you want to generate a click event on the CheckBox?
If you want to toggle it's value:
theCheckBox.Checked = !theCheckBox.Checked;
If you want to trigger some functionality that is connected to the Click event, it's a better idea to move the code out from the Click event handler into a separate method that can be called from anywhere:
private void theCheckBox_Click(object sender, EventArgs e)
{
HandleCheckBoxClick((CheckBox)sender);
}
private void HandleCheckBoxClick(CheckBox sender)
{
// do what is needed here
}
When you design your code like that, you can easily invoke the functionality from anywhere:
HandleCheckBoxClick(theCheckBox);
The same approach can (and perhaps should) be used for most control event handlers; move as much code as possible out from event handlers and into methods that are more reusable.
I'm still setting up a new workstation so I can't research this properly at the moment, but with UI Automation maybe it's possible that the checkbox supports the IInvokeProvider and you can use the Invoke method?
I don't think you can generate a click event in that way without calling the checkBox_Click event handler directly. But you can do this:
checkBox.Checked = !checkBox.Checked;
The CheckedChanged handler will still be called even if you do this.
The Button PerformClick() method validates the active control, testing whether the active control can lose the current focus. There are two ways to possibly do the same thing for a CheckBox. Approach #1 is to use reflection to call the methods that are internal to the Control class:
public class CheckBoxPerformClick : CheckBox {
private readonly static MethodInfo callValidateActiveControl;
private readonly static PropertyInfo propValidationCancelled;
static CheckBoxPerformClick() {
try {
Type ty = typeof(Control);
Type outBool = Type.GetType("System.Boolean&");
callValidateActiveControl = ty.GetMethod("ValidateActiveControl", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { outBool }, null);
propValidationCancelled = ty.GetProperty("ValidationCancelled", BindingFlags.Instance | BindingFlags.NonPublic);
} catch {}
}
public CheckBoxPerformClick() : base() {
this.Text = "Checkbox";
this.Appearance = Appearance.Button;
}
public void PerformClick() {
if (callValidateActiveControl != null && propValidationCancelled != null) {
try {
Object[] args = new Object[1];
bool validate = (bool) callValidateActiveControl.Invoke(this, args);
bool validatedControlAllowsFocusChange = (bool) args[0];
if (validate || validatedControlAllowsFocusChange) {
bool cancelled = (bool) propValidationCancelled.GetValue(this);
if (!cancelled) {
ResetFlagsandPaint();
OnClick(EventArgs.Empty);
}
}
} catch {
}
}
}
}
Approach #2 tries to do the same thing, but without reflection:
public class CheckBoxPerformClick2 : CheckBox {
public CheckBoxPerformClick2() : base() {
this.Text = "Checkbox";
this.Appearance = Appearance.Button;
}
public void PerformClick() {
bool validate = CanPerformClick();
if (validate) {
ResetFlagsandPaint();
OnClick(EventArgs.Empty);
}
}
// before allowing a click, make sure this control can receive the focus, and that other controls don't require validation
public bool CanPerformClick() {
if (!CanSelect)
return false;
Control c = this.Parent;
while (c != null) {
if (c is ContainerControl)
break;
c = c.Parent;
}
bool valid = true;
if (c is ContainerControl) {
var cc = (ContainerControl) c;
valid = cc.Validate(true);
}
return valid;
}
}

Categories

Resources