This guide will walk through the process of writing an event for Mayhem. There are a many similarities between this and the guide for creating a reaction.

We are going to write a simplified version of the official Folder Change event. We will specify a folder path and the event will trigger whenever the contents of that folder are changed.

Set up a new VS C# class library project for this event. This guide walks through that process: Setting Up Visual Studio To Write Modules

Create a new class called FolderChange and open it up, make sure the class is public.

Add the following attribute to the class:

[DataContract]
[MayhemModule("Folder Change", "This event monitors changes on a given folder.")]


Mayhem looks for classes denoted with the MayhemModule attribute to know it is a module that can be used. All events and reactions you write will need to have that attribute specified. The first string is the title of your module in the module lists and what shows up on the main Mayhem window. The second string is a brief explanation of what the module does.

We have to add a reference to System.Runtime.Serialization for the DataContract attribute. We need that attribute to be able to serialize some module settings.

Now extend the class EventBase from MayhemCore. Basic events do not need to extend a lot of methods from EventBase. At their core, all that has to happen is have the method Trigger() be called at some point to make the event trigger an attached Mayhem Reaction.

Fields

Let’s first look at the fields we need to have to make the event work. We want to monitor changes to a folder, so we need to store the folder path somewhere:

[DataMember]
private string folderToMonitor;

 

The [DataMember] attribute tells Mayhem to store that value when it shuts down and to reload it when it restarts. Lastly, we need a FileSystemWatcher that does the hard work of actually monitoring the file system for us.

private FileSystemWatcher fileWatcher;

 

Initialization

In order to initialize Mayhem events correctly, three methods can be overridden; OnLoadDefaults(), OnLoadFromSaved() and OnAfterLoad(). You should never use the object's constructor in events. Since we are using DataContracts, the constructor is not called on deserialization.

orderops


As can be seen in Figure 1, either OnLoadDefaults or OnLoadsFromSaved() is called after object instantiation, depending whether the Event is newly created or loaded from a saved state when Mayhem starts up. In either case, OnAfterLoad() is called at the end of the startup sequence.

In OnLoadDefaults, we want to provide some good defaults, which the user can customize in the Module’s configuration shortly after its creation. In OnLoadFromSaved(), we can do additional initialization coming from a saved state. However, we do not need this method for our folder monitor event. In OnAfterLoad() we can be certain that all fields have been either initialized or retrieved from a saved state, so we can initialize the Event’s functionality safely here. Thus, our OnLoadDefaults() is implemented as following:

protected override void OnLoadDefaults()
{
  folderToMonitor = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
}

Here we are setting the user’s “My Documents” directory as the default folder to monitor and by default support monitoring Write changes.
In OnAfterLoad() we configure the FileSystemWatcher to call an event handler when it finds changes to the directory we want to monitor:

protected override void OnAfterLoad()
{
    // Set up the File System Watcher
    fileWatcher = new FileSystemWatcher();

    fileWatcher.Changed += OnChanged;
    fileWatcher.Created += OnChanged;
    fileWatcher.Deleted += OnChanged;
    fileWatcher.Renamed += OnChanged;

    fileWatcher.NotifyFilter = NotifyFilters.LastWrite |
                                NotifyFilters.FileName |
                                NotifyFilters.DirectoryName;

    // Try to set the path. 
    try
    {
        fileWatcher.Path = folderToMonitor;
    }
    catch(ArgumentException e)
    {
        Logger.WriteLine("Folder doesn't seem to exist or be accesible");
    }
}


We need that try catch block in there. If we were running Mayhem and configured the module to a certain folder, shut down Mayhem, deleted the folder and started it back up folderToMonitor would contain the old path, but fileWatcher would throw an exception because the folder no longer exists.

The event handler for the FileSystemMonitor events is very simple but at the same time very important. We need to call Trigger() from EventBase inside of the event handler so that the event will actually work!

private void OnChanged(object o, FileSystemEventArgs a)
{
    Trigger();
}

Enabling and Disabling

Events need a way to be disabled or enabled. This occurs either when the user turns the associated Mayhem Connection on and off or when the configuration dialog is shown, at which time events get disabled by default. To build useful events, we thus need to override the OnEnabling() and OnDisabled() methods from EventBase:

protected override void OnEnabling(EnablingEventArgs e)
{
    fileWatcher.EnableRaisingEvents = true;
}
protected override void OnDisabled(DisabledEventArgs e)
{
    fileWatcher.EnableRaisingEvents = false;
}

In our case, the only thing we need to do when disabling/enabling the event is to to enable or disable events originating from the FileSystemWatcher.

WPF Configuration Dialog

We want the user to be able to configure the directory to monitor changes in so we need to create a configuration window.

To do this, let's have our event class extend the IWpfConfigurable interface from MayhemWpf.

Each Mayhem Event that implements the IWpfConfigurable interface must implement the ConfigurationControl() property and return a configuration dialog:

 

public WpfConfiguration ConfigurationControl
{
    get
    {
        return new FolderChangeConfig(folderToMonitor);
    }
}

Adding the XAML

As you might have noticed, we still need to implement the actual dialog class which we already referred to as “FolderChangeConfig” in the code above. So, the next step is to create a WPF UserControl in visual studio. To do this follow the following steps:

1. In the Solution Explorer, right-click and select Add -> New Item…

2. Find “User Control (WPF)” and change the name to FolderChangeConfig.xaml

3. Click “Add”

Visual Studio will create the FolderChangeConfig.xaml interface definition file as well as the FolderChangeConfig.xaml.cs Code-Behind file.

Click on FolderChangeConfig.xaml.cs and derive from WpfConfiguration instead of UserControl. We also need to add a title for the WpfConfiguration. This can be set in the Code-Behind file by overriding Title

public override string Title
{
    get
    {
        return "Folder Change";
    }
}

Click on FolderChangeConfig.xaml to open the WPF interface builder. Here you need to change the header to load some additional classes from the MayhemWpf namespace. It should look as follows:

<src:WpfConfiguration x:Class="DefaultModules.Wpf.FolderChangeConfig"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:src="clr-namespace:MayhemWpf.UserControls;assembly=MayhemWpf"
             Width="400">

Make sure that the file now also closes with the following tag:
</src:WpfConfiguration>

In this dialog, we want the user to be able to select a directory to monitor.  The example interface is implemented as a WPF grid. The main interactive component of theconfiguration interface is the “Browse” button, which allows the user to select a directory. Row 1 of the grid is reserved for a special “ConfigErrorMessage” label, that comes out of a hiding state when there is a problem with the specified directory path. The final interface grid should look similar to this:

<src:WpfConfiguration x:Class="DefaultModules.Wpf.FolderChangeConfig" 
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"           
                      xmlns:src="clr-namespace:MayhemWpf.UserControls;assembly=MayhemWpf" 
                      Width="400">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="240" />
            <ColumnDefinition Width="60" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>

        <TextBlock HorizontalAlignment="Left" Style="{StaticResource ConfigLabel}" Grid.Column="0" Grid.Row="0">Monitor Folder:</TextBlock>
        <TextBox Name="LocationBox" TextChanged="LocationBox_TextChanged" Grid.Column="1" Grid.Row="0" />
        <Button Height="25" Margin="5,0,0,0" Click="Browse_Click" Grid.Column="2" Grid.Row="0">Browse</Button>

        <TextBlock Style="{StaticResource ConfigErrorMessage}" Name="textInvalid" Grid.Column="1" Grid.Row="1">Folder location is invalid</TextBlock>
    </Grid>
</src:WpfConfiguration>

 

Tying it all Together: The Code-Behind

In the Code-Behind file, FolderChangeConfig.xaml.cs, the interface events need to be hooked up, so we need to implement the hooks specified in the .xaml file. The first of which is the Browse Button. When the button is clicked, we open a dialog that lets the user specify a directory to monitor.

private void Browse_Click(object sender, RoutedEventArgs e)
{
    var dlg = new FolderBrowserDialog();
    dlg.ShowNewFolderButton = true;
    dlg.SelectedPath = FolderToMonitor;

    if (dlg.ShowDialog() == DialogResult.OK)
    {
        LocationBox.Text = dlg.SelectedPath;
    }
}

 
Next is a way for us to check if the entered folder is valid.

private void LocationBox_TextChanged(object sender, TextChangedEventArgs e)
{
    CanSave = true;
    if (!(LocationBox.Text.Length > 0 && Directory.Exists(LocationBox.Text)))
    {
        CanSave = false;
    }

    textInvalid.Visibility = CanSave ? Visibility.Collapsed : Visibility.Visible;
}

When the textbox containing the directory name changes, we want to check that the folder entered really exists. The CanSave boolean flag controlls the enabled state of the save button. We also make the error message we added visible if the user can't save so that they know what to fix.

Communication with the Event

The XAML configuration needs some way of reporting the user settings to the Event that generates it and also fill in the textbox with the values stored in the event. Setting default values is done through the constructor of the configuration, and reporting settings back is done through properties. The configuration constructor thus takes relevant settings from the event in its constructor and saves them temporarily in some fields, and in the case of the folder location in the “FolderToMonitor” property. Add the following to the FolderChangeConfig.xaml.cs file.

public string FolderToMonitor
{
    get;
    private set; 
}
 
public FolderChangeConfig(string path)
{
    InitializeComponent();
    FolderToMonitor = path;
}

In order to set things on the GUI, we have to wait for the controls to be created. To do this, lets override WpfConfiguration's OnLoad() method.

Here we set the location box to show the value stored in the event to the user:

public override void OnLoad()
{
    textBoxDirectory.Text = FolderToMonitor;
}

We are now all set up to create a fully functioning configuration window. We now need the event to take the user selected folder location and use that new setting. This is done by subclassing  OnSaved() in the event implementation ( FolderChange.cs):

public void OnSaved(WpfConfiguration configurationControl)
{
    FolderChangeConfig config = configurationControl as FolderChangeConfig;
    folderToMonitor = config.FolderToMonitor;

    fileWatcher.Path = folderToMonitor;
}

The OnSaved method is passed the instance of the configuration window your event created. So we need to cast it in order to get access to the important property. Once we have the data, we set the path on the file watcher.

We now have one last method to implement. The GetConfigString method. This method takes the configuration values for the event and creates a string that shows what the configuration is. This string is shown on the main Mayhem window.

A good implementation for our folder change event would be to display part of the path.

public string GetConfigString()
{
    return Path.GetDirectoryName(folderToMonitor);
}

 

Conclusion

We now need to build the class library we have created and add it to Mayhem. So build your project, and locate the dll you created in your project’s bin folder.

Locate the main Mayhem directory, if you compiled the Mayhem source itself, then that is the top-most bin folder. If you installed Mayhem from the web, that is %ProgramFiles%/Outercurve/Mayhem. Just paste your dll into that folder."

You now have a fully functioning event that monitors a user defined folder. Build on top of this, and take it as far as you'd like.
Expanding on this specific event you could try to implement an extended version that monitors the folder’s size and fires an event when the size has exceeded a certain threshold.

Last edited Mar 17, 2012 at 8:18 PM by TheSavior, version 16

Comments

No comments yet.