Reputation: 18675
It's often necessary to prevent some control's event handling when setting is properties in code like check-boxes or radio-buttons etc. To achieve this, I use the following helper:
public static class PropertySetter
{
private static readonly object locker = new object();
public static object CurrentObject { get; private set; }
public static string CurrentPropertyName { get; private set; }
public static void SetValueFor(this object obj, string propertyName, object value)
{
if (obj == null) { throw new ArgumentNullException("obj"); }
if (string.IsNullOrEmpty(propertyName) == true) { throw new ArgumentNullException("'propertyName"); }
PropertyInfo propertyInfo = obj.GetType().GetProperty(propertyName);
if (propertyInfo == null)
{
throw new ArgumentOutOfRangeException("propertyName", "Property not found.");
}
lock (locker)
{
CurrentObject = obj;
CurrentPropertyName = propertyName;
try
{
propertyInfo.SetValue(obj, value, null);
}
catch (Exception)
{
throw;
}
finally
{
CurrentObject = null;
CurrentPropertyName = null;
}
}
}
public static bool CanHandleEventFor(this object obj, string propertyName)
{
return object.ReferenceEquals(obj, CurrentObject) == false && CurrentPropertyName.Equals(propertyName) == false;
}
}
Usage:
checkBox.SetValueFor("Checked", false);
void checkBox_Checked(object sender, EventArgs e)
{
if (sender.CanHandleEventFor("Checked")) return;
// ...
}
However I thougt perhaps there is a better way without writing the if
inside the event handler but to detach all event handlers for some event by either passing it's name as an additinal parameter or the event field itself and after the property is set reattach them. Is that even possible?
Upvotes: 1
Views: 503
Reputation: 18675
I finally managed to solve it:
class Program
{
static void Main(string[] args)
{
var fc = new TestCheckbox();
fc.CheckedChanged += (sender, e) =>
{
int i = 0;
};
// 'CheckedChanged' event is not raised.
fc.SetValueFor(x => x.Checked = true, "CheckedChanged");
// 'CheckedChanged' event it raised.
fc.Checked = false;
}
}
class TestCheckbox
{
private bool _checked;
public event EventHandler CheckedChanged;
public bool Checked
{
get { return _checked; }
set
{
_checked = value;
if (CheckedChanged != null)
{
CheckedChanged(this, EventArgs.Empty);
}
}
}
}
public static class PropertySetter
{
private static readonly object locker = new object();
public static void SetValueFor<T>(this T obj, Action<T> action, string eventName)
{
lock (locker)
{
try
{
// Get event handlers.
Type type = obj.GetType();
var eventInfo = type.GetEvent(eventName);
var fieldInfo = type.GetField(eventName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
var multicastDelegate = fieldInfo.GetValue(obj) as MulticastDelegate;
Delegate[] delegates = multicastDelegate.GetInvocationList();
// Remove event handlers.
foreach (var item in delegates)
{
eventInfo.RemoveEventHandler(obj, item);
}
try
{
action(obj);
}
catch (Exception)
{
throw;
}
finally
{
// Restore event handlers.
foreach (var item in delegates)
{
eventInfo.AddEventHandler(obj, item);
}
}
}
catch (Exception)
{
throw;
}
}
}
}
Unfortunately this works only in theory and doesn't work that easy with .NET because there is no exclusive field for each event and it requires some hacking so I'll stick to the simplest solution which is this one I think:
class Program
{
static void Main(string[] args)
{
CheckBox checkBox = new CheckBox();
checkBox.CheckedChanged += (sender, e) =>
{
if (!sender.CanHandleEvent("CheckedChanged")) return;
int i = 0;
};
checkBox.SetValueFor(x => x.Checked = true, "CheckedChanged");
checkBox.Checked = false;
}
}
public static class PropertySetter
{
private static readonly object locker = new object();
public static object CurrentObject { get; private set; }
public static string CurrentEventName { get; private set; }
public static void SetValueFor<T>(this T obj, Action<T> action, string eventName)
{
lock (locker)
{
CurrentObject = obj;
CurrentEventName = eventName;
try
{
action(obj);
}
catch (Exception)
{
throw;
}
finally
{
CurrentObject = null;
CurrentEventName = null;
}
}
}
public static bool CanHandleEvent(this object obj, string eventName)
{
return !(object.ReferenceEquals(obj, CurrentObject) == true && CurrentEventName.Equals(eventName) == true);
}
}
Upvotes: 1