Semantic zoom – beyond the stock behaviour – #windev #winrt

Last year I was working on Cineworld app for Windows 8. Having use LongListSelector I wanted to ensure that I can transfer the look and feel. However I was fighting multiple battles – the first one is that while LongListSelector is a single control, SemanticZoom control itself contains ZoomInView and ZoomedOutView. These are independent of each other and most examples then to show settings data to zoomed in view and then zoomed out view referring to zoomed in view. It gets messy – maybe I am just slow but it was not intuitive and took me ages.

While mucking around, I came across this post and from there, kept trying till I got what I wanted.

LLS1LLS 

Screenshot (220)

Screenshot (221)

 

<Page.Resources>
    <CollectionViewSource x:Name="cvsFilms" IsSourceGrouped="true" />

    <!-- Zoomed out templates and selectors -->
    <DataTemplate x:Key="GroupTemplate">
        <Border Margin="6" Width="75" Height="75" Background="Black" >
            <TextBlock Text="{Binding Name}" Foreground="White" FontSize="32" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Border>
    </DataTemplate>
    <DataTemplate x:Key="EmptyGroupTemplate">
        <Border Margin="6" Width="75" Height="75" BorderBrush="Black" BorderThickness="1" Background="LightGray">
            <TextBlock Text="{Binding Name}" Foreground="Black" FontSize="32" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Border>
    </DataTemplate>
    <local:EmptyOrFullSelector
            x:Key="FilmGroupEmptyOrFullSelector"
            Empty="{StaticResource EmptyGroupTemplate}"
            Full="{StaticResource GroupTemplate}" />
    <DataTemplate x:Key="GroupHeaderTemplate">
        <Border Margin="20, 20, 0, 0" Width="75" Height="75" Background="Black" Tapped="Grid_Tapped">
            <TextBlock Text="{Binding Key}" Foreground="White" FontSize="32" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Border>
    </DataTemplate>
    <DataTemplate x:Key="GroupItemTemplate">
        <Border Margin="14, 20, 0, 0">
            <Grid Height="350" Width="185" Margin="0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <Image  Source="{Binding PosterUrl}" Height="278" Width="185" VerticalAlignment="Top" HorizontalAlignment="Left" Stretch="UniformToFill"/>
                <TextBlock Grid.Row="1" TextWrapping="Wrap" VerticalAlignment="Top" Text="{Binding Title}" HorizontalAlignment="Center" FontFamily="Segoe UI" />
            </Grid>
        </Border>
    </DataTemplate>
</Page.Resources>

<SemanticZoom x:Name="semanticZoom" Background="White" HorizontalAlignment="Stretch" Grid.Row="1" VerticalAlignment="Stretch">
    <SemanticZoom.ZoomedOutView>
        <GridView ScrollViewer.IsHorizontalScrollChainingEnabled="False" HorizontalAlignment="Center" ItemTemplateSelector="{StaticResource FilmGroupEmptyOrFullSelector}" >
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapGrid Background="LightGray" MaximumRowsOrColumns="9" VerticalChildrenAlignment="Center" Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>
        </GridView>
    </SemanticZoom.ZoomedOutView>
    <SemanticZoom.ZoomedInView>
        <GridView x:Name="gvZoomedInFilms" ItemsSource="{Binding Source={StaticResource cvsFilms}}" IsSwipeEnabled="True" ScrollViewer.IsHorizontalScrollChainingEnabled="False" ItemTemplate="{StaticResource GroupItemTemplate}">
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>
            <GridView.GroupStyle>
                <GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}">
                    <GroupStyle.Panel>
                        <ItemsPanelTemplate>
                            <VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,50,0"/>
                        </ItemsPanelTemplate>
                    </GroupStyle.Panel>
                </GroupStyle>
            </GridView.GroupStyle>
        </GridView>
    </SemanticZoom.ZoomedInView>
</SemanticZoom>
List<GroupInfoList<object>> dataLetter = null;

public ListFilms()
{
    this.InitializeComponent();

    FilmData cd = new FilmData(App.Films);

    dataLetter = cd.GroupsByLetter; 
    cvsFilms.Source = dataLetter;
    gvZoomedInFilms.SelectionChanged -= gvZoomedIn_SelectionChanged;
    gvZoomedInFilms.SelectedItem = null;
    (semanticZoom.ZoomedOutView as ListViewBase).ItemsSource = cd.FilmHeaders;     gvZoomedInFilms.SelectionChanged += gvZoomedIn_SelectionChanged;

    semanticZoom.ViewChangeStarted -= semanticZoom_ViewChangeStarted;
    semanticZoom.ViewChangeStarted += semanticZoom_ViewChangeStarted;
}

void semanticZoom_ViewChangeStarted(object sender, SemanticZoomViewChangedEventArgs e)
{
    if (e.SourceItem == null)
        return;

    if (e.SourceItem.Item.GetType() == typeof(HeaderItem))
    {
        HeaderItem hi = (HeaderItem)e.SourceItem.Item;

        var group = dataLetter.Find(d => ((char)d.Key) == hi.Name);
        if (group != null)
            e.DestinationItem = new SemanticZoomLocation() { Item = group };
    }
}

// FilmData looks like this
private List<GroupInfoList<object>> groupsByLetter = null;

public List<GroupInfoList<object>> GroupsByLetter
{
    get
    {
        if (groupsByLetter == null)
        {
            groupsByLetter = new List<GroupInfoList<object>>();

            var query = from item in Collection
                        orderby ((FilmInfo)item).Title
                        group item by ((FilmInfo)item).HeaderChar into g
                        select new { GroupName = g.Key, Items = g };

            foreach (var g in query)
            {
                GroupInfoList<object> info = new GroupInfoList<object>();
                info.Key = g.GroupName;
                foreach (var item in g.Items)
                {
                    info.Add(item);
                }
                groupsByLetter.Add(info);
            }
        }

        return groupsByLetter;
    }
}

List<HeaderItem> filmHeaders = null;
public List<HeaderItem> FilmHeaders
{
    get
    {
        if (filmHeaders == null)
        {
            filmHeaders = new List<HeaderItem>();

            char c = '#';

            filmHeaders.Add(new HeaderItem() { Name = '#', IsEnabled = this.GroupsByLetter.Exists(k => ((char)k.Key) == c) });
                    
            for (int i = 65; i <= 90; i++)
            {
                c = (char)i;

                if (this.GroupsByLetter.Exists(k => ((char)k.Key) == c))
                    filmHeaders.Add(new HeaderItem() { Name = c, IsEnabled = true });
                else
                    filmHeaders.Add(new HeaderItem() { Name = c, IsEnabled = false });
            }                    
         }

        return filmHeaders;
    }
}

I think that’s about it. Its been 6 months now and I think I am going to change how it looks :) some stuff will remain as it is, others will have to change

FlipTile, CycleTile and various iterations of #wpdev sdks

As you might have realised, the last few weeks, cineworld app has been my obsession. I started work on WP8 port and then back-ported some changes to WP7 version of the app (its the only one that’s live at the moment so makes sense). So what did I do so far on it (WP7 version) ?

1) Moved to using 50% space when displaying film posters, moved film little on top of the posters, removed the button style that caused me many xaml exceptions on WP8
2) Moved to using good quality images only for ImageTile control.
3) Added background task to download data files and to set Tile images

This is live as we speak and a few people emailed me saying it looks great. Now I am not going to create yet another background task for WP8 version (since I wanted to use Cycle Tile Template for WP8 version of the app.
Microsoft’s own tile related pages show how to support newer tiles for WP7.8 and WP8 even though the app itself is built on WP 7.1 (Mango) sdk

So far I stayed away from temptation of managing image repository on the device. you can see that it gets messy eventually and you have to clean up and what not.. however after implementing basic solution, it started throwing exceptions.. files used for tiles cannot be remote. Bugger.. more work

Step 1): Download image files locally. I have been using AsyncWebClient (Custom wrapper with TaskCompletionSource) exposing awaitable DownloadFile method. Plug that in and download all the files. Its background tasks, I am not really fussed about parallel execution.. just manageable execution (read sync like)

Step 2): For Uri array using relative path and ??? still nothing. More work, a quick search about the net found me a couple of posts for “live tile isolated storage image WP7″ File Path in Isolated File Storage is the one that I first opened and a quick scan reminded me of “isostore” based Uri

Step 3): Save all the images to Shell\SharedContent and switch all Uri to use isotore:/Shell/SharedContent/Filename.jpg

The code below will help you create live tiles with new tile templates from WP7.1 (Mango) sdk based project. I will be using the same code for WP8 in coming days since its the same background task

A word to the wise:
* You cannot determine if the template used by Tile is Standard or a new one.
* What I now do is Create a CycleTileData template when user wants to pin (if WP7.8 or WP8) else StandardTileData
* When iterating tiles, I try CycleTileData template and failing that try StandardTileData for update

Code snippets that might help:

Add AppExtra above App element in WMAppManifest.xml file (works for both WP7.8 and WP8)

<AppExtra xmlns="" AppPlatformVersion="8.0">
  <Extra Name="Tiles"/>
</AppExtra>
public async Task DownloadFileAsync(string url, string filename, string folder = null)
{
    // create a web client for downloading the string
    var wc = new WebClient();

    try
    {
        IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();

        string file = (folder == null ? filename : String.Format("{0}/{1}", folder, filename));
                
        using (Stream s = await wc.OpenReadTaskAsync(url))
        {
            using (Stream slocal = isf.OpenFile(file, FileMode.Create))
            {
                s.CopyTo(slocal);
            }
        }
    }
    catch (Exception ex)
    {
        if (1 == 1) // just to check exceptions during debugging. do whatever pleases you
        {
        }
    }
}

//works for both WP7.8 and WP8
private static Version TargetedVersion = new Version(7, 10, 8858);
public static bool IsTargetedVersion { get { return Environment.OSVersion.Version >= TargetedVersion; } }

private static void SetTileBackground(string[] filesToShow, string folder, Random random, ShellTile currentTile)
{
    bool tryFlip = false;

    if (Config.IsTargetedVersion)
    {
        try
        {
            Uri smallimage = new Uri("Images/CycleSmall.png", UriKind.Relative);
            Uri mediumimage = new Uri("Images/CycleMedium.png", UriKind.Relative);

            UpdateCycleTile(smallimage, mediumimage, folder, filesToShow, random, currentTile);
        }
        catch
        {
            tryFlip = true;
        }
    }
    else
        tryFlip = true;
            
    if(tryFlip)
    {
        int rand = random.Next(0, filesToShow.Length);

        StandardTileData NewTileData = new StandardTileData
        {
            BackBackgroundImage = new Uri(String.Format("isostore:{0}/{1}", folder, filesToShow[rand]), UriKind.Absolute)
        };

        currentTile.Update(NewTileData);
    }
}

public static void UpdateCycleTile(
    Uri smallBackgroundImage, Uri backgroundImage,
    string folder, string[] filesToShow, Random random, ShellTile currentTile)
{
    // Get the new cycleTileData type.
    Type cycleTileDataType = Type.GetType("Microsoft.Phone.Shell.CycleTileData, Microsoft.Phone");

    // Get the ShellTile type so we can call the new version of "Update" that takes the new Tile templates.
    Type shellTileType = Type.GetType("Microsoft.Phone.Shell.ShellTile, Microsoft.Phone");

    // Get the constructor for the new FlipTileData class and assign it to our variable to hold the Tile properties.
    var UpdateTileData = cycleTileDataType.GetConstructor(new Type[] { }).Invoke(null);

    // Set the properties. 
    SetProperty(UpdateTileData, "SmallBackgroundImage", smallBackgroundImage);

    Uri[] mediumImages = new Uri[9];

    mediumImages[0] = backgroundImage;
    for (int i = 1; i < 9; i++)
    {
        int rand = random.Next(filesToShow.Length);

        mediumImages[i] = new Uri(String.Format("isostore:{0}/{1}", folder, filesToShow[rand]), UriKind.Absolute);
    }

    SetProperty(UpdateTileData, "CycleImages", mediumImages);

    SetProperty(UpdateTileData, "Title", "my cineworld");
            
    // Invoke the new version of ShellTile.Update.
    shellTileType.GetMethod("Update").Invoke(currentTile, new Object[] { UpdateTileData });
}

private static void SetProperty(object instance, string name, object value)
{
    var setMethod = instance.GetType().GetProperty(name).GetSetMethod();
    setMethod.Invoke(instance, new object[] { value });
}

This by no means is fine / perfect code. Please treat it as a PoC and do whatever you do normally

Geolocator and MovementThreshold #wpdev #win8dev

Last few months, I have had too much on my plate. Been doing lot to Windows 8 and Phone development. Surprisingly Microsoft UK is keeping tabs on phone developers who aren’t adapting to #WP8 and seemingly, I was invited to a dev event targeting the likes of me :P

Sure I am going and in anticipation of the event, I decided to port Cineworld app to WP8. Its been a challenging task. Reworking a few bits of LongListSelector, Visual Studio / Blend disliking all the xaml and of course a button style causing xaml exception. If that wasn’t enough:

Having used Geolocator in Windows 8 app, I decided to use the same for WP8 as its usually a lot faster than GeoCoordinateWatcher. I moved the code to be more like Win8 and noticed that the location detection was flaky at best. Now I did the stupid thing and like in WP7 version, I decided to set both DesiredAccuracy to High and MovementThreshold to 0 thinking absolutely nothing.

Of course that didn’t help it, it made matters worse, instead of flaky behaviour, it decided to call it quits.

var pos = await locator.GetGeopositionAsync(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(2));

instead of timing out, it started to hang. In desperation, I started looking at other events exposed by this class and tried hooking into StatusChanged event. Now things were bad, I noticed that it was getting operation aborted exception and decided that something was seriously wrong and that I should take a nap instead :)

Of course the problem was still there when I woke up the next day :P its only later that I decided to search on Geolocation and operation aborted and realised that I wasn’t the only one and it was all my fault – causality of me setting the MovementThreshold to 0. I set it to 10 instead and bingo.. it all works.

While I didn’t find it, I hope this post helps people like an earlier post helped me.

MVVM and me – Part 2

I could have done this post yesterday but that would have been a long essay. Today we are going to look at displaying static data on XAML pages.

In C# when you have data that does not change, one tends to use const or static readonly. const causes compiler to replace all occurrences of variable usage with the actual data at compile time. That means you assemblies has no lookups at run-time. Static read-only is a static variable that can also be set in constructor – is read-only otherwise.

Now based on my previous experience doing WinForms and Webforms, normally I tend to set those directly in XAML. Say you want to content of a button or a textblock text (label). So the ideal way would be.

  1. Take View Model
  2. Define consts
  3. Set bindings to consts
  4. Compile and reload

Eureka.. there’s nothing..

Well XAML bindings make a big fuss of INotifyPropertyChanged interface. From sample I know that it works with properties so could it be that ?

Lets start:

  1. Take View Model
  2. Define consts
  3. Define properties
  4. Set property in the constructor to consts
  5. Set bindings to properties
  6. Compile and reload

There.. much better..

Lesson, const and compiler time optimisations are no good.. you need to define properties and additionally call PropertyChanged events and what not including set the properties at VM Creation in order to display data the proper MVVM way. A Single task of setting a text to a textblock or a button is translated into a handful tasks..

Update 1:
My friend @Scottisafool … suggested an alternative approach that he tends to use

  1. Define resources in App.xaml
  2. Set bindings to resources
  3. Compile and reload

This also works.. sure it means that all your strings are in one place but that’s still not MVVM
So it turns out there is not right way but there is a wrong way!! Everything else but code-behind as the last resource is the pukka MVVM way. Wrap up for today I think.

Update 2:
More messages suggesting that the mechanism to declare labels is to either hard code those or use Resources. That’s all fine.. I could use ResourceDictionary just in case the future me might want to localise the app.

That however still leaves the need to support of real consts impossible. There isn’t any support for those in bindings wrt View Model

Background Audio #win8dev #winrt

In this post I am going to detail some of the work I have been doing on Background Audio. But before I get onto it, let me say that the last few months have been crazy / busy, not highly motivational for personal dev etc. I have done a few updates over the last few months but no new development until this month.

About 2 weeks back I started work on Porting Slydr code to WPF control and that was a breeze. Infact last night I saw something about a simple app being tested.. makes me think on whether I should beat them to it :) or not.

Now back to Background Audio. With Windows Phone, one had to create a Background Audio Player which the app and system would then communicate with. The app could kick the Background Audio Player off and then exit and the audio would keep on playing. I have talked about this before and will saying it again. WPDev 7x provided many ways of playing audio. With Windows 8 and #WinRT your options are restricted. I have gotten used to giving MediaElement more usage and credit for what it does.

<MediaElement x:Name="mePlayer" />

All you need to do is set the Source property and then call Play(). The audio starts playing. If the app gets suspended, the music stops playing. To enable background audio there are a number of things you need to do.
* Firstly set MediaElement‘s AudioCategory to BackgroundCapableMedia. This still doesn’t activate the Background Audio as its only the first step

* Now open package.appmanifest file. Open the Declarations tab and add Background Tasks. While you are there set the Supported task types property to Audio and Control Channel. You will also need to set the Start page in App settings. Of course now that you have done this, you will have to create a Badge logo before you can save the changes to appmanifest.
appManifest

* Now back to codebehind of your page and add event handlers for MediaControl‘s PlayPressed, PausePressed, StopPressed and PlayPauseTogglePressed events

In my app I also support Previous Track / Next Track so I additionally added support for PreviousTrackPressed and NextTrackPressed events.

MediaControl.PlayPressed += MediaControl_PlayPressed;
MediaControl.PausePressed += MediaControl_StopPressed;
MediaControl.StopPressed += MediaControl_StopPressed;
MediaControl.PlayPauseTogglePressed += MediaControl_PlayPauseTogglePressed;
MediaControl.NextTrackPressed += MediaControl_NextTrackPressed;
MediaControl.PreviousTrackPressed += MediaControl_PreviousTrackPressed;

and

void MediaControl_PreviousTrackPressed(object sender, object e)
{
    this.Previous();
}

void MediaControl_NextTrackPressed(object sender, object e)
{
    this.Next();
}

void MediaControl_PlayPauseTogglePressed(object sender, object e)
{
    if (MediaControl.IsPlaying)
        this.Stop();
    else
        this.Play();
}

void MediaControl_StopPressed(object sender, object e)
{
    this.Stop();
}

void MediaControl_PlayPressed(object sender, object e)
{
    this.Play();
}

Now when you run the app, initiate the audio playback and navigate away from the app.. Bingo. the audio is still playing.

PhotoViewer with pinch & zoom, pan and flick support

A while back after scouring the net and merging some code, I created PhotoViewer that is used in my cool camera app. It might not be perfect but it works acceptably. Here’s how it work in code.

Lets start with Xaml..

<Image x:Name="imgViewer"  Stretch="UniformToFill" Width="728" Height="480">
    <Image.RenderTransform>
        <ScaleTransform x:Name="scaleTrans" ScaleX="1" ScaleY="1" />
    </Image.RenderTransform>
    <toolkit:GestureService.GestureListener>
        <toolkit:GestureListener Flick="GestureListener_Flick" 
            PinchStarted="GestureListener_PinchStart" 
            PinchDelta="GestureListener_PinchDelta" 
            PinchCompleted="GestureListener_PinchComplete"
            DragStarted="GestureListener_DragStart"
            DragDelta="GestureListener_DragDelta"
            DragCompleted="GestureListener_DragCompleted"
            Tap="GestureListener_Tap"
            DoubleTap="GestureListener_DoubleTap"/>
    </toolkit:GestureService.GestureListener>
</Image>

as you can see.. we start with Image and add a ScaleTransform. Next up with add GestureService from SL Toolkit. I am going to subscribe to some events. Here’s how the code behind looks

lets start by defining some data members

private readonly DispatcherTimer m_animationTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
private double m_friction = 0.99;
private Point m_scrollStartPoint;
private Point m_scrollTarget = new Point();
private double m_hvelocity;
private double m_vvelocity;
private double m_angle;
private double _cx, _cy;

Next up, set the Tick event in the constructor so we can do something on Tick of the DispatcherTimer.

m_animationTimer.Tick += new EventHandler(m_animationTimer_Tick);

Now the real code.. lets implement some of the event handlers

void m_animationTimer_Tick(object sender, EventArgs e)
{
    if (m_hvelocity < 0.1)
        m_hvelocity = 0;

    if (m_vvelocity < 0.1)
        m_vvelocity = 0;

    if (m_hvelocity == 0 && m_vvelocity == 0)
    {
        m_animationTimer.Stop();
        return;
    }

    if (m_angle > 0 && m_angle < 180)
        scaleTrans.CenterY -= m_vvelocity;
    else
        scaleTrans.CenterY += m_vvelocity;

    if(m_angle > 90 && m_angle < 270)
        scaleTrans.CenterX += m_hvelocity;
    else
        scaleTrans.CenterX -= m_hvelocity;
            
     m_hvelocity *= m_friction;
     m_vvelocity *= m_friction;

     if ((scaleTrans.CenterX <= 0 || scaleTrans.CenterX >= imgViewer.Width) || (scaleTrans.CenterY <= 0 || scaleTrans.CenterY >= imgViewer.Height))
     {
         if (scaleTrans.CenterX < 0)
             scaleTrans.CenterX = 0;

         if (scaleTrans.CenterX > imgViewer.Width)
             scaleTrans.CenterX = imgViewer.Width;

         if (scaleTrans.CenterY < 0)
             scaleTrans.CenterY = 0;

          if (scaleTrans.CenterY > imgViewer.Height)
              scaleTrans.CenterY = imgViewer.Height;

          m_animationTimer.Stop();
    }
}
        
private void GestureListener_Flick(object sender, FlickGestureEventArgs e)
{
    m_angle = e.Angle;

    m_animationTimer.Stop();

    m_scrollStartPoint = e.GetPosition(this);
    m_scrollTarget.X = m_scrollStartPoint.X + e.HorizontalVelocity;
    m_scrollTarget.Y = m_scrollStartPoint.Y + e.VerticalVelocity;
                
    m_hvelocity = Math.Abs(e.HorizontalVelocity)/100;
    m_vvelocity = Math.Abs(e.VerticalVelocity)/100;
                                
    if (m_scrollTarget.X < 0) 
        m_scrollTarget.X = 0;

    if(m_scrollTarget.X > imgViewer.Width)
        m_scrollTarget.X = imgViewer.Width;

    if (m_scrollTarget.Y < 0)
        m_scrollTarget.Y = 0;

    if (m_scrollTarget.Y > imgViewer.Height)
        m_scrollTarget.Y = imgViewer.Height;

    m_animationTimer.Start();
}

private void GestureListener_PinchStart(object sender, PinchStartedGestureEventArgs e)
{
    Point p1 = e.GetPosition(imgViewer, 0);
    Point p2 = e.GetPosition(imgViewer, 1);

    scaleTrans.CenterX = (p1.X + ((p2.X - p1.X) / 2));
    scaleTrans.CenterY = (p1.Y + ((p2.Y - p1.Y) / 2));

    _cx = scaleTrans.ScaleX;
    _cy = scaleTrans.ScaleY;
}

private void GestureListener_PinchDelta(object sender, PinchGestureEventArgs e)
{         
    // Compute new scaling factors
    double cx = _cx * e.DistanceRatio;
    double cy = _cy * e.DistanceRatio;

    // If they're between 1.0 and 4.0, inclusive, apply them
    if (cx >= 1.0 && cx <= 4.0 && cy >= 1.0 && cy <= 4.0)
    {
        if ((cy - 1) < 0.1 && (cx - 1) < 0.1)
            cx = cy = 1;
            
        scaleTrans.ScaleX = cx;
        scaleTrans.ScaleY = cy;
    }
}

private void GestureListener_PinchComplete(object sender, PinchGestureEventArgs e)
{
}

private void GestureListener_DragStart(object sender, DragStartedGestureEventArgs e)
{
}

private void GestureListener_DragDelta(object sender, DragDeltaGestureEventArgs e)
{
    scaleTrans.CenterX = (scaleTrans.CenterX - e.HorizontalChange);
    scaleTrans.CenterY = (scaleTrans.CenterY - e.VerticalChange);

    if (scaleTrans.CenterX < 0)
        scaleTrans.CenterX = 0;
    else if (scaleTrans.CenterX > (imgViewer.Height * scaleTrans.ScaleX))
        scaleTrans.CenterX = imgViewer.Height * scaleTrans.ScaleX;

    if(scaleTrans.CenterY < 0)
        scaleTrans.CenterY = 0;
    else if (scaleTrans.CenterY > (imgViewer.Height * scaleTrans.ScaleY))
        scaleTrans.CenterY = imgViewer.Height * scaleTrans.ScaleY;
}

private void GestureListener_DragCompleted(object sender, DragCompletedGestureEventArgs e)
{
    scaleTrans.CenterX = (scaleTrans.CenterX - e.HorizontalChange);
    scaleTrans.CenterY = (scaleTrans.CenterY - e.VerticalChange);

    if (scaleTrans.CenterX < 0)
        scaleTrans.CenterX = 0;
    else if (scaleTrans.CenterX > imgViewer.Width)
        scaleTrans.CenterX = imgViewer.Width;

    if (scaleTrans.CenterY < 0)
        scaleTrans.CenterY = 0;
    else if (scaleTrans.CenterY > (imgViewer.Height))
        scaleTrans.CenterY = imgViewer.Height;
}

private void GestureListener_DoubleTap(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
{
    scaleTrans.ScaleX = scaleTrans.ScaleY = 1;
}

private void GestureListener_Tap(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
{
    m_animationTimer.Stop();
    m_hvelocity = 0;
    m_vvelocity = 0;
}

Slides and Sample Code from Async Task talk

I have uploaded sample from from my async task talk I did last week on github.
https://github.com/hermitdave/AsyncTask

Here is the slide from the talk – Async Programming.

I started by talking about Event based async.. how framework converted sync WCF methods into event driven async methods when one adds a reference for Silverlight or Winddows Phone or Windows 8 projects. I further explained that if you had multiple dependent calls and how async task might actually help.

Next up for a very simple explanation of async task. how async task code creates a dynamic structure which is used to execute the actual code. I mentioned how async task is run on the same thread the current execution is suspended. This raised many questions.. I tried to unsatisfactorily explain the mechanism (by the looks of it). The idea was to talk through individual samples. So next I fired up App and VS and talked individual samples.

There was a hairy project moment when I was trying to show multi-threading using async task.

I think it was okay.. maybe I should have chosen an easier subject ? maybe I just need to sound more convincing next time. I am also going to have comparable async event samples side by side. There were some comments. 1st one was the same day.. very critical comment suggesting that I knew nothing (as I said async does not result in creation of new threads for execution of the said code). The chap further critisied my samples are overly simple.. and finally my projector skills (issues during the last part of event).

Oh well.. Should I continue talking on the subject ?

Why would you ever use async void

Suppose you have a method that uses async task mechanism. Simplest example would be say displaying MessageDialog. Example I will use is using Launcher class to launch browser with a Url.

string BingMapsUri = String.Format("http://www.bing.com/maps/default.aspx?cp={0}~{1}&lvl=18&style=u", latitude, longitute);
 Uri uri = new Uri(BingMapsUri, UriKind.Absolute);
 await Launcher.LaunchUriAsync(uri);

this will launch the default browser with the uri above. Suppose you had an appbar button and you wanted to use click or tapped event to execute above code. The click event handler by default looks like this

private void btnViewOnMap_Click(object sender, RoutedEventArgs e)
{
}

one way would be to declare btnViewOnMap_Click to return Task and declare it async.

private async Task btnViewOnMap_Click(object sender, RoutedEventArgs e)
{
}

However event handlers do not like this and will refuse to work. In my early Win8 dev days I was stumped by this… what the heck was I supposed to do ? The answer was simple.

private async void btnViewOnMap_Click(object sender, RoutedEventArgs e)
{
}

just add async declaration to the event handler. this just informs the framework that this method will make and await async tasks. the event handler would look like this

private async void btnViewOnMap_Click(object sender, RoutedEventArgs e)
{
    string BingMapsUri = String.Format("http://www.bing.com/maps/default.aspx?cp={0}~{1}&lvl=18&style=u", latitude, longitute);
    Uri uri = new Uri(BingMapsUri, UriKind.Absolute);
    await Launcher.LaunchUriAsync(uri);
}

That is the only use of async void. In all other circumstances, you should use async Task

Implement Search Contract the easy way #win8dev

Most apps contain some sort of data that you as a developer would like exposed. That’s were the Search Contract comes into play. Say the user is on the start screen and wishes to search your app, they could just start typing and select your app from the list. If you implemented Search contract, your app will be listed in the app list and the user could just type away and hit enter to see results. That would be nice wouldn’t it ?

Lets get on with it and add search contract in a simple / easy way.
Right click on the Win8 project and select Search Contract. Name it whatever you wish and hit enter.

This does two things:

  • Adds override for OnSearchActivated in your App.xaml.cs
  • Adds required page for search and display of results.

This is what the OnSearchActivated looks like

protected async override void OnSearchActivated(Windows.ApplicationModel.Activation.SearchActivatedEventArgs args)
{
    // TODO: Register the Windows.ApplicationModel.Search.SearchPane.GetForCurrentView().QuerySubmitted

    // event in OnWindowCreated to speed up searches once the application is already running

    // If the Window isn't already using Frame navigation, insert our own Frame
    var previousContent = Window.Current.Content;
    var frame = previousContent as Frame;

    // If the app does not contain a top-level frame, it is possible that this 
    // is the initial launch of the app. Typically this method and OnLaunched 
    // in App.xaml.cs can call a common method.
    if (frame == null)
    {
        // Create a Frame to act as the navigation context and associate it with
        // a SuspensionManager key
        frame = new Frame();
        Cineworld.Common.SuspensionManager.RegisterFrame(frame, "AppFrame");

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            // Restore the saved session state only when appropriate
            try
            {
                await Cineworld.Common.SuspensionManager.RestoreAsync();
            }
            catch //(Cineworld.Common.SuspensionManagerException)
            {
                //Something went wrong restoring state.
                //Assume there is no state and continue
            }
        }
    }

    frame.Navigate(typeof(SearchResults), args.QueryText);
    Window.Current.Content = frame;

    // Ensure the current window is active
    Window.Current.Activate();
}

as you can see it is passing the search query to the Search Results page while navigating.
Now lets look at the code-behind in SearchResults.xaml.
FYI, I am loading my dataset if required in the OnNavigatedTo method. This page is a simple results display page. It contains GridView / ListView to display results. Lets look at the LoadState method

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    var queryText = navigationParameter as String;

    List<SearchResult> matches = new List<SearchResult>();

    foreach (var f in App.Films.Values)
    {
        IEnumerable<CastInfo> casts = from cast in f.FilmCast
                                        where cast.Name.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)
                                        select cast;

        foreach (var c in casts)
        {
            //searchCinemas.Add(c.Name, c);
            matches.Add(new SearchResult() { Name = c.Name, Subtitle = String.Format("{0} in {1}", c.Character, f.Title), SearchObject = f, Image = c.ProfilePath });
        }
    }

    IEnumerable<FilmInfo> films = from film in App.Films.Values
                                    where film.Title.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)
                                    select film;

    searchFilms.Clear();
    searchCinemas.Clear();

    foreach (var f in films)
    {
        searchFilms.Add(f.Title, f);
        matches.Add(new SearchResult() { Name = f.Title, Image = f.PosterUrl, SearchObject = f });
    }

    IEnumerable<CinemaInfo> cinemas = from cinema in App.Cinemas.Values
                                        where cinema.Name.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase)
                                        select cinema;

    foreach (var c in cinemas)
    {
        searchCinemas.Add(c.Name, c);
        matches.Add(new SearchResult() { Name = c.Name, SearchObject = c, Image = new Uri("ms-appx:///Assets/Background.png") });
    }
            
    // TODO: Application-specific searching logic.  The search process is responsible for
    //       creating a list of user-selectable result categories:
    //
    //       filterList.Add(new Filter("<filter name>", <result count>));
    //
    //       Only the first filter, typically "All", should pass true as a third argument in
    //       order to start in an active state.  Results for the active filter are provided
    //       in Filter_SelectionChanged below.

    // Communicate results through the view model
    this.DefaultViewModel["QueryText"] = '\u201c' + queryText + '\u201d';

    bAllowNav = false;

    this.DefaultViewModel["Results"] = matches;

    bAllowNav = true;

    var filterList = new List<Filter>();
    filterList.Add(new Filter("All", 0, true));

    //this.DefaultViewModel["Filters"] = filterList;
    //this.DefaultViewModel["ShowFilters"] = filterList.Count > 1;
}

I added code to search my dataset and to add results to DefaultViewModel. I don’t use MVVM but the default code implemented basic binding etc and I thought I’d leave it at that. I accumulate the results and save them and let bindings do the rest. Let me show you how it looks
Say I got to start screen, type I ben and select my app, this is what I get.

Now you have a nice list of matched entries. Now suppose I wanted to let users click on a matched entry and to navigate them to details page ? I implemented GridView ItemClick event (SelectionChanged doesn’t work for some reason)

private void resultsGridView_ItemClick(object sender, ItemClickEventArgs e)
{
    if (e.ClickedItem != null && bAllowNav)
    {
        SearchResult res = (SearchResult)e.ClickedItem;
        if (res.SearchObject is FilmInfo)
        {
            FilmDetails.SelectedFilm = (res.SearchObject as FilmInfo);
            this.Frame.Navigate(typeof(FilmDetails));
        }
        else if (res.SearchObject is CinemaInfo)
        {
            CinemaDetails.SelectedCinema = (res.SearchObject as CinemaInfo);
            this.Frame.Navigate(typeof(CinemaDetails));
        }
    }
}

Hope this post is helpful to others looking to implement the search contract. Happy coding!

#async task and the for loop bug

I have used various flavours of async task implementation.
* WP7 / async CTP / v3 VS 2010
* Win8 / VS 2012
* Azure / VS 2012
* SL / async targetting pack / VS 2012

I blogged about how i had issues with #async on #aszure and funnily enough i wasn’t the only one. As discussed in http://invokeit.wordpress.com/2012/11/07/windows-azure-and-async-task-mechanism-async/ if you use something like this

AsyncDataSourceObj aeo = new AsyncDataSourceObj();

protected async Task BuildFilmDataSet()
{
    Films films = await aeo.GetFilmData();
    foreach(var film in films)
    {
        Cinemas cinemas = await aeo.GetCinemaData(film);

        // do things

        // do more things 
    }
}

It doesn’t work and a little investigation showed me that i wasn’t doing anything wrong.

The moment await aeo.GetCinemaData(film); gets called, the execution instead of awaiting moves to the next iteration of for loop. Why in the world you would want that ? I am sure if you wanted that one would have used Parallel.For instead.

For some reason, the creators thought that we clever people wouldn’t ever want to do a async call in for loop. I mean its not like its the most common loop usage etc…

The suggestion is to use Task.Yield() (or TaskEx.Yield) within for loop

It however doesn’t always work. I have tried it with my last Silverlight + Async Targetting pack and its still the same. Infact I had to revert to event based async in that case.

Here’s a screen of azure sample in action