The Code-Behind

0
213

Listing 2.2 contains the code-behind, which must handle all the special features of this flashlight—strobe mode, SOS mode, and various colors.

LISTING 2.2 MainPage.xaml.cs—The Code-Behind for Flashlight

[code]

using System;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
namespace WindowsPhoneApp
{
public partial class MainPage : PhoneApplicationPage
{
// Members for the two application bar buttons:
IApplicationBarIconButton sosButton;
IApplicationBarIconButton strobeButton;
// For the two special modes:
SolidColorBrush onBrush;
SolidColorBrush offBrush = new SolidColorBrush(Colors.Black);
DispatcherTimer strobeTimer = new DispatcherTimer();
DispatcherTimer sosTimer = new DispatcherTimer();
int sosStep;
// Remember the chosen color, for future app activations or launches:
Setting<Color> savedColor = new Setting<Color>(“SavedColor”, Colors.White);
// The current mode (Solid, Sos, or Strobe)
FlashlightMode mode = FlashlightMode.Solid;
public MainPage()
{
InitializeComponent();
// Assign application bar buttons to member fields, because this cannot be
// done by InitializeComponent:
this.sosButton = this.ApplicationBar.Buttons[0]
as IApplicationBarIconButton;
this.strobeButton = this.ApplicationBar.Buttons[1]
as IApplicationBarIconButton;
// Initialize the timer for strobe mode
this.strobeTimer.Interval = TimeSpan.FromSeconds(.1); // Not too fast!
this.strobeTimer.Tick += StrobeTimer_Tick;
// Initialize the timer for SOS mode
this.sosTimer.Interval = TimeSpan.Zero;
this.sosTimer.Tick += SosTimer_Tick;
// Attach the same Click handler to all menu items in the application bar
foreach (IApplicationBarMenuItem menuItem in this.ApplicationBar.MenuItems)
menuItem.Click += MenuItem_Click;
// Restore persisted color
this.onBrush = new SolidColorBrush(this.savedColor.Value);
this.BackgroundGrid.Background = onBrush;
}
// The menu item Click handler that changes the flashlight color
void MenuItem_Click(object sender, EventArgs e)
{
// Grab the text from the menu item to determine the desired color
string chosenColor = (sender as IApplicationBarMenuItem).Text;
// Use reflection to turn the color name (e.g. “red”) into an actual Color
Color c = (Color)typeof(Colors).GetProperty(chosenColor,
BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase).
GetValue(null, null);
// Persist this choice and set the background color
this.savedColor.Value = c;
this.onBrush = new SolidColorBrush(this.savedColor.Value);
this.BackgroundGrid.Background = onBrush;
}
// The Click handler for the strobe button
void StrobeButton_Click(object sender, EventArgs e)
{
// First, reset the current state to solid mode
FlashlightMode mode = this.mode;
RestoreSolidMode();
// If we were already in strobe mode, then this click
// cancels it and we are done
if (mode == FlashlightMode.Strobe)
return;
// Show a warning
MessageBoxResult result = MessageBox.Show(“Strobe lights can trigger “ +
“seizures for people with photosensitive epilepsy. “ +
“Are you sure you want to start the strobe light?”,
“Warning!”, MessageBoxButton.OKCancel);
// If the user agreed, change to strobe mode
if (result == MessageBoxResult.OK)
{
// Change the button icon, the mode, and start the timer
(sender as IApplicationBarIconButton).IconUri =
new Uri(“Images/cancel.png”, UriKind.Relative);
this.mode = FlashlightMode.Strobe;
this.strobeTimer.Start();
}
}
void StrobeTimer_Tick(object sender, EventArgs e)
{
// Toggle the background on every tick
if (this.BackgroundGrid.Background == this.onBrush)
this.BackgroundGrid.Background = this.offBrush;
else
this.BackgroundGrid.Background = this.onBrush;
}
// The Click handler for the SOS button
void SosButton_Click(object sender, EventArgs e)
{
// First, reset the current state to solid mode
FlashlightMode mode = this.mode;
RestoreSolidMode();
// If we were already in SOS mode, then this click
// cancels it and we are done
if (mode == FlashlightMode.Sos)
return;
// Change to SOS mode
// Change the button icon, the mode, a counter, and start the timer
(sender as IApplicationBarIconButton).IconUri =
new Uri(“Images/cancel.png”, UriKind.Relative);
this.mode = FlashlightMode.Sos;
this.sosStep = 0;
this.sosTimer.Start();
}
void SosTimer_Tick(object sender, EventArgs e)
{
// Toggle the background, but also adjust the time between each tick in
// order to make the dot-dot-dot-dash-dash-dash-dot-dot-dot pattern
switch (this.sosStep)
{
case 1: case 3: case 5: // Each dot in the first S
case 13: case 15: case 17: // Each dot in the second S
this.BackgroundGrid.Background = this.onBrush;
this.sosTimer.Interval = TimeSpan.FromSeconds(.2); // A short value
break;
case 7: case 9: case 11: // Each dash in the O
this.BackgroundGrid.Background = this.onBrush;
this.sosTimer.Interval = TimeSpan.FromSeconds(1); // A long value
break;
case 18: // The space between the end of one SOS
// and the beginning of the next one
this.BackgroundGrid.Background = this.offBrush;
this.sosTimer.Interval = TimeSpan.FromSeconds(1);
break;
default: // The space between each dot/dash
this.BackgroundGrid.Background = this.offBrush;
this.sosTimer.Interval = TimeSpan.FromSeconds(.2);
break;
}
// Cycle from 0 – 18
this.sosStep = (this.sosStep + 1) % 19;
}
// Reset the state associated with mode switches
void RestoreSolidMode()
{
this.strobeTimer.Stop();
this.sosTimer.Stop();
this.BackgroundGrid.Background = onBrush;
this.sosButton.IconUri = new Uri(“Images/sos.png”, UriKind.Relative);
this.strobeButton.IconUri = new Uri(“Images/strobe.png”, UriKind.Relative);
this.mode = FlashlightMode.Solid;
}
// All three modes
enum FlashlightMode
{
Solid,
Sos,
Strobe
}
}
}

[/code]

Notes:

  • The sosButton and strobeButton fields are explicitly assigned by referencing items in the application bar’s Buttons collection because the XAML naming approach is not supported for items in the application bar, as described in an earlier warning. Assigning such member variables right after InitializeComponent is a nice practice to avoid hardcoded indices scattered throughout your code.
  • Two DispatcherTimers are used to perform the SOS and strobe on/off patterns. This technique is described in an upcoming sidebar.
  • Rather than setting all eight menu item Click event handlers in XAML, the constructor loops through the MenuItems collection and assigns MenuItem_Click to each item. This shortcut works nicely in this case because the same handler is able to work for all menu items.
  • Once again, an instance of the Setting class is used to remember the user’s color preference for subsequent usage of the app. Because BackgroundGrid’s background is set in the code-behind, the “White” setting in XAML in Listing 2.1 is actually unnecessary.
  • To work for every menu item, the MenuItem_Click handler must figure out which color has just been selected. To do this, it retrieves the label from the menu item that was just tapped (passed as the sender) then uses a .NET reflection trick to turn the string into an instance of the Color class. This works thanks to the static Colors class that
    contains named properties for several color instances. Note that this trick only works for the small set of colors represented by this class. You could define your own Colors class with a different set of properties, however, if you wanted to do this with different colors.
  • In this app, the chosen color setting is persisted as soon as a new one is selected (inside MenuItem_Click). This is unlike the previous app, in which this action was only done when the page was about to be departed (inside OnNavigatedFrom).
  • The sender in MenuItem_Click is cast to IApplicationBarMenuItem (an interface implemented by ApplicationBarMenuItem) rather than directly to ApplicationBarMenuItem. A similar thing is done in later event handlers with IApplicationBarIconButton when the sender is an ApplicationBarIconButton. The
    Windows Phone team prefers that code references the interfaces rather than the concrete classes to allow for future flexibility, although this is only important for class libraries that don’t also instantiate the concrete button and menu item instances (as this app does in its XAML).
  • The strobe button Click handler, StrobeButton_Click, shows a standard message box to guard against accidental activation of strobe mode and to educate the user about the danger of strobe lights.
  • The Tick event handler for the strobe timer simply toggles the background between the “on” brush (white, unless the user changed the color) and the “off” brush (black). Once the strobe timer has been started, this is called every .1 seconds, as configured in this page’s constructor. Although you could certainly make this toggle more frequently, I would strongly caution against it. When I tried a value of .05 seconds instead, I got a bad headache after a quick test!
  • Both button Click handlers temporarily change the button’s icon to a cancel image because a subsequent click to the button returns the flashlight to solid mode. They use the sender to access the tapped button just for demonstration, as they could have easily used the strobeButton and sosButton fields instead.
  • The Tick event handler for the SOS timer is more complicated than the handler for the strobe timer, as it has to
    repeatedly produce the Morse code pattern for the SOS distress signal (dot-dot-dot-dash-dash-dash-dotdot-
    dot). It dynamically adjusts the timer’s interval to achieve this effect.
  • The capitalization of Sos in the code follows a .NET Framework coding guideline of avoiding more than two consecutive capital letters, even for a well-known abbreviation. You can see this practice throughout the .NET
    Framework APIs, with terms such as Uri, Xml, Xaml, and so on. Note that this guideline does not apply to labels inside user interfaces!

DispatcherTimer and Other Time-Based Approaches for Executing Code

DispatcherTimer, used by Flashlight, is the most natural timer to use in a Silverlight app.You can start and stop it at any time, customize its frequency with its Interval property, and handle its Tick event to perform work at the chosen interval. Event handlers for Tick are guaranteed to be called on the UI thread, so code inside these handlers can manipulate elements on the page the same way this is done everywhere else. DispatcherTimer is not the only timer available, however.

The System.Threading namespace has a Timer class that provides similar functionality, but the callback you provide does not get called on the UI thread.With this mechanism, you need to partition any logic that updates the UI into a different method and use the page’s dispatcher to invoke it on the UI thread.Here’s an example:

[code]

void TimerCallback(object state)
{
// Call the DoTheRealWork method on the UI thread:
this.Dispatcher.BeginInvoke(DoTheRealWork);
}

[/code]

Unless your timer-based code has no need to update the UI, you should stick to using DispatcherTimer instead of Timer.

The Reactive Extensions for .NET also includes a mechanism for creating a sequence that produces each value at a timed interval (Microsoft.Phone.Reactive.Observable.Timer) but the apps in this book series avoid using Reactive Extensions for the sake of having easilyunderstood code.

Any of these timers can work great for the needs of Flashlight and apps like it, but they should not be used for animations.These timers are not in sync with the screen’s refresh rate, nor are they in sync with the Silverlight rendering engine. Instead,many animations should use the animations classes covered throughout Part II,“Transforms & Animations.”These classes could even be used in Flashlight instead of a timer.

Complex animations (such as physics-based animations) can use a static CompositionTarget.Rendering event that gets raised on every frame, regardless of the exact timing

Message Boxes

Flashlight uses a message box to show a standard warning that enables the user to cancel the action. On most platforms, using a message box to communicate information is indicative of a lazy programmer who doesn’t want to create a nicer-looking user interface. On Windows Phone, however, a message box is not only appropriate for many situations, but it has a lot of niceties that are hard to create on your own! As with the phone’s builtin apps, it animates in and out (with a flip), it dims and disables the rest of the screen (including the application bar), its buttons tilt when pressed, it automatically shows the status bar with a background that matches the message box background (regardless of the
app’s SystemTray.IsVisible setting), it makes a pleasant sound, and it vibrates. Naturally, it also respects the user’s theme and the phone’s orientation.

MessageBox contains two overloads of its static Show method. With one, you simply pass a single piece of text:

[code]MessageBox.Show(“This is the message.”);[/code]

The standard message box with no caption and a single OK button.
FIGURE 2.7 The standard message box with no
caption and a single OK button.

As shown in Figure 2.7, the resultant message box shows the message with a single OK button. It looks odd because it
has no caption, so apps should not use this overload of Show.

The message box used by Flashlight, shown in the context of the entire page.
FIGURE 2.8 The message box used by
Flashlight, shown in the context of the entire page.

The more functional overload of Show, used by Flashlight, enables you to set the text and caption, plus choose what
buttons you want with a value from the MessageBoxButton enumeration: OK (a single OK button) or OKCancel (two
buttons—OK and cancel). Figure 2.8 shows the message box created back in Listing 2.2.

Both overloads of Show return a MessageBoxResult enumeration value that indicates which button, if any, was tapped. The only supported values are OK and Cancel. The latter is returned if the user taps the cancel button or if the user simply dismisses the message box with the hardware Back button.

Unfortunately, MessageBox.Show does not support custom labels for the two buttons. The “ok” and “cancel” labels are
all you get. Built-in phone apps, on the other hand, often customize the “ok” label to be more specific to the task at
hand, such as “call” versus “don’t call” or “delete” versus “cancel.”

You can actually customize the text on the two message box buttons, but not with the MessageBox class. Instead, this functionality is hidden in an odd place—the Microsoft.Xna.Framework.GamerServices assembly! The Guide class in the
Microsoft.Xna.Framework.GamerServices namespace provides a pair of static methods that any app (XNA or Silverlight) can use without any special capabilities—BeginShowMessageBox and EndShowMessageBox. BeginShowMessageBox can be used as follows:

[code]

Guide.BeginShowMessageBox(“Title”,
“This is the message.”,
new string[] { “button 1”, “button 2” }, // 2 buttons with custom labels
0, // Button index that has focus
// (irrelevant for the phone)
MessageBoxIcon.None, // This is ignored
new AsyncCallback(OnMessageBoxClosed), // Callback to process result
null // Custom state given to callback
);

[/code]

The OnMessageBoxClosed callback, which uses EndShowMessageBox, can look as follows:

[code]

void OnMessageBoxClosed(IAsyncResult result)
{
// See which button was tapped (if any)
int? buttonIndex = Guide.EndShowMessageBox(result);
if (buttonIndex == 1)
// Perform action #1
else if (buttonIndex == 2)
// Perform action #2
else
// Message box was dismissed with the hardware back button
}

[/code]

Despite the fact that you pass an arbitrary list of button labels to BeginShowMessageBox, only one or two labels are supported because you can only have one or two buttons. When using your own labels, be sure to follow design guidelines by putting the positive OKstyle button on the left and the negative cancel-style button on the right.

special features of this flashlight
special features of this flashlight