I am working on a simple GUI framework, and I faced a problem calling protected virtual methods.
Here is the IKeyboardInputListenerService interface of service I use to receive keyboard events and the Control class that represents a base for all my GUI controls. The internal method is the one being refactored.
public interface IKeyboardInputListenerService
{
event EventHandler<KeyboardEventArgs> KeyPressed;
}
public abstract class Control
{
public IKeyboardInputListenerService KeyboardInputListenerService { get; }
protected Control(IKeyboardInputListenerService keyboardInputListenerService) =>
KeyboardInputListenerService = keyboardInputListenerService;
public event EventHandler<KeyboardEventArgs> KeyPressed;
/* protected */ internal virtual void OnKeyPressed(object sender, KeyboardEventArgs args)
{
if (enabled && visible && focused && !args.Suppressed)
{
KeyPressed?.Invoke(sender, args);
args.Suppressed = true;
}
}
public void Activate() =>
KeyboardInputListenerService.KeyPressed += new EventHandler<KeyboardEventArgs>(OnKeyPressed);
}
I also created a ContainerControl class that is supposed to contain child controls(like the Panel or GroupBox in Windows Forms) and that overrides the virtual method:
public abstract class ContainerControl : Control
{
private readonly ObservableCollection<Control> controls;
protected ContainerControl(IKeyboardInputListenerService keyboardInputListenerService)
: base(keyboardInputListenerService) =>
controls = new ObservableCollection<Control>();
/* protected */ internal override void OnKeyPressed(object sender, KeyboardEventArgs args)
{
foreach (Control control in controls)
control.OnKeyPressed(sender, args);
base.OnKeyPressed(sender, args);
}
}
Problem is, I cannot decide which modifier to use for methods such as OnKeyPressed. I wanted to make them protected, but it causes a compiler error:
Error CS1540 Cannot access protected member 'Control.OnKeyPressed(object, KeyboardEventArgs)' via a qualifier of type 'Control'; the qualifier must be of type 'ContainerControl' (or derived from it)
I can make them public, but I do not really think it is a good idea, because there is no reason for it except for resolving the problem caused by a cross-hierarchy call. I made them internal, but there is also a drawback: if anyone will want to create a user control, they will not be able to receive events, so the control will be useless.
The question is how to get access from a derived class to virtual methods of the base class without making the methods public-accessed.
Use protected internal
protected internal virtual void OnKeyPressed(object sender, KeyboardEventArgs args)
{ ... }
The documentation says:
protected internal The type or member can be accessed by any code in the assembly in which it is declared, or from within a derived class in another assembly.
Related
I am trying to subscribe to an event that is in my base class but the method in my derived class doesn't seem to trigger whenever that event is triggered. Sample code is below.
public abstract class BaseClass
{
public delegate void EventHandler(object sender, EventArgs e);
public event EventHandler Event;
protected virtual void OnEvent(EventArgs ea)
{
if (this.Event!= null)
{
this.Event(null, ea);
}
}
}
public partial class DerivedClass : BaseClass
{
protected override void OnInit(EventArgs e)
{
base.Event+= DoSomething;
}
private void DoSomething(object sender, EventArgs e)
{
//Do Something here.
}
}
BaseClass.OnEvent is called in another control that has the same base class and the derived class where I want to subscribe to is inside another control. Is this possible?
BaseClass.OnEvent is called in another control that has the same base class and the derived class where I want to subscribe to is inside another control. Is this possible?
If you have a derived class and you have two instances of this derived class, the method of instance one won't be called if anything happens in instance two.
In your case you even have two different derived classes, sharing one base class and at runtime you have at least one instance of each derived class, which means, there is no communication between these two.
If you need to link two instances together you have to do something like this:
var instanceOne = new DerivedClassOne();
var instanceTwo = new DerivedClassTwo();
// When something in one happens, let two know:
instanceOne.OnEvent += (sender, e) => instanceTwo.ReactOnOtherChange();
Hello i create a class which contains the event and the variables :
namespace FS
{
public class SomeEventArgs : EventArgs
{
public readonly string SomeVarible;
public SomeEventArgs (string someVarible)
{
SomeVarible= someVarible;
}
}
}
and i want to use this in another class :
namespace FS
{
delegate void Example(object sender, SomeEventArgs e);
public class OtherClass
{
public event Example example;
}
and i get this error :
Error CS7025 Inconsistent accessibility: event type 'Example' is less
accessible than event 'OtherClass.example'
You haven't specified access modifier of your delegate void Example(object sender, SomeEventArgs e); default one is internal, but event in class OtherClass is public.
It's illogical to expose something to other libraries with public modifier, while the underlying type is only accessible inside your library, because it is internal.
Easiest solution is making your delegate public, or your event public internal, depends how you want to use it.
You can find more about access modifiers here in documentation.
I am creating an class library in C++/CLI to be used with C#, and as part of that library, i am offering up a customised version of System.Windows.Forms.Form and System.Windows.Forms.Control as System.Windows.Forms.HAForm and System.Windows.Forms.HAControl. I want to override OnPaint in a way that allows me to have OnPaint_Pre, OnPaint_Post AND OnPaint as i need to handle certain things every time before any painting is done, and handle some things AFTER painted has completed.
While this alone is simple enough, with this being a class in a library that is to be inherited from, i do not want to simply create an OnPaint in my class as this will be overwritten by the end developers OnPaint, and even if they do call base.OnPaint, the events will be fired out of order. i.e. OnPaint_Pre, my OnPaint, OnPaint_Post, sub classes OnPaint.
How would i create a class that inserts two events, one before the existing event, and one after?
I hope you don't mind me using C# syntax instead of C++/CLI...
You can make the HAForm/HAControl OnPaint override sealed, and create a new virtual function that derived classes can override. You can even use an intermediate class to give the new virtual function the same name:
public class HAControlBase : Control
{
protected virtual void OnPaintPre(PaintEventArgs e) { }
protected virtual void OnPaintPost(PaintEventArgs e) { }
internal virtual void OnPaintImpl(PaintEventArgs e) {
base.OnPaint(e);
}
protected sealed override OnPaint(PaintEventArgs e) {
OnPaintPre(e);
OnPaintImpl(e);
OnPaintPost(e);
}
}
public class HAControl : HAControlBase
{
internal sealed override void OnPaintImpl(PaintEventArgs e) {
OnPaint(e);
}
protected new virtual void OnPaint(PaintEventArgs e) {
base.OnPaintImpl(e);
}
}
Now, even if a derived class overrides HAControl.OnPaint, it will only be called after HAControlBase.OnPaint has already finished with OnPaintPre, there is no way to override Control.OnPaint (because that override is sealed) to call anything before OnPaintPre.
I am bubbling events in my application and so therefore using the bubble events method. As this method handles all sorts of bubbled events their is a switch or if statement within it to determine what sort of event we're dealing with. I was wondering if I could get around this by creating different versions of the event args class. So let me explain, say I have two types of event that are handled differently called X and Y, I create new event args classes for these two events as they store different types of info.
public class EventsArgsX : EventsArgs
public class EventsArgsY : EventsArgs
then when I RaiseBubbleEvent from somewhere in my application I can pass either of the two event arg based types, so..
EventArgsX foox = new EventArgsX();
RaiseBubbleEvent(null,foox);
or
EventArgsY fooy = new EventArgsY();
RaiseBubbleEvent(null,fooy);
then the OnBubbleEvent method picks this up, who's signature is
override OnBubbleEvent(object source, EventArgs e)
now i cant overload this method as its overriden in the first place, so what I thought I could do was have another method with overloads in it to handle this, so
protected override OnBubbleEvent(object source, EventArgs e)
{
DoStuff(e);
}
private void DoStuff(EventArgsY args)
{}
private void DoStuff(EventArgsX args)
{}
but of course the problem is that EventArgs e in the OnBubbleEvent method is of type EventArgs at the time of calling. However we know its not. So how would i case it back to its actual type in order for the method call to work?
Many thanks, hope you can help me with this, its seems really easy like a might be missing something or that it just cant be done
any ideas??
It's simple:
protected override OnBubbleEvent(object source, EventArgs e)
{
if(e is EventArgsX)
DoStuff((EventArgsX)e);
else if(e is EventArgsY)
DoStuff((EventArgsY)e);
}
This, being KISS, is not very extensible. If you're planning on adding more event types, you can try double dispatch:
public abstract class EventArgsBase : EventArgs
{
public abstract void Bubble(IEventBubbler eb);
}
public interface IEventBubbler
{
Bubble(EventArgsX ex);
Bubble(EventArgsY ey);
}
public class EventArgsX : EventArgsBase
{
public virtual void Bubble(IEventBubbler eb)
{
eb.Bubble(this);
}
}
public class EventArgsY : EventArgsBase
{
public virtual void Bubble(IEventBubbler eb)
{
eb.Bubble(this);
}
}
In following code, I want to extend the behaviour of a class by deriving/subclassing it, and make use of an event of the base class:
public class A
{
public event EventHandler SomeEvent;
public void someMethod()
{
if(SomeEvent != null) SomeEvent(this, someArgs);
}
}
public class B : A
{
public void someOtherMethod()
{
if(SomeEvent != null) SomeEvent(this, someArgs); // << why is this not possible?
//Error: The event 'SomeEvent' can only appear on the left hand side of += or -=
//(except when used from within the type 'A')
}
}
Why isn't it possible?
And what is the common solution for this kind of situation?
Others have explained how to get round the issue, but not why it's coming up.
When you declare a public field-like event, the compiler creates a public event, and a private field. Within the same class (or nested classes) you can get at the field directly, e.g. to invoke all the handlers. From other classes, you only see the event, which only allows subscription and unsubscription.
The standard practice here is to have a protected virtual method OnSomeEvent on your base class, then call that method in derived classes. Also, for threading reasons you will want to keep a reference to the handler before checking null and calling it.
For an explanation of the why read Jon Skeet's answer or the C# specification which describes how the compiler automatically creates a private field.
Here is one possible work around.
public class A
{
public event EventHandler SomeEvent;
public void someMethod()
{
OnSomeEvent();
}
protected void OnSomeEvent()
{
EventHandler handler = SomeEvent;
if(handler != null)
handler(this, someArgs);
}
}
public class B : A
{
public void someOtherMethod()
{
OnSomeEvent();
}
}
Edit: Updated code based upon Framework Design Guidelines section 5.4 and reminders by others.
Todd's answer is correct. Often you will see this implemented throughout the .NET framework as OnXXX(EventArgs) methods:
public class Foo
{
public event EventHandler Click;
protected virtual void OnClick(EventArgs e)
{
var click = Click;
if (click != null)
click(this, e);
}
}
I strongly encourage you to consider the EventArgs<T>/EventHandler<T> pattern before you find yourself making all manner of CustomEventArgs/CustomEventHandler for raising events.
The reason the original code doesn't work is because you need to have access to the event's delegate in order to raise it, and C# keeps this delegate private.
Events in C# are represented publicly by a pair of methods, add_SomeEvent and remove_SomeEvent, which is why you can subscribe to an event from outside the class, but not raise it.
My answer would be that you shouldn't have to do this.
C# nicely enforces Only the type declaring/publishing the event should fire/raise it.
If the base class trusted derivations to have the capability to raise its events, the creator would expose protected methods to do that. If they don't exist, its a good hint that you probably shouldn't do this.
My contrived example as to how different the world would be if derived types were allowed to raise events in their ancestors. Note: this is not valid C# code.. (yet..)
public class GoodVigilante
{
public event EventHandler LaunchMissiles;
public void Evaluate()
{
Action a = DetermineCourseOfAction(); // method that evaluates every possible
// non-violent solution before resorting to 'Unleashing the fury'
if (null != a)
{ a.Do(); }
else
{ if (null != LaunchMissiles) LaunchMissiles(this, EventArgs.Empty); }
}
virtual protected string WhatsTheTime()
{ return DateTime.Now.ToString(); }
....
}
public class TriggerHappy : GoodVigilante
{
protected override string WhatsTheTime()
{
if (null != LaunchMissiles) LaunchMissiles(this, EventArgs.Empty);
}
}
// client code
GoodVigilante a = new GoodVigilante();
a.LaunchMissiles += new EventHandler(FireAway);
GoodVigilante b = new TriggerHappy(); // rogue/imposter
b.LaunchMissiles += new EventHandler(FireAway);
private void FireAway(object sender, EventArgs e)
{
// nuke 'em
}
Wrap it with a protected virtual On... method:
public class BaseClass
{
public event EventHandler<MyArgs> SomeEvent;
protected virtual void OnSomeEvent()
{
if(SomeEvent!= null)
SomeEvent(this, new MyArgs(...) );
}
}
Then override this in a derived class
public class DerivedClass : BaseClass
{
protected override void OnSomeEvent()
{
//do something
base.OnSomeEvent();
}
}
You'll set this pattern all over .Net - all form and web controls follow it.
Do not use the prefix Raise... - this is not consistent with MS's standards and can cause confusion elsewhere.