I followed this tutorial but I couldn't apply what I learned to my project.
I have a LineGraph object (Dynamic Data Display) and I want to create an event that is raised when the thickness of the LineGraph is equal to 0.
How am I supposed to write it following this tutorial ?
Here is how I would do it with a RoutedEvent:
Create a class that derives from LineGraph, let's say CustomLineGraph:
public class CustomLineGraph : LineGraph {
}
Create our routed event like this:
public class CustomLineGraph : LineGraph {
public static readonly RoutedEvent ThicknessEvent = EventManager.RegisterRoutedEvent("Thickness", RoutingStrategy.Bubble, typeof(RoutedEventHandler, typeof(CustomLineGraph));
// .NET event wrapper
public event RoutedEventHandler Thickness
{
add { AddHandler(CustomLineGraph.ThicknessEvent, value); }
remove { RemoveHandler(CustomLineGraph.ThicknessEvent, value); }
}
}
Now we override the StrokeThickness property so we can raise our custom routed event when the value of that property is 0.
public class CustomLineGraph : LineGraph {
public static readonly RoutedEvent ThicknessEvent = EventManager.RegisterRoutedEvent("Thickness", RoutingStrategy.Bubble, typeof(RoutedEventHandler, typeof(CustomLineGraph));
// .NET event wrapper
public event RoutedEventHandler Thickness
{
add { AddHandler(CustomLineGraph.ThicknessEvent, value); }
remove { RemoveHandler(CustomLineGraph.ThicknessEvent, value); }
}
public override double StrokeThickness {
get { return base.StrokeThickness; }
set
{
base.StrokeThickness = value;
if (value == 0)
RaiseEvent(new RoutedEventArgs(CustomLineGraph.ThicknessEvent, this));
}
}
}
We are done !
Personally, I usually avoid creating events, preferring instead to create delegates. If there is some particular reason that you specifically need an event, then please ignore this answer. The reasons that I prefer to use delegates are that you don't need to create additional EventArgs classes and I can also set my own parameter types.
First, let's create a delegate:
public delegate void TypeOfDelegate(YourDataType dataInstance);
Now a getter and setter:
public TypeOfDelegate DelegateProperty { get; set; }
Now let's create a method that matches the in and out parameters of the delegate:
public void CanBeCalledAnything(YourDataType dataInstance)
{
// do something with the dataInstance parameter
}
Now we can set this method as one (of many) handlers for this delegate:
DelegateProperty += CanBeCalledAnything;
Finally, let's call our delegate... this is equivalent to raising the event:
if (DelegateProperty != null) DelegateProperty(dataInstanceOfTypeYourDataType);
Note the important check for null. So that's it! If you want more or less parameters, just add or remove them from the delegate declaration and the handling method... simple.
Related
Interface-Events can be Implemented explicit. For example we are able to pass
delegates to another Event.
Here the TestHandler-Event is wrapped (not sure if its the right term) by the SomeHandler-Event to Implement the ISomeHandleable-Interface.
public delegate void HandlerDelegate();
public interface ISomeHandleable
{
event HandlerDelegate SomeHandler;
}
public class Test : ISomeHandleable
{
event HandlerDelegate ISomeHandleable.SomeHandler
{
add { TestHandler += value; }
remove { TestHandler -= value; }
}
public event HandlerDelegate TestHandler;
public void Fire() => TestHandler?.Invoke();
}
I have just recently seen, that we are also able to Implement ISomeHandleable.SomeHandleras follows:
event HandlerDelegate ISomeHandleable.SomeHandler
{
add { }
remove { }
}
But I have not yet found any documentation and possible usecases to this, and I also dont understand what it does.
I only know, delegates can still be added to ISomeHandleable.SomeHandler but the Event cannot be invoked by the Class Test anymore.
But as you can define Events with empty Accessors, what does it do and how is it meant to be used?
Quite some time ago, I noticed that the Windows Forms editor of Visual Studio does not support events which contain generic type parameters. For example, an event like
public event EventHandler<ListEventArgs<int>> MyStrangeEvent { add { ... } remove { ... } }
where
public class ListEventArgs<T> : EventArgs { List<T> args; }
does not even show up in the event list in the property manager of Visual Studio. Now, this is a somewhat artificial example that could easily be modified to work in Visual Studio by rewriting the classes and their events. However, I am currently working on a project where I cannot change some classes for compatibility reasons. The only thing I can do is to change the events of my user control. The events of this control currently look like this:
public event EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
Note that the underlying Plane class (represented by the _Plane instance which is a protected field) cannot be changed. Its DrawingError event and its EventArgs type are declared in the Plane class like this:
public class Plane<T> where T : ISurface
{
...
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
...
public class DrawingErrorEventArgs : EventArgs { ... /* Uses T */ ... }
}
Of course, the Windows Forms editor of Visual Studio does not show any of the events of my user control. I have been looking for a number of workarounds to get them shown again, but have not been able to find a workaround that actually works. Here are some things that I tried:
Created a MyPlane class which inherits from Plane and used that instead: public event EventHandler<MyPlane.DrawingErrorEventArgs> DrawingError .... For reasons unknown to me, the events still don't show up in the editor. Perhaps this is due to the parameters of the event, some of which still are generic. Find a minimal working example below.
Created a helper class which defines implicit conversion operators between EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> and EventHandler<GDIPlane.DrawingErrorEventArgs> where GDIPlane is just a dummy class which inherits from Plane<GDISurface>. This does work to some extent, but duplicates event calls since the conversion creates new event handlers which are passed down to _Plane which cannot be removed/unregistered properly.
Tried to inherit from EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>, which obviously does not work since EventHandler<T> is sealed.
Are there any other ways to make my events visible again in the Windows Forms editor?
Best regards
Andreas
EDIT: Minimal working example for 1:
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
public class GDIPlane : Plane<GDISurface> { }
GDIPlane _Plane = null;
public event EventHandler<GDIPlane.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
}
DrawingError does not show up in the list of events in the property manager when clicking on a TestControl instance.
EDIT2: This is the original problem (without any workarounds) where the DrawingError event does of TestControl does not show up either:
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
Plane<GDISurface> _Plane = null;
public event EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
}
This is behavior specific to Visual Studio, and the cause is rooted in the fact that EventHandler<> does not specify covariance on its 'TEventArgs' (it would impose seemingly silly restrictions) and the tools do not perform enough introspection of your code to suss out an appropriate type (even though you've left a trail of type data in constructing the control.) Thus, it seems as though VS does not support generic event properties. You may consider filing a feature request on Microsoft Connect, I wouldn't suggest filing it as a bug as they may label it "by design" and close it.
As a general rule, if you need generic type parameters on your events and you need design time support for them (which are different implementation concerns), you're looking at wrapping them in a presentation-specific facade (e.g. "extra layer of code to facilitate design-time needs".)
Personally, I would reduce the generic typing you have in play now, it seems a bit excessive and if you don't understand covariance/contravariance in generic types it might put you in a tight spot at some point, such as now.
However, to work around your problem:
Consider using a custom event args class which could transport data in a non-generic property, and also use a non-generic EventHandler event/property. Understanding the 'type' of the event is then shifted away from generic type parameters and made the responsibility of your non-generic event args instead. If the 'class' of the event args is insufficient, you can add a property to convey the event type (or data type) so that receiving code can properly interpret it (assuming, of course, that it does not already know by some other means.):
public class DataEventArgs : EventArgs
{
//public string EventTypeOrPurpose { get; set; }
public object Data { get; set; }
}
This is most often only used to ferry data through an event chain, and it is usually implemented as follows:
public class DataEventArgs<T> : EventArgs
{
public T Data { get; set; }
}
Unfortunately, this also has a covariance problem, to resolve it you would actually want something more like this:
public interface IDataArgs<out T>
{
T Data { get; }
}
public class DataEventArgs<T> : EventArgs, IDataArgs<T>
{
public DataEventArgs<T>(T data)
{
_data = data;
}
private T _data;
public T Data { get { return _data; } }
}
Even so, these generic versions still don't work around Visual Studio's limitations, this is merely more proper alternative forms of what you already have shown us.
UPDATE: As requested, here is what a "purpose built facade" might look like in the most basic sense. Note that the usercontrol functions as a facade layer in this case as the eventhandler it exposes delegates to the underlying object model. There is no direct access to underlying object model from the user control (from consumer/designer perspective.)
Please note the reference tracking for event handlers is not necessary unless you dispose of these user controls throughout the lifetime of the app (it is only done to ensure proper delegate removal based on the delegate provided, which is wrapped in a closure/delegate, as you see below.)
Also worth noting I did not test-run this code beyond verifying that the designer shows DrawingError in the property grid when dropped onto a form.
namespace SampleCase3
{
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
private Plane<GDISurface> _Plane = new Plane<GDISurface>(); // requires initialization for my own testing
public TestControl()
{
}
// i am adding this map *only* so that the removal of an event handler can be done properly
private Dictionary<EventHandler, EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>> _cleanupMap = new Dictionary<EventHandler, EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>>();
public event EventHandler DrawingError
{
add
{
var nonGenericHandler = value;
var genericHandler = (EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>)delegate(object sender, Plane<GDISurface>.DrawingErrorEventArgs e)
{
nonGenericHandler(sender, e);
};
_Plane.DrawingError += genericHandler;
_cleanupMap[nonGenericHandler] = genericHandler;
}
remove
{
var nonGenericHandler = value;
var genericHandler = default(EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>);
if (_cleanupMap.TryGetValue(nonGenericHandler, out genericHandler))
{
_Plane.DrawingError -= genericHandler;
_cleanupMap.Remove(nonGenericHandler);
}
}
}
}
}
To complement the above, here is what a non-generic event handler would now look like:
private void testControl1_DrawingError(object sender, EventArgs e)
{
var genericDrawingErrorEventArgs = e as Plane<GDISurface>.DrawingErrorEventArgs;
if (genericDrawingErrorEventArgs != null)
{
// TODO:
}
}
Note that the consumer here has to have knowledge of the type for e to perform conversion. The use of the as operator will bypass ancestry checks under the assumption that the conversion should succeed.
Something like this is as close as you're going to get. Yes it is ugly by most of our standards, but if you absolutely 'need' design-time support on top of these components and you cannot change Plane<T> (which would be more appropriate) then this, or something close to this, is the only viable workaround.
HTH
I'm having some issues here, I'm working with a class called Cell and when I create each cell I want to raise an event OnCellCreated which my IGameViewer will attach to eventually. For some weird reason it doesn't work though, now I've bypassed this by instead calling IGameViewer.DisplayCell in the constructor, but it's incredibly strange because it passes the exact same object reference from the constructor and it works, but when I try to do it with my event I will get a null object reference. So does anyone have any ideas?
Here's the code
class Cell
{
public delegate void CellChangedHandler(Cell cell);
#region Properties & Fields
private Mark markType = Mark.Empty;
private IGameViewer viewer;
public static event CellChangedHandler OnCellChanged;
public static event CellChangedHandler OnCellCreated;
public readonly Tuple<int, int> pos;
public Mark MarkType {
get { return markType; }
set
{
// Only allow changes to cells without a mark
if (markType.Equals(Mark.Empty))
{
markType = value;
OnCellChanged(this); //Model -> Viewer & Presenter, both can attach to this event
}
}
}
#endregion
#region Constructors
public Cell(IGameViewer viewer, Tuple<int, int> coords)
{
this.viewer = viewer;
this.pos = coords;
OnCellCreated(this); // <- This causes an object null reference exception to be thrown
viewer.DisplayCell(this); // <- This doesn't, even if I reverse the calling order
}
#endregion
}
Your OnCellCreated event is null, because noone yet subscribed to it. And how can caller do that if you call it already in construcutor. ? One can not subscribe to the event of the instance if the instance is not yet created (you are in constructor)
You can create a CellFactory class and add CellCretaed event to the factory. You ask CellFactory to create Cell instance, and CellFactory after creation of it, raises an event. Naturally you have to subscribe to that event before calling factory method.
Check if there're subscribers for the event whenever raising it:
public Cell(IGameViewer viewer, Tuple<int, int> coords) {
this.viewer = viewer;
...
if (!Object.ReferenceEquals(null, OnCellCreated))
OnCellCreated(this);
...
}
As #Tigran answered, the problem is that no one has yet subscribed to the event. What were you expecting to happen by invoking the event on the constructor?
If you had followed the pattern to implement events in .NET classes this would have never happened.
Change your code to this:
class Cell
{
private Mark markType = Mark.Empty;
private IGameViewer viewer;
public static event EventHandler CellChanged;
public readonly Tuple<int, int> pos;
public Cell(IGameViewer viewer, Tuple<int, int> coords)
{
this.viewer = viewer;
this.pos = coords;
viewer.DisplayCell(this);
}
public Mark MarkType {
get { return markType; }
set
{
// Only allow changes to cells without a mark
if (markType.Equals(Mark.Empty))
{
markType = value;
OnCellChanged();
}
}
}
protected virtual void OnCellChanged()
{
var handler = this.CellChanged;
if (handler)
{
handler(this, EventArgs.Empty);
}
}
}
This seems to be basics of the language, but I do not understand how is this accomplished in .Net. I have a member variable in a class, say a bool _isCommitted. I want something to happen whenever _isCommitted is true. Something like this:
//Whenever _isCommitted == true()
{
Foo()
}
Basically like an event, but here it is my variable. How to? Many thanks..
This is normally done through properties and a backing private field. You need to ensure you only ever access through the property.
private bool _isCommitted;
public bool IsCommitted
{
get { return _isCommitted; }
set
{
if(value)
{
//do something
}
_isCommitted = value;
}
}
At the most basic level, you can create an event in your class:
public delegate void MyHandler(bool b);
public event MyHandler CommittedChanged;
Now people can subscribe to your event like so:
public void SomeHandlerMethod(bool b) { ... }
...
someInstance.CommittedChanged += SomeHandlerMethod;
someInstance.CommittedChanged += ASecondHandlerMethod;
someInstance.CommittedChanged += x => { /* inline handler using lambda */ };
A user can unregister his event handler this way:
someInstance.CommittedChanged -= SomeHandlerMethod;
And wherever you decide to change your variable, you will follow it up with:
if (CommittedChanged != null) CommittedChanged(_isCommitted);
This will call everyone who has registered a function with your event.
Having said this, there are plenty of improvements that you can do. First, make _isCommitted into a property, and do the event callback in its setter. This way, you won't forget to call the handlers.
public IsCommitted {
get { return _isCommitted; }
set {
_isCommitted = value;
if (CommittedChanged != null) CommittedChanged(_isCommitted);
}
}
Read more about events here.
This is enough to get you going. However, if you delve further into the C# framework, you will find a standardized way of using this event framework inside of the System.ComponentModel namespace. Sepcifically, the interface INotifyPropertyChanged, which ties neatly into a more generic event system that also plays well with some of Microsoft's own technologies, such as WPF, allowing GUI elements to pick up on changes to your class automatically. Read more about INotifyPropertyChanged here.
You basically need PropertyChangedEvent PropertyChangedEventHandler Delegate
I think C# properties is what you need.
private bool _isCommitted;
public bool IsCommitted
{
get { return _isCommitted; }
set { if(value){/*DO SOMETHING HERE*/}
_isCommitted = value; }
}
I'm trying to use the MVVM pattern in my Silverlight 3 application and am having problems getting binding to a command property of a view model working. First off, I'm trying to add an attached property called ClickCommand, like this:
public static class Command
{
public static readonly DependencyProperty ClickCommandProperty =
DependencyProperty.RegisterAttached(
"ClickCommand", typeof(Command<RoutedEventHandler>),
typeof(Command), null);
public static Command<RoutedEventHandler> GetClickCommand(
DependencyObject target)
{
return target.GetValue(ClickCommandProperty)
as Command<RoutedEventHandler>;
}
public static void SetClickCommand(
DependencyObject target, Command<RoutedEventHandler> value)
{
// Breakpoints here are never reached
var btn = target as ButtonBase;
if (btn != null)
{
var oldValue = GetClickCommand(target);
btn.Click -= oldValue.Action;
target.SetValue(ClickCommandProperty, value);
btn.Click += value.Action;
}
}
}
The generic Command class is a wrapper around a delegate. I'm only wrapping a delegate because I wondered if having a delegate type for a property was the reason things weren't working for me originally. Here's that class:
public class Command<T> /* I'm not allowed to constrain T to a delegate type */
{
public Command(T action)
{
this.Action = action;
}
public T Action { get; set; }
}
Here's how I am using the attached property:
<Button u:Command.ClickCommand="{Binding DoThatThing}" Content="New"/>
The syntax seems to be accepted, and I think that when I tested all of this with a string property type, that worked fine. Here's the view model class that is being bound to:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public Command<RoutedEventHandler> DoThatThing
{
get
{
return new Command<RoutedEventHandler>(
(s, e) => Debug.WriteLine("Never output!"));
}
}
}
The delegate contained in the Command property is never invoked. Also, when I place breakpoints in the getter and setter of the attached property, they are never reached.
In trying to isolate the problem, I changing the property type to string; the breakpoint in the getter and setter was also never reached, yet throwing an exception in them did cause the application to terminate, so I am thinking it's a framework eccentricity.
Why is this stuff not working? I also welcome alternate, hopefully simpler ways to bind event handlers to view models.
You have at least two problems here.
First, you are relying on the SetXxx method being executed. The CLR wrappers for dependency properties (the property setter or SetXxx method) are not executed when the DP is set from XAML; rather, WPF sets the value of the internally managed DP "slot" directly. (This also explains why your breakpoints were never hit.) Therefore, your logic for handling changes must always occur in the OnXxxChanged callback, not in the setter; WPF will call that callback for you when the property changes regardless of where that change comes from. Thus (example taken from a slightly different implementation of commands, but should give you the idea):
// Note callback in PropertyMetadata
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(Click),
new PropertyMetadata(OnCommandChanged));
// GetXxx and SetXxx wrappers contain boilerplate only
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
// WPF will call the following when the property is set, even when it's set in XAML
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ButtonBase button = d as ButtonBase;
if (button != null)
{
// do something with button.Click here
}
}
Second, even with this change, setting ClickCommand on a control that doesn't already have a value set will cause an exception, because oldValue is null and therefore oldValue.Action causes a NullReferenceException. You need to check for this case (you should also check for newValue == null though this is unlikely ever to happen).