Andrej Tozon's blog

In the Attic

NAVIGATION - SEARCH

Reactive.buffering.from event.

Reactive?

This post is continuing the series about Reactive Extensions, a powerful little framework currently available for .NET 3.5 & 4, Silverlight 3/4, Windows Phone 7 and JavaScript,  that Microsoft’s Dev Labs are working on.

Buffering?

In this post, I’ll look into two reactive operators that will help me shape my Windows Phone 7 app into something that could eventually be called a  game, even. Those operators are:

BufferWithTime. BufferWithTime() will buffer values, produced in a specified time interval, to publish them all at once when that interval ends. Take, for example, a logger service, where you don’t need to log all events at once; instead, you would check your log queue every minute or so and log all queued events in a batch.

BufferWithCount. BufferWithCount() does a similar thing, but instead of a Timespan value, it takes an integer that specifies the number of values to buffer, before publishing all buffered values.

From event?

We know the FromEvent() construction operator already – it-s used to observe the sequence of specified events, the observation starting upon subscribing to that sequence.

So?

Right… Ok, let’s take the code from the previous post and build on that. Where I left it off was having an ItemsControl with nine tiles in it. What I wanted to do next was make those tiles react to user mouse clicks…

So let’s go and do that, taking small steps.

The source code for this blog post is available from here.

To be able to subscribe to tiles’ mouse events, we first need to find the visual elements representing them. This may seem a difficult task at first, because ItemsControl’s Items property holds a collection of data items (in this case, those are letters), not their visual representations. It’s not, really. Actually, this is a Linq query that will project the collection of letters (data) to a collection of tiles (visuals) through the item container:

IEnumerable<DependencyObject> c = from i in list.Items
                                  select list.ItemContainerGenerator.ContainerFromItem(i);

We’re going to observe these tiles so we’ll turn them to an observable collection:

IObservable<DependencyObject> oc = c.ToObservable();

Now, here’s the trick. We’re going to use a single Linq expression that will notify the observer whenever a tile (any tile!) has been clicked with a mouse, handing it a reference to that tile:

IObservable<Control> ee = from i in oc
    from e in Observable.FromEvent<MouseButtonEventArgs>(i, "MouseLeftButtonDown")
    select VisualTreeHelper.GetParent(((Grid)e.EventArgs.OriginalSource).Parent) as Control;

The expression is three part:

  1. Take all tiles and
  2. subscribe to their MouseLeftButtonDown event and
  3. when that event happens, return a reference to the tile that raised that event (using VisualTreeHelper to get tile’s parent element, as for the item's data template shown below).

Subscribing to this observable sequence of tiles will enable us to react to tile MouseLeftButtonDown events:

ee.Subscribe(Reveal);

In reaction to the mouse down event, we’ll make the tile go into a “Selected” state:

private static void Reveal(Control control)
{
    VisualStateManager.GoToState(control, "Selected", true);
}

But for this, we need to adjust the tile’s data template first. This is a simple “two-sided element” template that flips slides when going between Selected and Unselected sides. Visual states are used to represent the state on each of the side and transitions between those states. I also added the third state – the Matched state will make the matched tiles disappear:

<DataTemplate x:Key="TileTemplate">
    <ContentControl>
        <ContentControl.Template>
            <ControlTemplate>
                <Grid x:Name="TileRoot"
                      RenderTransformOrigin="0.5,0.5"
                      Opacity="0"
                      Width="142"
                      Height="142"
                      Margin="5">
                    <Grid.Projection>
                        <PlaneProjection CenterOfRotationX="0" 
                                         RotationY="-90" />
                    </Grid.Projection>
                    <Grid.RenderTransform>
                        <ScaleTransform />
                    </Grid.RenderTransform>
                    <i:Interaction.Behaviors>
                        <ib:LoadedBehavior />
                    </i:Interaction.Behaviors>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="SelectionStates">
                            <VisualState x:Name="Unselected">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)"
                                                                   Storyboard.TargetName="front">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="180" />
                                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="0" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)"
                                                                   Storyboard.TargetName="back">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="180" />
                                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="0" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Canvas.ZIndex)"
                                                                   Storyboard.TargetName="back">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <System:Int32>1</System:Int32>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0.5">
                                            <DiscreteObjectKeyFrame.Value>
                                                <System:Int32>0</System:Int32>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                        <DiscreteObjectKeyFrame KeyTime="0:0:1">
                                            <DiscreteObjectKeyFrame.Value>
                                                <System:Int32>0</System:Int32>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Selected">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)"
                                                                   Storyboard.TargetName="front">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="180" />
                                    </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)"
                                                                   Storyboard.TargetName="back">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="180" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Canvas.ZIndex)"
                                                                   Storyboard.TargetName="back">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <System:Int32>0</System:Int32>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0.5">
                                            <DiscreteObjectKeyFrame.Value>
                                                <System:Int32>1</System:Int32>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                        <DiscreteObjectKeyFrame KeyTime="0:0:1">
                                            <DiscreteObjectKeyFrame.Value>
                                                <System:Int32>1</System:Int32>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Matched">
                                <Storyboard>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="UIElement.Opacity"
                                                                   Storyboard.TargetName="TileRoot">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="1" />
                                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="0" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="UIElement.Opacity"
                                                                   Storyboard.TargetName="front">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="0" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)"
                                                                   Storyboard.TargetName="TileRoot">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="1" />
                                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="1.5" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
                                                                   Storyboard.TargetName="TileRoot">
                                        <EasingDoubleKeyFrame KeyTime="0" Value="1" />
                                        <EasingDoubleKeyFrame KeyTime="0:0:1" Value="1.5" />
                                    </DoubleAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Grid x:Name="back"
                          HorizontalAlignment="Left"
                          Height="142"
                          Margin="0"
                          VerticalAlignment="Bottom"
                          Width="142"
                          Background="{StaticResource PhoneForegroundBrush}">
                        <Grid.Projection>
                            <PlaneProjection RotationY="180" />
                        </Grid.Projection>
                        <TextBlock Text="{Binding}"
                                   FontSize="{StaticResource PhoneFontSizeExtraExtraLarge}"
                                   Foreground="{StaticResource PhoneAccentBrush}"
                                   VerticalAlignment="Bottom"
                                   HorizontalAlignment="Left"
                                   IsHitTestVisible="False"
                                   Margin="20,0,0,10" />
                    </Grid>
                    <Grid x:Name="front"
                          HorizontalAlignment="Left"
                          Margin="0"
                          VerticalAlignment="Bottom"
                          Background="{StaticResource PhoneAccentBrush}"
                          Width="142"
                          Height="142">
                        <Grid.Projection>
                            <PlaneProjection />
                        </Grid.Projection>
                        <TextBlock Text="{Binding}"
                                   FontSize="{StaticResource PhoneFontSizeExtraExtraLarge}"
                                   Foreground="{StaticResource PhoneForegroundBrush}"
                                   VerticalAlignment="Bottom"
                                   IsHitTestVisible="False"
                                   HorizontalAlignment="Left"
                                   Margin="20,0,0,10" />
                    </Grid>
                </Grid>
            </ControlTemplate>
        </ContentControl.Template>
    </ContentControl>
</DataTemplate>

The game

Great, now we’ve got ourselves a very simple game of revealing tiles. Sounds familiar? Yes, it looks a lot like a Memory  game.

In a Memory game, we’re dealing with pairs, right? Selecting two of the tiles makes up for a single turn in the game. If we take the existing observable collection and  additionally make it buffer to two elements, the observer will only going get notified when the user selects two tiles. But we also need to let the revealing animation to finish (lasts 1 second), so we have to delay the event publication for one second.

var ff = ee.BufferWithCount(2).Delay(TimeSpan.FromSeconds(1));

The OnNext handler will get both data items associated with the tiles, we just need to subscribe:

ff.ObserveOnDispatcher().Subscribe(OnPairRevealed);

The handler’s implementation at this point is this:

private void OnPairRevealed(IList<Control> tiles)
{
    string state = (char) tiles[0].DataContext == (char) tiles[1].DataContext ? "Matched" : "Unselected";
    VisualStateManager.GoToState(tiles[0], state, true);
    VisualStateManager.GoToState(tiles[1], state, true);
}

Matched and Unselected states are the only valid states to transition from the Selected state. Target state is determined by comparing letters on the tiles – if both letters are the same, it’s a match!

Making it work

There’s a tiny small thing left to make this all work. The previous version of this app was showing the initial tile animation only, and now we need to wire in the logic described in this post. Nothing easier – with existing animation sequence subscription, we only need to call the function when the initial sequence ends. So this:

.Subscribe(AddLetter);

becomes this:

.Subscribe(AddLetter, ObserveClicks);

Video and source code

See the application in action…

… and download source code.

Finishing

Reactive Extensions sure are a fun way to program – take a quick run through the source code to see how little code was required to get things moving as shown in the video.

In future posts I’ll shape up this app into more functional Memory game. Possibly end up wanting publishing it on the marketplace…

Oh, and Happy New Year everybody!



My Month with Windows Phone 7

I had a pleasure of using a Windows Phone 7 device (LG Optimus 7 to be exact) for the past month and this is me sharing my experience with it. A quick note on my phone background: I’ve been using an iPhone for the past two years and am used to my wife’s HTC Hero (Android).

The thing is – I had to switch back to the iPhone after I returned my WP7 today (had it on a one month test drive) and it felt - weird. Let me just say that switching from the iPhone to WP7 and back felt a lot like switching from ZX Spectrum to a PC and back to the ZX (a 20-year old metaphor but who cares Smile). Something along the way of comparing serious, monochrome OS to a colorful, toyish one, if you will… Which OS suits you better remains a matter of personal taste, but to answer the question I got on one of my recent WP7 developer talks, I definitely don’t think that younger population won’t be attracted to Windows Phone 7; there are a lot of features that young audience will very much appreciate.

First, there’s the people hub – to track and manage everything and everybody from a single place is just awesome. Android is offering a similar story, but IMHO it’s not that well executed as it’s on WP7. My only wish here is that Microsoft would allow for 3rd party apps to easily integrate into the hub. And on the subject, twitter not being integrated into the hub is a huge miss, for example.

I also definitely love the Metro UI – simple, quickly recognizable graphics and large fonts. Easy for brains to process while on the move. There are, a couple of quirks I ran into… you can only add tiles on the single main screen (vertically, from the top down), and the main screen can hold up to 8 tiles. Imagine a live tile you put in the 7th row of the main screen. The gesture for unlocking the phone would be flick up, flick up. Except when you flick up the second time you can’t be sure where you’ll land - it depends on how hard you flicked. When in a hurry or on the go, that tile hunting game can be pretty time-consuming. And this is the phone that is supposed to save us from our phones, right? Either making flick up / down gestures to move a whole page up or down, or the ability to put tiles on the secondary screen on the left of the main screen would make more sense because your flick up, flick right gesture would land you exactly where you expected.
One additional thing that annoyed me was having to do two gestures to answer the call (unlock, answer) – why, oh why? Phone calls are the reason they are called phones – they should require the least amount of thinking and physical activities with this devices.
And, and I don’t know if this is related to WP7 OS or physical device - when answering a call, there was a certain lag involved before the microphone got switched on. That resulted in many confusing calls when I answered with ‘hello’, which the person on the other side didn’t hear, and waited for me to say something.

Build quality – I don’t know, the phone felt plasticky to me. I was especially worried I would accidentally pull out the Start button because of it’s embossed logo on it. And just to be clear – putting USB port on the side? That’s the worst place to put it. Cradle, anyone? Also, the photo button would be one of the greatest features on the phone if I wasn’t accidentally repeatedly hitting it when holding the phone in my left hand. It responds immediately, making your phone ready to take a photo in a matter of a second. But because of its position, it’s either a blessing, or a curse.

Ringtones – well, I tried to find a ring tone on my Optimus 7 but I couldn’t find one. The wide variety of beepy and clicky tones didn’t do much for me; I need noise to tell me it’s my phone ringing. Old phone, please!

Applications – tried them a few and they worked great… except for some with long(er), virtualized lists… jerky as hell. Hardly usable. Hard to keep up reading a list when every second or so you completely lose your track. Marketplace hung a couple of times. People’s hub crashed a couple of times, esp. when deleting a contact, imported from the SIM card. I was really impressed with the shipped IE browser. Very responsive and smooth. The whole being online experience is totally abstracted away from the phone, it feels really great.

A couple of other quirks: When trying to make a phone call, the contact info always tended to show in a landscape mode. I realized that I needed to hold my phone in a straight vertical position in order to be displayed correctly. The typing keyboard didn’t exactly liked my fingers’ typing, I was missing the keys constantly – wish there was a way to calibrate the keyboard.

In conclusion: yes, the OS has issues, some of them quite serious. From a casual user’s standpoint, I’d say the most annoying is the one with microphone lag and I hope this  will get fixed somehow, either by Microsoft or the device manufacturers. On the positive side, the phone is very easy to use and offers a great experience.
I have to say I got pretty used to it in the past month and I’m definitely considering buying my own WP7 in the near future. If I was living in a country, supported by the WP7 marketplace and the App hub, that would be a non-brainer.



Slides: Application Development for Windows Phone 7

I had the pleasure of participating in the local Microsoft roadshow that was going on in the past month.

4 talks. 4 cities. 1 subject: Application Development for Windows Phone 7.

I said I’d publish the slides. Here.

Also, from last I’ve heard, the situation for WP7 developers in Slovenia is improving. More details soon.



(Kind of) transparent Lock Screen for Windows Phone 7

Well, I thought it would be neat to have a transparent screen on my WP7 phone so I went and created a fully transparent 480x800 image and saved it as a PNG. Of course Zune software didn’t allow me to sync it with the phone (doesn’t support PNGs) so I renamed the image to JPG, which synced perfectly (shown as black image in the photo list).

I set the image as my lock screen image.

I now have a black lock screen on my phone, but whenever I touch the screen, the black fades away and reveals what’s beneath the lock screen (that’s when the lock screen is supposed to slide up a bit to hint the gesture to unlock).

Furthermore, it appears that this tap that hints the gesture also causes the reactivation of the app that was running (and deactivated) when the phone got locked. You can play a little game too… if you tap your finger fast enough to not let the lock screen fully reappear again, you can cause the app to fully reactivate (and continue to run)! I’ve got plenty of practice with this from my ZX Spectrum days so I managed to fully reactivate the official twitter app to load and refresh my timeline :) So what’s going on is the device is launching (and obviously terminating) the deactivated app each time you touch the lock screen?

I also noticed that sometimes the application bar from the deactivated app is displayed on my “transparent” locked screen even without touching it… I couldn’t figure out when this happens though.

Anyway, I’m enclosing my test transparent image here in case you’d want to give it a try.

phone



Reactive Extensions #3: Windows Phone 7

Following the theme from my previous two posts, this post will be about using Reactive Extensions on Windows Phone 7. I'll use a similar scenario as before – gradually load a few tiles into an ItemsControl. Let’s get started.

Starting a project

Create a new “Windows Phone Application”. Add references to assemblies Microsoft.Phone.Reactive and System.Observable to add support for Rx, then Microsoft.Phone.Controls.Toolkit (found in Silverlight Toolkit for Windows Phone 7) and System.Windows.Interactivity (Expression Blend for Windows Phone 7 SDK, should be already installed if you installed WP7 tools / Expresion Blend 4 SP1).

Layout

Put a ListBox on the main, name it list and possibly change the control to an ItemsControl (we don’t need to select items).

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <ItemsControl Name="list" />
</Grid>

Create the ItemTemplate:

<DataTemplate x:Key="TileTemplate">
    <Grid Width="142"
          Height="142"
          Background="{StaticResource PhoneAccentBrush}"
          Margin="5">
        <TextBlock Text="{Binding}"
                   FontSize="{StaticResource PhoneFontSizeExtraExtraLarge}"
                   Foreground="{StaticResource PhoneForegroundBrush}"
                   VerticalAlignment="Bottom"
                   HorizontalAlignment="Left"
                   Margin="20,0,0,10" />
    </Grid>
</DataTemplate>

… use WrapPanel for the ItemsPanel:

<ItemsPanelTemplate x:Key="WrapItemsPanelTemplate">
    <toolkit:WrapPanel />
</ItemsPanelTemplate>

… and update the ItemsControl to use them:

<ItemsControl Name="list" 
              ItemTemplate="{StaticResource TileTemplate}"
              ItemsPanel="{StaticResource WrapItemsPanelTemplate}" />

Reactive Extensions

I copied the code from my previous post:

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();

        Loaded += OnLoaded;
    }

    private readonly string text = "reactive wp7";

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        text.ToObservable()
            .OnTimeline(TimeSpan.FromSeconds(.3))
            .ObserveOnDispatcher()
            .Subscribe(AddLetter);
    }

    private void AddLetter(char letter)
    {
        list.Items.Add(letter);
    }

Animating on appearance

Instead of using layout states as in Silverlight version (they are not supported in WP7’s ListBox), I was back on using a behavior to trigger the entrance animation:

public class LoadedBehavior : Behavior<FrameworkElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Loaded += AssociatedObject_Loaded;
    }

    void AssociatedObject_Loaded(object sender, System.Windows.RoutedEventArgs e)
    {
        CreateStoryboard().Begin();
    }

    private Storyboard CreateStoryboard()
    {
        Storyboard sb = new Storyboard();
        DoubleAnimation animation = new DoubleAnimation
        {
            Duration = TimeSpan.FromMilliseconds(1000),
            From = -90,
            To = 0,
            EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
        };
        Storyboard.SetTargetProperty(animation, new PropertyPath("(UIElement.Projection).(PlaneProjection.RotationY)"));

        sb.Children.Add(animation);

        animation = new DoubleAnimation
        {
            Duration = TimeSpan.FromMilliseconds(1000),
            From = 0,
            To = 1,
            EasingFunction =
                new CubicEase() { EasingMode = EasingMode.EaseOut }
        };
        Storyboard.SetTargetProperty(animation, new PropertyPath(UIElement.OpacityProperty));

        sb.Children.Add(animation);
        Storyboard.SetTarget(sb, AssociatedObject);
        return sb;
    }
}

Wrapping up

The last thing to do was adding the behavior to the ItemTemplate and prettify the layout a bit.

That’s it, pretty easy – tiled letters are now gradually filling up the space on the main page, each letter appearing on the screen with a subtle animation. For more details on how that works check out my previous posts on Reactive Extensions.

The demo project can be downloaded from here. In the future I’ll look into using more Reactive Extensions on Windows Phone 7 – stay tuned.

image