NoesisGUI

Events Overview

This section describes the concept of events, events terminology, describes what routed events are and how are they routed through a tree of elements, summarizes how you handle events, and introduces how to create your own custom events.

Delegate Events

A delegate event exposes a simple delegate that will be called when the event is raised. Each event is referenced by a delegate stored in the class instance.

C++
struct DownloadCompletedEventArgs: public EventArgs
{
    // ...
};

tpyedef void DownloadCompletedT(BaseComponent* sender, const DownloadCompletedEventArgs& e);
typedef Delegate<DownloadCompletedT> DownloadCompletedEventHandler;

class DownloadControl: public Control
{
    //...

private:
    DownloadCompletedEventHandler _downloadCompleted;

    NS_DECLARE_REFLECTION(DownloadControl, Control)
};

Register Event

If you want that event to be recognized by the XAML parser, it must be registered inside the class reflection.

NS_IMPLEMENT_REFLECTION(DownloadControl)
{
    NsMeta<TypeId>("DownloadControl");
    NsEvent("DownloadCompleted", &DownloadControl::_downloadCompleted);
}

Connect Event Handlers

Each event must have a function named as the event returning a DelegateEvent_ wrapper, which implements operator += to add a new handler to the event and operator -= to remove a handler.

class DownloadControl: public Control
{
public:
    DelegateEvent_<DownloadCompletedEventHandler> DownloadCompleted()
    {
        return DelegateEvent_<DownloadCompletedEventHandler>(_downloadCompleted);
    }

    //...
};

// another class registers against the event...
DownloadHandler::DownloadHandler(DownloadControl* control)
{
    control->DownloadCompleted() += MakeDelegate(this, &DownloadHandler::OnDownloadCompleted);
}

void DownloadHandler::OnDownloadCompleted(BaseComponent* sender, const DownloadCompletedEventArgs& args)
{
    //...
}

Raise the Event

Each event should have an associated virtual function with the name of the event prefixed by On that will raise the event and will allow inheritors to handle the event and cancel it. For example, in the previous example that function would be OnDownloadCompleted().

class DownloadControl: public Control
{
protected:
    virtual void OnDownloadCompleted(const DownloadCompletedEventArgs& args)
    {
        _downloadCompleted(this, args);
    }

   //...
};

Inside a Template

When the UI element forms part of a template it should override CloneOverride() method to clone all its members, so element is correctly replicated when template gets applied. Events should be cloned too, so generated template elements can notify to the code-behind object.

class DownloadControl: publicControl
{
protected:
    void CloneOverride(FrameworkElement* clone_, FrameworkTemplate* template_) const override
    {
        ParentClass::CloneOverride(clone_, template_);

        DownloadControl* clone = (DownloadControl*)(clone_);
        clone->_downloadCompleted = _downloadCompleted;
    }

   //...
};

UIElement Events

UIElement provides static storage for delegate events, so you can define many events in a class without growing instance size.

Connect Event Handlers

In this case events will be exposed through a function that will return an Event_ wrapper defined by UIElement, which also implements operator += to add a new handler to the event and operator -= to remove a handler.

NS_DECLARE_SYMBOL(DownloadCompleted)

class DownloadControl: public Control
{
public:
    UIElement::Event_<DownloadCompletedEventHandler> DownloadCompleted()
    {
        return UIElement::Event_<DownloadCompletedEventHandler>(this, NSS(DownloadCompleted));
    }

    // Needed by NsEvent
    EventHandler& DownloadControl::GetDownloadCompletedEvent()
    {
        return GetEventHandler(NSS(DownloadCompleted));
    }

    //...
};

// another component registers against the event...
DownloadHandler::DownloadHandler(DownloadControl* control)
{
    control->DownloadCompleted() += MakeDelegate(this, &DownloadHandler::OnDownloadCompleted);
}

void DownloadHandler::OnDownloadCompleted(BaseComponent* sender, const DownloadCompletedEventArgs& args)
{
    //...
}

Register Event

The same as with delegate events, if you want the event to be recognized by the XAML parser, you must register it inside class reflection.

NS_IMPLEMENT_REFLECTION(DownloadControl)
{
    NsMeta<TypeId>("DownloadControl");
    NsEvent("DownloadCompleted", &DownloadControl::GetDownloadCompletedEvent);
}

Raise the Event

Event is raised in the corresponding virtual function by calling UIElement.RaiseEvent.

class DownloadControl: public Control
{
protected:
    virtual void OnDownloadCompleted(const DownloadCompletedEventArgs& args)
    {
        RaiseEvent(NSS(DownloadCompleted), args);
    }

   //...
};

Routed Events

A routed event is a type of event that can invoke handlers on multiple listeners in an element tree, rather than just on the object that raised the event.

Each routed event is referenced as an instance of a RoutedEvent class. That handle or identifier must be stored as a class public member, and registered in the UIElementData metadata (shown later).

C++
struct DownloadCompletedEventArgs: public RoutedEventArgs
{
    // ...
};

class DownloadControl: public Control
{
    /// Routed events
    //@{
    static const RoutedEvent* DownloadCompletedEvent;
    //@}

    //...
};

const RoutedEvent* DownloadControl::DownloadCompletedEvent;

Event Name Conventions

There are established naming conventions regarding routed events too. The routed event itself will have a basic name, DownloadCompleted. That name must be unique within each registering type. When you create the identifier field, name this field by the name of the event as you registered it, plus the suffix Event. This field is your identifier for the routed event, and it will be used later as an input for the AddHandler and RemoveHandler calls made when registering event handlers. That's why previous example used DownloadCompletedEvent as event identifier.

Register Event

In order for your event to support event routing, you must register that event into a table maintained by the type metadata, and give it a unique identifier. To register the event, you call the RegisterEvent() method within the body of your reflection implementation function.

NS_IMPLEMENT_REFLECTION(DownloadControl)
{
    NsMeta<TypeId>("DownloadControl");

    Ptr<UIElementData> data data = NsMeta<UIElementData>(TypeOf<SelfClass>());
    data->RegisterEvent(DownloadCompletedEvent, "DownloadCompleted", RoutingStrategy_Bubbling);
}

Register Event Handlers

Each event needs to have a function named as the event and returning a RoutedEvent_ wrapper, defined by UIElement and implementing operator += to call AddHandler() and operator -= to call RemoveHandler(). That way users of the class can easily add handlers to events.

class DownloadControl: public Control
{
public:
    UIElement::RoutedEvent_<DownloadCompletedEventHandler> DownloadCompleted()
    {
        return UIElement::RoutedEvent_<DownloadCompletedEventHandler>(this, DownloadCompletedEvent);
    }

    //...
};

// another class registers against the event...
DownloadHandler::DownloadHandler(DownloadControl* control)
{
    control->DownloadCompleted() += MakeDelegate(this, &DownloadHandler::OnDownloadCompleted);
}

void DownloadHandler::OnDownloadCompleted(BaseComponent* sender, const DownloadCompletedEventArgs& args)
{
    //...
}

Raise the Event

Each event must have an associated virtual function with the name of the event prefixed by On that will raise the event and will allow inheritors to handle the event and cancel it. For the previous example the function would be OnDownloadCompleted().

class DownloadControl: public Control
{
protected:
    virtual void OnDownloadCompleted(const DownloadCompletedEventArgs& args)
    {
        RaiseEvent(args);
    }

   //...
}

Code-Behind Events

Code behind is the code that is joined with markup-defined objects. The XAML language includes the keyword x:Class that make it possible to associate code files with markup files, from the markup file side.

Code-behind class

The class specified in x:Class must derive from the type that backs the root element.

<UserControl x:Class="Samples.DownloadHandler">
    ...
</UserControl>
class DownloadHandler: public UserControl
{
    //...
};

Event handlers

The event handlers you write in the code behind must be non-static instance methods. These methods must be defined by the class within the namespace identified by x:Class. You cannot qualify the name of an event handler to instruct the XAML parser to look for an event handler in a different class scope.

The handler must match the delegate for the appropriate event in the backing type system.

<UserControl x:Class="Samples.DownloadHandler">
    ...
    <DownloadControl DownloadCompleted="OnDownloadCompleted"/>
    ...
</UserControl>
class DownloadHandler: public UserControl
{
private:
    void OnDownloadCompleted(BaseComponent* sender, const DownloadCompletedEventArgs& args)
    {
    }

    //...
};

Event connection

In order to link events with code behind class, XAML parser will call during parsing process the function ConnectEvent() defined by the code behind instance for each event found in the XAML. This function provides the name of the event being linked, the name of the event handler that should be attached, and the source object where event will occur.

class DownloadHandler: public UserControl
{
private:
    bool ConnectEvent(BaseComponent* source, const char* event, const char* handler) override
    {
        // Handle events
        NS_CONNECT_EVENT(DownloadControl, DownloadCompleted, OnDownloadCompleted);
        NS_CONNECT_EVENT(Button, Click, OnCancelClick);
        NS_CONNECT_EVENT(Button, Click, OnAcceptClick);

        // Not connected
        return false;
    }

    //...
};
© 2017 Noesis Technologies