EDN Admin
Well-known member
Why, hello there! We are going to pick up where we left off at the end of our Coding4Fun video. In the video we built a reaction that increments the volume level on the computer. We are going to expand from there to make a reaction that enables you to set the volume level to a specified volume. <iFrame src="http://channel9.msdn.com/Blogs/Clint/Project-Mayhem-Creating-a-module/player?w=512&h=288" frameborder="0" scrolling="no" width="512px" height="288px </iFrame> http://media.ch9.ms/c4f/c4f/MayhemVolumeStart.zip MayhemVolumeStart.zip file contains the class library project all set up with the NuGet package references and the reaction that increments the volume. The first step we are going to take is to add the additional references we need to be configurable. The first reference is MayhemWpf from the same NuGet feed MayhemCore is from. ( http://makemayhem.com/nuget http://makemayhem.com/nuget ). Add a reference to the following assemblies: PresentationCore PresentationFramework System.Runtime.Serialization System.Xaml WindowsBase . Next, we switch the code from incrementing the volume to setting the volume to 50%. To do this, replace the line: <pre class="brush: csharp
device.AudioEndpointVolume.VolumeStepUp();
[/code] with <pre class="brush: csharp
device.AudioEndpointVolume.MasterVolumeLevelScalar = 50 / 100.0f;
[/code] The reaction now works successfully to set the volume to half way. The next step is to make that setting configurable, and persist between application runs. Let’s make that 50 a variable. Add a private integer to the class (I’m calling it level) and change the above line to be: <pre class="brush: csharp
device.AudioEndpointVolume.MasterVolumeLevelScalar = level / 100.0f;
[/code] Since that variable will be user configurable, we want to be able to show a string on the main window that says what the reaction is doing. To do this, we have to tell Mayhem that our reaction is configurable by implement the IWpfConfigurable interface from MayhemWpf . In order to set that configuration string we have to implement the method GetConfigString . The implementation of this method should return some string that provides a look into what the configuration settings are for the reaction. I will make the GetConfigString method be the following: <pre class="brush: csharp
public string GetConfigString()
{
return string.Format("Set the volume to {0}%",level);
}
[/code] <pre> [/code] Now, in order to make the module save the configured state when the application is shut down and restarted, we need to add a little bit of data contract code. We are going to add the [DataContract] attribute to our class. This attribute comes from the System.Runtime.Serialization framework. Our class header now looks like: <pre class="brush: csharp
[DataContract]
[MayhemModule("Set Volume", "Sets the master volume level")]
public class SetVolume : ReactionBase, IWpfConfigurable
[/code] Note that I also changed the MayhemModule attribute’s name and description to reflect the new functionality. We also need to mark the configurable variables to be saved as well. To do that, add the [DataMember] attribute to our level variable: <pre class="brush: csharp
[DataMember]
private int level;
[/code] In order to set a default value for this variable, we shouldn’t initialize it using a constructor. Constructors should never be used in modules. Instead, we have a method that should be overridden that you get from ReactionBase . OnLoadDefaults is to load the settings you want to have by default. <pre class="brush: csharp
protected override void OnLoadDefaults()
{
level = 50;
}
[/code] Now when you use the SetVolume reaction in Mayhem, it will at 50%, but if that value is changed and the application is restarted, it will be set to the value it was at before it was closed! The next step is to make the configuration window that actually enables that variable to be modified. Add a new WPF UserControl item to the project named SetVolumeConfig.xaml . Replace the contents of the XAML file with: <pre class="brush: xml
<src:WpfConfiguration x:Class="Volume.SetVolumeConfig"
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="300
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Style="{StaticResource ConfigLabel}" Grid.Column="0" Grid.Row="0 Volume:</TextBlock>
<Slider Grid.Column="1" Grid.Row="0" Height="23" HorizontalAlignment="Left" Name="VolumeSlider" VerticalAlignment="Top" Width="200" Maximum="100" />
</Grid>
</src:WpfConfiguration>
[/code] This configuration window simply has a volume slider allowing values between 0 and 100. WpfConfiguration handles the display of how the module asks for information. As you hopefully notice, it doesn’t contain the save button, or the title of the window. All that is handled for you so that we can maintain consistency between configuration windows. We also provide default styles that further maintain consistency and user expectation. We now need to create the backing code that makes the configuration window come together. Change SetVolumeConfig.xaml.cs to extend the base class WpfConfiguration instead of the default UserControl base class. In the constructor, we should ask for all the information we need to populate the config window with the current settings. In this situation, that only means the current volume level setting. Add an int parameter to the constructor called level . Also add a public property on the class with a private setter called Level . In the constructor, set this property with the parameter passed in to the constructor. Your class should now look like this: <pre class="brush: csharp
using MayhemWpf.UserControls;
namespace Volume
{
public partial class SetVolumeConfig : WpfConfiguration
{
public int Level
{
get;
private set;
}
public SetVolumeConfig(int level)
{
this.Level = level;
InitializeComponent();
}
}
}
[/code] WpfConfiguration gives us some handy hooks that we can use. We need to set the value property on our slider. This could be done with data bindings, but for simplicity we will just be doing this with code. Also, there is a Boolean flag in the WpfConfiguration class that enables the Save button on the dialog. When handling user input, the save button should only be enabled if the input is valid. Since the slider always has a value in our range, the Save button can always be enabled. We will set this flag in the OnLoad method. Override the OnLoad method as follows: <pre class="brush: csharp
public override void OnLoad()
{
VolumeSlider.Value = this.Level;
CanSave = true;
}
[/code] We then also override the OnSave method to set the property to the value the SetVolume class will expect. <pre class="brush: csharp
public override void OnSave()
{
this.Level = (int)VolumeSlider.Value;
}
[/code] Lastly, we need to set the title that shows up on the configuration window. To do this, override the Title property and have it return “Set Volume”: <pre class="brush: csharp
public override string Title
{
get { return "Set Volume"; }
}
[/code] Now we need to go back to the SetVolume class and implement those configuration related methods. The ConfigurationControl property needs to return an instance of the SetVolumeConfig class. Remember that its constructor expects the current volume setting: <pre class="brush: csharp
public WpfConfiguration ConfigurationControl
{
get
{
return new SetVolumeConfig(level);
}
}
[/code] Lastly, OnSaved takes the instance of WpfConfiguration control we just returned in the above property. We need to cast this back to SetVolumeConfig to be able to get access to the Level property: <pre class="brush: csharp
public void OnSaved(WpfConfiguration configurationControl)
{
var config = (SetVolumeConfig)configurationControl;
this.level = config.Level;
}
[/code] That’s all it takes! This code provides us with a complete reaction that enables us to set the volume level on the computer. As you can see, we provide a very straightforward API enabling you, the developer, to write user configurable modules for Mayhem. You can click the http://media.ch9.ms/c4f/c4f/MayhemVolumeFinal.zip download link at the top of the screen to get the final code in MayhemVolumeFinal.zip. We have more documentation available on the available methods and APIs on our http://mayhem.codeplex.com/documentation CodePlex documentation page . <img src="http://m.webtrends.com/dcs1wotjh10000w0irc493s0e_6x1g/njs.gif?dcssip=channel9.msdn.com&dcsuri=http://channel9.msdn.com/Feeds/RSS&WT.dl=0&WT.entryid=Entry:RSSView:faf9580cdc464e22a2d6a041014269f4
View the full article
device.AudioEndpointVolume.VolumeStepUp();
[/code] with <pre class="brush: csharp
device.AudioEndpointVolume.MasterVolumeLevelScalar = 50 / 100.0f;
[/code] The reaction now works successfully to set the volume to half way. The next step is to make that setting configurable, and persist between application runs. Let’s make that 50 a variable. Add a private integer to the class (I’m calling it level) and change the above line to be: <pre class="brush: csharp
device.AudioEndpointVolume.MasterVolumeLevelScalar = level / 100.0f;
[/code] Since that variable will be user configurable, we want to be able to show a string on the main window that says what the reaction is doing. To do this, we have to tell Mayhem that our reaction is configurable by implement the IWpfConfigurable interface from MayhemWpf . In order to set that configuration string we have to implement the method GetConfigString . The implementation of this method should return some string that provides a look into what the configuration settings are for the reaction. I will make the GetConfigString method be the following: <pre class="brush: csharp
public string GetConfigString()
{
return string.Format("Set the volume to {0}%",level);
}
[/code] <pre> [/code] Now, in order to make the module save the configured state when the application is shut down and restarted, we need to add a little bit of data contract code. We are going to add the [DataContract] attribute to our class. This attribute comes from the System.Runtime.Serialization framework. Our class header now looks like: <pre class="brush: csharp
[DataContract]
[MayhemModule("Set Volume", "Sets the master volume level")]
public class SetVolume : ReactionBase, IWpfConfigurable
[/code] Note that I also changed the MayhemModule attribute’s name and description to reflect the new functionality. We also need to mark the configurable variables to be saved as well. To do that, add the [DataMember] attribute to our level variable: <pre class="brush: csharp
[DataMember]
private int level;
[/code] In order to set a default value for this variable, we shouldn’t initialize it using a constructor. Constructors should never be used in modules. Instead, we have a method that should be overridden that you get from ReactionBase . OnLoadDefaults is to load the settings you want to have by default. <pre class="brush: csharp
protected override void OnLoadDefaults()
{
level = 50;
}
[/code] Now when you use the SetVolume reaction in Mayhem, it will at 50%, but if that value is changed and the application is restarted, it will be set to the value it was at before it was closed! The next step is to make the configuration window that actually enables that variable to be modified. Add a new WPF UserControl item to the project named SetVolumeConfig.xaml . Replace the contents of the XAML file with: <pre class="brush: xml
<src:WpfConfiguration x:Class="Volume.SetVolumeConfig"
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="300
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Style="{StaticResource ConfigLabel}" Grid.Column="0" Grid.Row="0 Volume:</TextBlock>
<Slider Grid.Column="1" Grid.Row="0" Height="23" HorizontalAlignment="Left" Name="VolumeSlider" VerticalAlignment="Top" Width="200" Maximum="100" />
</Grid>
</src:WpfConfiguration>
[/code] This configuration window simply has a volume slider allowing values between 0 and 100. WpfConfiguration handles the display of how the module asks for information. As you hopefully notice, it doesn’t contain the save button, or the title of the window. All that is handled for you so that we can maintain consistency between configuration windows. We also provide default styles that further maintain consistency and user expectation. We now need to create the backing code that makes the configuration window come together. Change SetVolumeConfig.xaml.cs to extend the base class WpfConfiguration instead of the default UserControl base class. In the constructor, we should ask for all the information we need to populate the config window with the current settings. In this situation, that only means the current volume level setting. Add an int parameter to the constructor called level . Also add a public property on the class with a private setter called Level . In the constructor, set this property with the parameter passed in to the constructor. Your class should now look like this: <pre class="brush: csharp
using MayhemWpf.UserControls;
namespace Volume
{
public partial class SetVolumeConfig : WpfConfiguration
{
public int Level
{
get;
private set;
}
public SetVolumeConfig(int level)
{
this.Level = level;
InitializeComponent();
}
}
}
[/code] WpfConfiguration gives us some handy hooks that we can use. We need to set the value property on our slider. This could be done with data bindings, but for simplicity we will just be doing this with code. Also, there is a Boolean flag in the WpfConfiguration class that enables the Save button on the dialog. When handling user input, the save button should only be enabled if the input is valid. Since the slider always has a value in our range, the Save button can always be enabled. We will set this flag in the OnLoad method. Override the OnLoad method as follows: <pre class="brush: csharp
public override void OnLoad()
{
VolumeSlider.Value = this.Level;
CanSave = true;
}
[/code] We then also override the OnSave method to set the property to the value the SetVolume class will expect. <pre class="brush: csharp
public override void OnSave()
{
this.Level = (int)VolumeSlider.Value;
}
[/code] Lastly, we need to set the title that shows up on the configuration window. To do this, override the Title property and have it return “Set Volume”: <pre class="brush: csharp
public override string Title
{
get { return "Set Volume"; }
}
[/code] Now we need to go back to the SetVolume class and implement those configuration related methods. The ConfigurationControl property needs to return an instance of the SetVolumeConfig class. Remember that its constructor expects the current volume setting: <pre class="brush: csharp
public WpfConfiguration ConfigurationControl
{
get
{
return new SetVolumeConfig(level);
}
}
[/code] Lastly, OnSaved takes the instance of WpfConfiguration control we just returned in the above property. We need to cast this back to SetVolumeConfig to be able to get access to the Level property: <pre class="brush: csharp
public void OnSaved(WpfConfiguration configurationControl)
{
var config = (SetVolumeConfig)configurationControl;
this.level = config.Level;
}
[/code] That’s all it takes! This code provides us with a complete reaction that enables us to set the volume level on the computer. As you can see, we provide a very straightforward API enabling you, the developer, to write user configurable modules for Mayhem. You can click the http://media.ch9.ms/c4f/c4f/MayhemVolumeFinal.zip download link at the top of the screen to get the final code in MayhemVolumeFinal.zip. We have more documentation available on the available methods and APIs on our http://mayhem.codeplex.com/documentation CodePlex documentation page . <img src="http://m.webtrends.com/dcs1wotjh10000w0irc493s0e_6x1g/njs.gif?dcssip=channel9.msdn.com&dcsuri=http://channel9.msdn.com/Feeds/RSS&WT.dl=0&WT.entryid=Entry:RSSView:faf9580cdc464e22a2d6a041014269f4
View the full article