I have a custom control in C# WinForms called BaseControl and there I have a property called Selected.
I want to have an event SelectedChanged and virtual method OnSelecteChanged in the base control and they should behave in the same manner as we have in Control class for many properties i.e. Click event and OnClick method.
Means anyone who derives from my BaseControl can either bind to the event or can override the OnSelectedChanged method.
So, when the value of Selected property is changed event should be fired and if the method is overridden control should go to that method.
I know how to fire the event but don't know how to do it for method.
Please guide me...
Below is an example of how events should be implemented:
public class BaseControl : Control
{
private object _selected;
public object Selected
{
get { return _selected; }
set
{
if (!Equals(_selected, value))
{
_selected = value;
OnSelectedChanged(EventArgs.Empty);
}
}
}
public event EventHandler SelectedChanged;
protected virtual void OnSelectedChanged(EventArgs e)
{
if (SelectedChanged != null)
SelectedChanged(this, e);
}
}
With this example, you can override OnSelectedChanged in an overriden class, like this:
public class MyControl : BaseControl
{
protected override void OnSelectedChanged(EventArgs e)
{
base.OnSelectedChanged(e);
// My own logic.
}
}
private bool _selected;
public bool Selected
{
get { return _selected; }
set
{
if (value != _selected)
{
_selected = value;
OnSelectedChanged();
}
}
}
public event EventHandler SelectedChanged;
protected virtual void OnSelectedChanged()
{
var handler = SelectedChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
Basically you don't fire the event from your Selected property setter - you call the method, and make the method call the event. Anyone overriding the method should call base.OnSelectedChanged to make sure the event still fires. So your method should look something like this:
protected virtual void OnSelectedChanged(EventArgs e) {
EventHandler handler = Selected; // Or your own delegate variable
if (handler != null) {
handler(this, e);
}
}
Related
I have created various properties inside of a User Control, and have had great success with accessing and editing them. I'm now trying to set up events for a number of these to be raised when one of these properties is changed. I have tried the MSDN example code for doing this (see here), but it is giving me this error when I try to build the solution:
Severity Code Description Project File Line Suppression State
Error CS0079 The event 'AbilityScoreDisplay.AbilityTitleChanged' can only appear on the left hand side of += or -= DnD Character Sheet C:\Users\bradley beasley\Documents\Visual Studio 2019\Projects\DnD Character Sheet\DnD Character Sheet\AbilityScoreDisplay.Designer.cs 199 Active
Another issue that I am having is that I am struggling to figure out how to get that event to appear in the Visual Studio 2019 Designer Properties window.
Here is the code that I have added to the designer file:
namespace DnD_Character_Sheet
{
partial class AbilityScoreDisplay : UserControl
{
public string AbilityTitle
{
get
{
return AbiltyTitleLabel.Text;
}
set
{
AbiltyTitleLabel.Text = value;
Invalidate();
}
}
public int AbilityModifier
{
get
{
return Convert.ToInt32(AbilityModifierTextBox.Text);
}
private set
{
if (value >= 0) AbilityModifierTextBox.Text = String.Format("+{0}", value);
else AbilityModifierTextBox.Text = value.ToString();
Invalidate();
}
}
public int AbilityScore
{
get
{
return Convert.ToInt32(AbilityScoreLabel.Text);
}
set
{
AbilityModifier = (int)(Math.Floor((double)(value) / 2)) - 5;
Invalidate();
}
}
private EventHandler onAbilityTitleChanged { get; set; }
private EventHandler onAbilityScoreChanged { get; set; }
public event EventHandler AbilityTitleChanged
{
add
{
onAbilityTitleChanged += value;
}
remove
{
onAbilityTitleChanged -= value;
}
}
public event EventHandler AbilityScoreChanged
{
add
{
onAbilityScoreChanged += value;
}
remove
{
onAbilityScoreChanged -= value;
}
}
protected virtual void OnAbilityTitleChanged(EventArgs e)
{
AbilityTitleChanged?.Invoke(this, e);
}
protected virtual void OnAbilityScoreChanged(EventArgs e)
{
AbilityScoreChanged?.Invoke(this, e);
}
}
}
The aim is to enable an event to be raised whenever a property is changed so that it can do other stuff elsewhere in the form that the controls will be in. I'm fairly certain that I am missing some very important stuff, or that my code is not that effective at all, but I am learning this kind of code for the first time, and I have tried many different things that have just not worked.
Any help at all would be greatly appreciated :)
I think you are confusing a few concepts. Let's do it step by step.
First, you need to be able to track event handlers:
private EventHandler _onAbilityTitleChanged;
You expose this event through a public property:
public event EventHandler AbilityTitleChanged
{
add
{
_onAbilityTitleChanged += value;
}
remove
{
_onAbilityTitleChanged -= value;
}
}
Finally, you need to fire the event so that all subscribed handlers can react to it. You can do so when the title changes (setter):
public string AbilityTitle
{
get
{
return AbiltyTitleLabel.Text;
}
set
{
AbiltyTitleLabel.Text = value;
//Raising the event!
_onAbilityTitleChanged?.Invoke(this, new EventArgs());
}
}
Other classes can then subscribe to your event:
var control = new AbilityScoreDisplay();
control.AbilityTitleChanged += SomeHandlerForWhenTitleChanges;
private void SomeHandlerForWhenTitleChanges(object sender, EventArgs e)
{
//....
}
You might want to read up a bit on the INotifyPropertyChanged interface as well.
You typically do this by implementing INotifyPropertyChanged. This allows you to use one single event for all the properties. The property name is passed in the event arguments.
partial class AbilityScoreDisplay : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
...
}
In the properties do this (with AbilityModifier as an example):
private int _abilityModifier;
public int AbilityModifier
{
get { return _abilityModifier; }
private set {
if (value != _abilityModifier) {
_abilityModifier = value;
AbilityModifierTextBox.Text = value >= 0
? String.Format("+{0}", value)
: value.ToString();
OnPropertyChanged(nameof(AbilityModifier));
}
}
}
Assuming this event handler
private void ScoreDisplay_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
}
You can subscribe the event with
PropertyChanged += ScoreDisplay_PropertyChanged;
You need to use the add/remove syntax only in rare cases. Typically, when you create your own event store, because you have a lot of events and don't want to consume space for unsubscribed events.
You can use INotifyPropertyChanged together with data binding to immediately update the UI when changes are made to the data. To do this you would create a class with properties and the INotifyPropertyChanged implementation. In the form you then assign an instance of this class to the DataSource of a BindingSource. The controls are then bound to this BindingSource.
Then you can drop all the code used to read from or to write to text boxes or labels etc., as the binding mechanism does it automatically for you.
I have my custom EventArgs in a separate class file that I can reference it later from different classes:
using System;
using System.Collections.Generic;
namespace SplitView
{
public class RowSelectedEventArgs:EventArgs {
public Patient selectedRow { get; set; }
public RowSelectedEventArgs(Patient selectedRow) : base(){
this.selectedRow = selectedRow;
}
}
}
In my MasterViewController I defined my event
public event EventHandler<RowSelectedEventArgs> RowClicked;
In DataSource which is in MasterViewController I can raise the event:
if (this.controller.RowClicked != null) {
this.controller.RowClicked (this, new RowSelectedEventArgs (this.controller.list [indexPath.Row]));
}
As you can see I have a field (controller) in my DataSource with which I reference the event. Now I have a SearchSource with the same concept (also field called controller). Now in SearchSource I want to raise the event:
if (this.controller.RowClicked != null) {
this.controller.RowClicked (this, new RowSelectedEventArgs (this.list [indexPath.Row]));
}
But I get
The event 'SplitView.MasterViewController.RowClicked' can only appear
on the left hand side of += or -= when used outside of the type
'SplitView.MasterViewController'
The only difference is that SearchSource is not part of the class MasterViewController (as it is with DataSource). But the event is public so it should work?
How can I raise the same event from different classes?
You can't directly raise an event outside of the type, which defines this event.
All you can do, is a method, which will raise event from outside:
public sealed class MyClass
{
// this should be called from inside
private void OnSomeEvent()
{
var handler = SomeEvent;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
// this should be called from outside
public void RaiseSomeEvent()
{
OnSomeEvent();
}
public event EventHandler SomeEvent;
// other code here...
}
Is the field conroller in SearchSource also fo type MasterViewController?
It seems that it is a different type.
I can't figure out how to use the CanExecuteChangedEventManager in MyCommand : ICommand.
I tried the following but value is the wrong type:
public event EventHandler CanExecuteChanged
{
add
{
CanExecuteChangedEventManager.AddHandler(this, value);
}
remove
{
CanExecuteChangedEventManager.RemoveHandler(this, value);
}
}
The class I'm writing will look like this but without leaks if possible:
public class ManualRelayCommand : ICommand
{
// CanExecute() and Execute() excluded
public event EventHandler CanExecuteChanged;
public virtual void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
Application.Current.Dispatcher.InvokeAsync(() => handler(this, EventArgs.Empty));
}
}
}
CanExecuteChangedEventManager is a class supporting weak event pattern. It is very common for controls developer to use weak events since you wish the controls not to have strong references to data.
CanExecuteChangedEventManager is used like this.
public event EventHandler<EventArgs> CanExecuteChanged
{
add
{
CanExecuteChangedEventManager.AddHandler(this, value);
}
remove
{
CanExecuteChangedEventManager.RemoveHandler(this, value);
}
}
You need EventHandler<EventArgs> and that would be it.
If you still have questions about this feel free to ask :)
Edit:
You are not creating a control you are just creating a RelayCommand which inherits from ICommand.
You do not need CanExecuteChangedEventManager.
When you inherit from ICommand you must implement public event EventHandler CanExecuteChanged;
You can let it be that way without changing it. You dont change PropertyChanged event either do you? Just let it be there and it will work magically.
Controls who allow commanding know how to subscribe to that event. Just like PropertyChanged event from INotifyPropertyChanged interface. :)
Implemented it like this:
public class ManualRelayCommand
{
///...
public override event EventHandler CanExecuteChanged
{
add
{
InternalCanExecuteChangedEventManager.AddHandler(this, value);
}
remove
{
InternalCanExecuteChangedEventManager.RemoveHandler(this, value);
}
}
private event EventHandler InternalCanExecuteChanged;
public void RaiseCanExecuteChanged()
{
EventHandler handler = InternalCanExecuteChanged;
if (handler != null)
{
if (_raiseCanExecuteOnDispatcher)
{
Application.Current.Dispatcher.BeginInvoke(new Action(() => handler(this, new EventArgs())));
}
else
{
handler(this, new EventArgs());
}
}
}
private class InternalCanExecuteChangedEventManager : WeakEventManager
{
private static readonly InternalCanExecuteChangedEventManager Manager = new InternalCanExecuteChangedEventManager();
static InternalCanExecuteChangedEventManager()
{
SetCurrentManager(typeof(InternalCanExecuteChangedEventManager), Manager);
}
internal static void AddHandler(ManualRelayCommand source, EventHandler handler)
{
Manager.ProtectedAddHandler(source, handler);
}
internal static void RemoveHandler(ManualRelayCommand source, EventHandler handler)
{
Manager.ProtectedRemoveHandler(source, handler);
}
////protected override ListenerList NewListenerList()
////{
//// return new ListenerList();
////}
protected override void StartListening(object source)
{
((ManualRelayCommand)source).InternalCanExecuteChanged += DeliverEvent;
}
protected override void StopListening(object source)
{
((ManualRelayCommand)source).InternalCanExecuteChanged -= DeliverEvent;
}
}
}
public class Basket
{
private int _unitCount;
public int UnitCount
{
get { return _unitCount; }
set
{
_unitCount = Math.Max(0, value);
OnUnitCountChanged(new EventArgs());
}
}
public event EventHandler UnitCountChanged;
public event EventHandler Depleted;
protected virtual void OnUnitCountChanged(EventArgs args)
{
var handler = UnitCountChanged;
if(handler!=null) { handler(this, args); }
if(_unitCount == 0) { OnDepleted(new EventArgs()); }
}
protected virtual void OnDepleted(EventArgs args)
{
var handler = UnitCountChanged;
if(handler!=null) { handler(this, args); }
}
}
Is there a problem with checking the conditions for Depleted and raising that event if necessary within the UnitCountChanged event, or should I be doing both in the UnitCount setter (and anywhere else in a non-trivial example)?
While I have seen it, I would recommend against it and raise the event in methods that it would occur in, like your UnitCount setter. Since you have the virtual access modifier keyword, someone could override the method and if they don't call the base object it wouldn't work as expected.
I'm not a fan of making it more complicated to use my code.
There are times when it may be useful (for example, if you are extending a base class and do not have the ability to override the methods that are raising the events), but in general, I'd recommend against it.
In this case, I'd say it's better to raise both events in the UnitCount setter:
public int UnitCount
{
get { return _unitCount; }
set
{
_unitCount = value;
OnUnitCountChanged(new EventArgs());
if(_unitCount == 0) { OnDepleted(new EventArgs()); }
}
}
I have a custom checkbox control that inherited from System.Windows.Forms.Control
and it hasn't CheckedChanged event. I want to implement CheckedChange same as dot net native CheckBox. How can I do it well ?
You are inheriting fromn Control, not CheckBox, so the solution is similar to the one proposed by Frigik, but it's not exactly that one.
First of all you have to define the event in your class, i.e.:
public event EventHandler CheckedChanged;
In this way every developer using your control can subscribe/unsubscribe to the event. This is not enough, since the event will never be triggered. To do so, you have to define a method to trigger it, and the call this method whenever the state of your control changes:
private void RaiseCheckedChanged()
{
if (CheckedChanged!= null)
CheckedChanged(this, EventArgs.Empty);
}
Where this method will be called depends on the structure of your control. For instance if you have a property Checked, you could call the method in its setter:
public bool Checked
{
get { return _checked; }
set
{
_checked = value;
RaiseCheckedChanged();
}
}
Try this code :
CheckBox chkList1 = new CheckBox();
chkList1.CheckedChanged += new EventHandler(CheckBox_CheckedChanged);
protected void CheckBox_CheckedChanged(object sender, EventArgs e)
{
// Do your stuff
}
Try this:
public class YourCheckBox:CheckBox
{
public event EventHandler<EventArgs> OnCheckedChangedCustom;
protected override void OnCheckedChanged(EventArgs e)
{
if (OnCheckedChangedCustom!=null)
{
OnCheckedChangedCustom(this, EventArgs.Empty);
}
base.OnCheckedChanged(e);
}
}