blog.reis.se

It's all about looks

officeAs you might have seen in the Windows Phone 7 Office application there are small round buttons in the panorama. As you should never use the application bar in the panorama it’s a good way to get some navigation points into the panorama. There are a few versions of this button out, ranging from styling a button and up to complete controls. However the ones I have found have things I don’t like, mostly they work with black and white images or with paths for the icon.

So I started out with a few basic specifications, I wanted:

  • A simple control to use, no special content
  • Only mage for the button (white with alpha channel)
  • To be able to easily add content next to the button
  • Ability to re-skin the button

The goal was to be able to define a button with just a simple:

<jc:RoundButton Image="/icons/refresh.png" Content="refresh" />

For those that don’t want to find out about my journey towards the RoundButton control there is a zip with the code and a sample project at the very bottom of the post…

When I first started out with the basic design the approach was the same as for others out there, two images that was swapped on light theme and shifted on pressed state. But I didn’t like it, I wanted the control to handle the image inversion stuff so my next thought was to create a new image with Writable bitmap and do some pixel magic, but before I got that far I stumbled on a blog post on displaying an image in the accent color. This was brilliant, using the image as a opacity mask, I could have whatever color I liked. So the core of the control got to be something like this:

<Rectangle x:Name="ImageForeground" Height="48" Width="48"
  Fill="with_whatever_color_or_template_color">
  <Rectangle.OpacityMask>
    <ImageBrush ImageSource="image.png" Stretch="Fill" />
  </Rectangle.OpacityMask>
</Rectangle>

The code

On to the control design, first of was to create the code behind for the button, creating the attached properties. This code ended up looking like this:

public class RoundButton : Button
{
    private object _layoutRoot; 
    public static readonly DependencyProperty ImageProperty  = DependencyProperty.Register("Image", typeof(ImageSource), typeof(RoundButton), null); 

    public RoundButton() : base()
    {
        DefaultStyleKey = typeof(RoundButton);
    }
    

    [Description("The image displayed by the button in dark theme (and in normal mode)"), Category("Common Properties")]
    public ImageSource Image
    {
        get { return (ImageSource)GetValue(ImageProperty); }
        set { SetValue(ImageProperty, value); }
     }

    public override void OnApplyTemplate()
    {
        _layoutRoot = GetTemplateChild( "LayoutRoot" ) as Border;
        Debug.Assert( _layoutRoot != null, "LayoutRoot is null" );
        base.OnApplyTemplate();
    }
}

Hurdles and solutions

So the template stuff should be easy now right? Just add the Image to ImageSource for the Brush like this:

<ImageBrush ImageSource="{TemplateBinding Image}" Stretch="Fill" />


Wrong! Turns out you can’t bind to an ImageSource in an ImageBrush, because ImageBrush don’t inherit from FrameworkElement, as BMiloshevska pointed out. Argh, so close. Back to the drawing board until Derek Lakin pointed me at an article about using a converter as a workaround. Here is the converter stuff:

public class ImageBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        BitmapImage image = (BitmapImage)value;
        ImageBrush imageBrush = new ImageBrush();
        if (image != null)
        {
            imageBrush.ImageSource = image;
        }
        return imageBrush;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}


Getting closer, but still there are a few hurdles left as you will see. Now it seems you can bind the ImageBrush to the DependecyProperty with a Converter, like this:

<ImageBrush ImageSource="{TemplateBinding Image, Converter={StaticResource brushConverter}}" Stretch="Fill" />


Wrong again! You can’t use a converter on a TemplateBinding, sigh. Then a new angle: The DataContext. You can TemplateBind the image to the DataContext and then use a normal binding (with converter) for the brush. Success! The binding ended up looking like this:

<Rectangle x:Name="ImageForeground" Height="48" Width="48"
    Fill="{TemplateBinding Foreground}"
    DataContext="{TemplateBinding Image}"
    OpacityMask="{Binding Converter={StaticResource brushConverter}}" />

The template

So now it was the last part left, creating the template style with states for normal, pressed and disabled. The last one is a bonus, with the image approach you would now need three images, white, black and grey, no you can use whatever you like. The template was placed in Themes/generic.xaml and looks in part like this (the full xaml is included in the zip):

<Style TargetType="local:RoundButton">

  <!-- Basic template setters -->

  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="local:RoundButton">
        <Border x:Name="LayoutRoot" Background="Transparent" Padding="12" >
          <VisualStateManager.VisualStateGroups>

          <!-- The states Normal/Pressed/DIsabled/... -->

          </VisualStateManager.VisualStateGroups>

          <!-- The actual button template where the image and content goes -->
          <Grid>
            <StackPanel Orientation="Horizontal">
              <Border Height="48" Width="48" x:Name="ButtonBackground" BorderThickness="3" CornerRadius="24" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" >
                <Border.Resources>
                  <local:ImageBrushConverter x:Key="brushConverter"/>
                </Border.Resources>
                <Rectangle x:Name="ImageForeground" Height="48" Width="48" HorizontalAlignment="Center" VerticalAlignment="Center" Fill="{TemplateBinding Foreground}"
                    DataContext="{TemplateBinding Image}"
                    OpacityMask="{Binding Converter={StaticResource brushConverter}}" />
              </Border>
              <ContentControl VerticalAlignment="Center" Margin="0" HorizontalAlignment="Left" Padding="8,0,0,0" Content="{TemplateBinding Content}" />
            </StackPanel>
          </Grid>
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

There, finally I reached my goal!

Improvements

So am I done now? Yes and no. The functionality is in place and the first four goals reached but there is more stuff I like to do:

  • I want to add some optional animation, for example the push behavior from the AppBar and the tilt effect (actually you can use the tilt effect that Peter Torr blogged about, as the control is based on a button)
  • I think there is a workaround to the Converter, I would like to eliminate that part.
  • Also create a round toggle button with two images (for example play/pause media button)

If anyone happen to have a solution or idea around those issues I would be happy to know.


The zip includes the control and a test project to try it out.


A couple of weeks ago I was working on a problem with handing audio over multiple pages in an application. It worked out well until the updated toolkit that came out in the beginning of November. I tried to add page transitions and after a day of frustration I found that my MediaElement injection broke the ability for page transitions.

The last solution was based on creating a ControlTemplate with a MediaElement in and ad that as the Template for the root frame, this breaks the ability for page transitions. As soon as I identified the problem I started to look for a solution. The solution was even simpler than I first anticipated. Instead of the injection I just added the MediaElemet as a resource in the App.xaml and then exposing it as a property. The code looks like this for the xaml part:


    

The code behind part is even simpler, it looks like this:

public static MediaElement GlobalMediaElement
{
  get { return Current.Resources["GlobalMedia"] as MediaElement; }
}

And you access it as before by calling it up in the page you need it on

var AppMediaElement = App.GlobalMediaElement;

There, simpler, cleaner and works as good as the first solution, and now I finally can start to use the page transitions again, sweet.

Update: As the MediaElement is very expensive it should be used with caution and here I will instantiate it at App level. In my case it is a player that Will play music and stream from the net so the purpose is to have it playing as long as I have music playing. However if you are not using it continuously you should not have a MediaElement like this. 

Update 2: I included a little testproject with code samples for this as well, that shows it works for both streams and included audio and should work for IsoStoraged files.

/ Håkan Reis


Building an audio application this weekend I ran into a problem; where do you place the media element when you have more than one page, and you want the audio to continue playing when you navigate between pages. I will get to the solution later on in the post but first some background.

During the weekend our .NET team spent two days with Windows Phone 7. A few weeks ago we brainstormed around a few application ideas. The ultimate goal from the weekend was to se how far we could get in about a day of coding. To get us all kick started we were happy to have Dag König with us to talk about windows phone 7 development and support us during the weekend with ideas and hardware.

We split into several groups and my group focused on an audio application with streaming media, based on listings from an XML source. The available streams were presented in a list and selecting one navigated us to another page where the player was located. Of course there was a lot of different problems solved during this session but one of them was as follows:

We wanted the stream to start playing when we navigated to the Player Page and continue to play in the background when we returned to the stream list in the Panorama Page. It shouldn’t stop until we directly stopped it or a new stream was selected.

After some thinking it was obvious that we needed place that’s globally accessible for the media element responsible for the sound. One such place is the application level, and it turned out to be the way to solve this. But you can’t just drop element in the xaml and be done, that would be too simple. However, the solution wasn’t that far away. I placed it in the common resources instead and used a property to expose it globally for the pages. So the first code part is from the app.xaml file:


  ...
  
    
    
      
      
      
      
      
        
      
    
  

And in the code behind you find the properties parts to make it accessible from the pages in the application (through App.GlobalMediaElement). The property is set the first time the media element is loaded (via the Loaded event) and then it’s just returning the original media element on reoccurring calls:

private static MediaElement _globalAudioElement = null;

public static MediaElement GlobalAudioElement 
{
  get { return _globalAudioElement;  }
}
        
private void OnMediaLoaded(object sender, RoutedEventArgs e)
{
  if (_globalAudioElement == null)
    _globalAudioElement = sender as MediaElement;
}

Finally in the end of app.xaml.cs there is the initialization section and here I injected the global media element in the system with the template from the resource:

private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
  if (RootVisual != RootFrame)
    RootVisual = RootFrame;
  
  RootFrame.Navigated -= CompleteInitializePhoneApplication;
  
  // Add this to inject the media element Control template          
  RootFrame.Template = App.Current.Resources["AudioContentTemplate"] as ControlTemplate;
}

And now you can access the MediaElement form any page and do your stuff almost as if it was added the the page itself with:

App.GlobalAudioElement.Source = streamUri;
App.GlobalAudioElement.Play();

That’s all there is to it, hope it helps someone out with the same problem we had. There is actually another solution but that one includes a bit of XNA coding, for this scenario it was enough with the above solution but if you need a more detailed and advanced control over the audio in your application I recommend looking into XNA.

// Håkan Reis


JayPokerI was trying out a few things on the windows phone 7 platform and wanted to come up with a simple application idea. There are numerous of simple list application examples out there like twitter and RSS readers but I wanted to try something different, something both useful and a bit more playful. And with a limited time.

So why not a planning poker application? Most of the graphics was already there like the coffee cup, infinity mark and the logo so it was simple to whip it together. But I still think there are a few interesting parts in this.

As it’s static data, there are no more than the numbers, coffee cup, question mark and the infinity sign.  I didn’t bother with any complicated view model, just went along with static buttons. And one big button as the currently selected card. I re-styled the button to make it look like a card and added the graphics and text. The big card is normally hidden but when I select a card it is populated with the selected cards content and the popped up.

The trick here is that if the selected card is an Image the I create a new image and assign it to the content and if its text I assign that directly to the content.

if (_currentCard.Content is Image)
  BigButton.Content = new Image
    {
      Source = ((Image)_currentCard.Content).Source, 
      Height = 234, 
      Width = 202
    };
else
  BigButton.Content = _currentCard.Content;

Next up is the animation. I wanted the card to flip over while I tuned up the dark overlay. The basic animation was easy to set, I just used a combination of scale and plane transforms to get the big card from the small size and up to the full screen view with a flip. It was a bit trickier to get the card to start and end the animation on the right spot. A little calculation together with naming of the places where I should modify the animation, with  an “x:Name” property, did the trick. Now I was able to modify it from code using the following to set the position:

int xValue = -180 + 120 * 
    int.Parse(_currentCard.GetValue(Grid.ColumnProperty)
     .ToString());
int yValue = -260 + 190 * 
    (int.Parse(_currentCard.GetValue(Grid.RowProperty)
     .ToString()) - 1);

((EasingDoubleKeyFrame)FindName("startX")).Value = xValue;
((SplineDoubleKeyFrame)FindName("endX")).Value = xValue;
((EasingDoubleKeyFrame)FindName("startY")).Value = yValue;
((SplineDoubleKeyFrame)FindName("endY")).Value = yValue;

It's not the most elegant code, with no safeguards when I cast between stuff, but I whipped it together during an afternoon and it was rewarding to see what could be accomplished with some 50 lines of code and a little blend. And who knows I might clean it up and publish it as soon as the devices are out and the market place is up.

Time to plan the next sprint // Håkan Reis