Using Generic Attached Properties to Simplify Windows 8 Settings Charm Pages

By January 16, 2013.NET, C#, Fun, Microsoft, Tools

I’ve always liked attached properties… so much so that a couple of years ago I created a series of generic classes to simplify the process of creating custom attached properties.

Attached Properties – a review:

  • External control interactions — like the Grid.Row attached property.
  • Basic info storage — just like the Tag property.
  • Behaviors — changing properties and behaviors of the parent control.
  • Bindings — to achieve custom bindings between UI controls.
  • If you use XAML, you use attached properties whether you realize it or not.

Creating a custom attached property only takes a few lines of code. Many declarative types depend upon each other, such as in the example code below. Miss changing just one, and at best you’ll get an error. At worst, the code will run and produce unpredictable and hard-to-track-down behaviors. If you copy and paste, eventually you will forget to change one of the dependencies.

 

Generic Attached Properties – a review:

It was easy to take the custom attached property pattern and put a generic spin on it. 

public class GenericAttachedProperty<A>
{
    public static readonly DependencyProperty ValueProperty =
            DependencyProperty.RegisterAttached(
            "Value",
            typeof(A),
            typeof(GenericAttachedProperty<A>),
            null);
 
    public static A GetValue(DependencyObject d)
    {
        return (A)d.GetValue(ValueProperty);
    }
 
    public static void SetValue(DependencyObject d, A value)
    {
        d.SetValue(ValueProperty, value);
    }
}

The generic classes that flowed from this base class have many advantages.

  • Do a great job with encapsulation of functionality.
  • Easy to use and extend.
  • Strongly typed.
  • Can be used with binding.
  • Can be used in code behind and XAML
  • Eliminates copy/paste errors.

To get a full accounting of the generic attached property classes that I created, you can review my previous blog entries on them.

Windows 8/Windows Phone 8 Update:

How do these classes work with Windows 8 and Windows Phone 8? With an update of the namespaces, the generic attached property classes absolutely work in both Windows 8 and Windows Phone 8. However, they do not work in XAML anymore, only code behind. For some that maybe an issue, but I’ve always been more of a code behind guy so it’s not as big of an issue for me.

#if WINDOWS_PHONE
using System.Windows;
#else
using Windows.UI.Xaml;
#endif

Without a doubt, the generic class, AttachedPropertyAssociatedObject, is the workhorse of the Generic Attached Property framework. It allows you to easily add behaviors to any Framework element. Previous blog entries that put the AttachedPropertyAssociatedObject to work and show its versatility are listed here:

After two years of continuous work, the AttachedPropertyAssociatedObject class gets an update as it starts its service to Window 8 and Window Phone 8.

The biggest change was to replace the complicated Dictionary framework with a simple ValueChanged attached property.

public static readonly DependencyProperty ValueChangedProperty =
    DependencyProperty.RegisterAttached(
        "ValueChanged",
        typeof(PropertyChangedCallback),
        typeof(AttachedPropertyAssociatedObject<O, T, A>),
        null);
 
public static PropertyChangedCallback GetValueChanged(DependencyObject d)
{
    return (PropertyChangedCallback)d.GetValue(ValueChangedProperty);
}
 
public static void SetValueChanged(DependencyObject d, PropertyChangedCallback value)
{
    d.SetValue(ValueChangedProperty, value);
}

 

In the AttachedPropertyAssociatedObject.OnValueChanged handler, we check to see if there is a value assigned to the new ValueChanged attached property. If found, we call it (in red).

protected static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    T associatedObject = d as T;
    if (associatedObject == null)
    {
        throw new Exception(String.Format("DependencyObject must be of type {0}", typeof(T)));
    }
 
    O attachedPropertyAssociatedObject = GetInstance(d);
    if (e.NewValue != null)
    {
        if (attachedPropertyAssociatedObject == null)
        {
            attachedPropertyAssociatedObject = new O();
            attachedPropertyAssociatedObject.AssociatedObject = associatedObject;
 
            SetInstance(d, attachedPropertyAssociatedObject);
            attachedPropertyAssociatedObject.Initialize();
        }
 
       PropertyChangedCallback callback = GetValueChanged(d);
        if (callback != null)
        {
            callback(associatedObject, e);
        }
    }
    else
    {
        if (attachedPropertyAssociatedObject != null)
        {
            attachedPropertyAssociatedObject.UnInitialize();
            SetInstance(d, null);
        }
    }
}

 

Let’s put it to some good use in Windows 8!

Windows 8 Settings Charm Pages Example:

We all know about the Charms bar in Windows 8 and that we should all start putting our settings pages under the Settings charm. Microsoft has a good example of adding these pages with their App settings sample. We’re not going to go into the details; that’s why we included the link to the sample. We’re going to assume you’re familiar with the process.

Adding new settings charm pages is not a hard concept, but there is a bunch of code to copy and paste. In short, this NEEDS to be handled by something based upon AttachedPropertyAssociatedObject.

We start off with a class to store the values of an instance of a settings charm page.

public class SettingsFlyoutInfo
{
    public double SettingsWidth { get; set; }
    public string SettingsID { get; set; }
    public string SettingsTitle { get; set; }
    public Type SettingsFlyoutType { get; set; }
}

 

We might need more than one settings charm page so we need a collection class. Note the PopupChanged event. This is very important when using the AdControl, but that’s for another blog entry.

public class SettingsFlyoutInfoCollection
{
    public SettingsFlyoutInfoCollection()
    {
        Infos = new Dictionary<string, SettingsFlyoutInfo>();
    }
 
    public event EventHandler<bool> PopupChanged;
    public Dictionary<string, SettingsFlyoutInfo> Infos { get; set; }
       
    internal void RaisePopupChanged(bool value)
    {
        if (this.PopupChanged != null)
        {
            this.PopupChanged(this, value);
        }
    }
 
    public void AddSettingsFlyoutInfo(Type type, string settingsID, string title, double width)
    {
        Infos.Add(settingsID, new SettingsFlyoutInfo()
        {
            SettingsFlyoutType = type,
            SettingsID = settingsID,
            SettingsTitle = title,
            SettingsWidth = width,
        });
    }
}

Now we get to the main class SettingsFlyoutAttachedProperty which is based upon AttachedPropertyAssociatedObject. Much of this code is just copied and pasted right out of the MS sample. The type of the attached property is the collection class we previously defined. Also note that we restrict the FrameworkElement attachment to a type of Page.

public class SettingsFlyoutAttachedProperty : WindowsStore.FalafelUtility.AttachedPropertyAssociatedObject<SettingsFlyoutAttachedProperty, Page, SettingsFlyoutInfoCollection>
{
    // Used to determine the correct height to ensure our custom UI fills the screen.
    private Rect windowBounds;
 
    // This is the container that will hold our custom content.
    private Popup settingsPopup;
 
    public override void Initialize()
    {
        windowBounds = Window.Current.Bounds;
        Window.Current.SizeChanged += Current_SizeChanged;
        SettingsPane.GetForCurrentView().CommandsRequested += onCommandsRequested;
    }
 
    public override void UnInitialize()
    {
        // Added to make sure the event handler for CommandsRequested is cleaned up before other scenarios.
        SettingsPane.GetForCurrentView().CommandsRequested -= onCommandsRequested;
 
        Window.Current.SizeChanged -= Current_SizeChanged;
    }
 
    void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
    {
        windowBounds = Window.Current.Bounds;
    } 

We handle the current view CommandsRequested event that gets triggered when the user selects the Settings Charm. We iterate through our collection and create a SettingsCommand instance for each of our desired pages. We also handle when we want to close any of our pages.

void onCommandsRequested(SettingsPane settingsPane, SettingsPaneCommandsRequestedEventArgs eventArgs)
{
    foreach (var item in this.Value.Infos.Values)
    {
        UICommandInvokedHandler handler = new UICommandInvokedHandler(onSettingsCommand);
 
        SettingsCommand generalCommand = new SettingsCommand(item.SettingsID, item.SettingsTitle, handler);
        eventArgs.Request.ApplicationCommands.Add(generalCommand);
    }
}
 
private void OnWindowActivated(object sender, Windows.UI.Core.WindowActivatedEventArgs e)
{
    if (e.WindowActivationState == Windows.UI.Core.CoreWindowActivationState.Deactivated)
    {
        settingsPopup.IsOpen = false;
    }
}
 
void OnPopupClosed(object sender, object e)
{
    Window.Current.Activated -= OnWindowActivated;
    this.Value.RaisePopupChanged(false);
}

 

This is the meat of the class. Here we are handling the SettingsCommand event that gets called when the user selects an instance of one of our Settings Charm pages. This code is copied and pasted directly from the MS sample, with minor changes to read values from our supporting classes. Now the SettingsFlyoutAttachedProperty class is complete.

void onSettingsCommand(IUICommand command)
{
    SettingsCommand scmd = command as SettingsCommand;
    var item = this.Value.Infos[scmd.Id.ToString()];
 
    // Create a Popup window which will contain our flyout.
    settingsPopup = new Popup();
    settingsPopup.Closed += OnPopupClosed;
    Window.Current.Activated += OnWindowActivated;
    settingsPopup.IsLightDismissEnabled = true;
    settingsPopup.Width = item.SettingsWidth;
    settingsPopup.Height = windowBounds.Height;
 
    // Add the proper animation for the panel.
    settingsPopup.ChildTransitions = new TransitionCollection();
    settingsPopup.ChildTransitions.Add(new PaneThemeTransition()
    {
        Edge = (SettingsPane.Edge == SettingsEdgeLocation.Right) ?
                EdgeTransitionLocation.Right :
                EdgeTransitionLocation.Left
    });
 
    // Create a SettingsFlyout the same dimenssions as the Popup.
    LayoutAwarePage mypane = Activator.CreateInstance(item.SettingsFlyoutType) as LayoutAwarePage;
    mypane.Width = item.SettingsWidth;
    mypane.Height = windowBounds.Height;
 
    // Place the SettingsFlyout inside our Popup window.
    settingsPopup.Child = mypane;
 
    // Let's define the location of our Popup.
    settingsPopup.SetValue(Canvas.LeftProperty, SettingsPane.Edge == SettingsEdgeLocation.Right ? (windowBounds.Width - item.SettingsWidth) : 0);
    settingsPopup.SetValue(Canvas.TopProperty, 0);
    settingsPopup.IsOpen = true;
    this.Value.RaisePopupChanged(true);
}

 

The actual page that the user sees must be based upon the LayoutAwarePage found in the MS sample. This class helps with, you guessed it, layout issues. From there you can add whatever content you need for your settings page.

 

Now that we have our generic attached property and our settings page, how much code do we need to initialize our page? Not much. We only need to create an instance of our collection class, add an instance of our info class, and set the value to our attached property class to our main page. Note that we clean up our pages if we navigate away from the main page.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    SettingsFlyoutInfoCollection collection = new SettingsFlyoutInfoCollection();
    collection.AddSettingsFlyoutInfo(typeof(SettingsFlyout), "FlyoutTestID", "Flyout Test", 500);
    SettingsFlyoutAttachedProperty.SetValue(this, collection);
}
 
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);
 
    SettingsFlyoutAttachedProperty.SetValue(this, null);
 
}

 

Running the Windows 8 Settings Charm Pages Example:

When running the example you can see that our Settings Charm page is shown as an option.

 

Selecting our charm option causes our settings page to be shown.

 

Need more pages? Just create additional pages based upon LayoutAwarePage and add them to the collection.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    SettingsFlyoutInfoCollection collection = new SettingsFlyoutInfoCollection();
    collection.AddSettingsFlyoutInfo(typeof(SettingsFlyout), "FlyoutTestID", "Flyout Test", 500);
    collection.AddSettingsFlyoutInfo(typeof(HelpFlyout), "HelpFlyoutID", "Help", 400);
    collection.AddSettingsFlyoutInfo(typeof(AboutFlyout), "AboutFlyoutID", "About", 450);
    collection.AddSettingsFlyoutInfo(typeof(PrivacyFlyout), "PrivacyFlyoutID", "Privacy", 500);
    SettingsFlyoutAttachedProperty.SetValue(this, collection);
}

 

Windows 8 Settings Charm Page Example – Try it yourself:

This example is available here via GitHub.

Now go and create your own setting charm pages.

The following two tabs change content below.