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.

C++ Implementation

Each event is referenced by a delegate stored in the class instance.

struct DownloadCompletedEventArgs: public Noesis::EventArgs
{
    // ...
};

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

class DownloadControl: public Noesis::Control
{
    //...

private:
    DownloadCompletedEventHandler _downloadCompleted;

    NS_DECLARE_REFLECTION(DownloadControl, Noesis::Control)
};

Register Event

If you want that event could be recognized by XAML parser, it must be registered in class reflection.

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

    NsEvent("DownloadCompleted");
}

Connect Event Handlers

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

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

    //...
};

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

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

Raise the Event

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

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

   //...
};

Inside a Template

When a 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: public Noesis::Control
{
protected:
    void CloneOverride(Noesis::FrameworkElement* clone_, Noesis::FrameworkTemplate* template_) const override
    {
        ParentClass::CloneOverride(clone_, template_);

        DownloadControl* clone = NsStaticCast<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 Noesis::Control
{
public:
    Noesis::UIElement::Event_<DownloadCompletedEventHandler> DownloadCompleted()
    {
        return Noesis::UIElement::Event_<DownloadCompletedEventHandler>(this, NSS(DownloadCompleted));
    }

    //...
};

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

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

Register Event

As it occurred with delegate events, if you want that it could be recognized by XAML parser, you must register it in class reflection.

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

    NsEvent("DownloadCompleted");
}

Raise the Event

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

class DownloadControl: public Noesis::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.

C++ Implementation

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.

struct DownloadCompletedEventArgs: public Noesis::RoutedEventArgs
{
    // ...
};

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

    //...
};

const Noesis::RoutedEvent* MyControl::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<Noesis::TypeId>("DownloadControl");

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

Register Event Handlers

Each event will have a function named as the event that will return a RoutedEvent_ wrapper, defined by UIElement and implements operator += to call AddHandler() and operator -= to call RemoveHandler(). That way users of the class can add handlers to an event using the operator += easier.

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

    //...
};

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

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

Raise the Event

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

class DownloadControl: public Noesis::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 Noesis::UserControl
{
    //...
};

Event handlers

The event handlers you write in the code behind must be instance methods and cannot be static 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 Noesis::UserControl
{
private:
    void OnDownloadCompleted(Noesis::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 Connect() 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 Noesis::UserControl
{
private:
    void Connect(Noesis::BaseComponent* source, const NsChar* eventName, const NsChar* handlerName)
    {
        if (String::Compare(eventName, "DownloadCompleted") == 0 &&
            String::Compare(handlerName, "OnDownloadCompleted") == 0)
        {
            ((DownloadControl*)source)->DownloadCompleted() += MakeDelegate(this,
                &DownloadHandler::OnDownloadCompleted);
            return;
        }

        if (String::Compare(eventName, "Click") == 0 &&
            String::Compare(handlerName, "OnCancelClick") == 0)
        {
            ((Noesis::Button*)source)->Click() += MakeDelegate(this,
                &DownloadHandler::OnCancelClick);
            return;
        }

        if (String::Compare(eventName, "Click") == 0 &&
            String::Compare(handlerName, "OnAcceptClick") == 0)
        {
            ((Noesis::Button*)source)->Click() += MakeDelegate(this,
                &DownloadHandler::OnAcceptClick);
            return;
        }
    }

    //...
};

As this code is repetitive you can define a macro to simplify it:

#define CONNECT_EVENT_HANDLER(sourceType, event, handler) \
    if (String::Compare(eventName, #event) == 0 && \
        String::Compare(handlerName, #handler) == 0) \
    { \
        ((sourceType*)source)->event() += MakeDelegate(this, &SelfClass::handler); \
        return; \
    }

class DownloadHandler: public Noesis::UserControl
{
private:
    void Connect(Noesis::BaseComponent* source, const NsChar* eventName, const NsChar* handlerName)
    {
        CONNECT_EVENT_HANDLER(DownloadControl, DownloadCompleted, OnDownloadCompleted);
        CONNECT_EVENT_HANDLER(Noesis::Button, Click, OnCancelClick);
        CONNECT_EVENT_HANDLER(Noesis::Button, Click, OnAcceptClick);
    }

    //...
};
© 2017 Noesis Technologies