Windows Phone For Newbies – Geofencing in Windows Phone 8.1 WinRT

Geofencing was one of the newest features Windows Phone 8.1 provided and for the developers it indeed was cool. Previously people did it their way but it’s always better to have an api in our hand. So, before digging into that i strongly suggest you guys to read my previous 2 articles if you haven’t done it already.

1. The Complete Reference of Map Control in Windows Phone 8.1 RT

2. Tracking Location in Windows Phone 8.1

These two would get you started pretty fast and you’d probably want that. If you already read my aforementioned 2 articles, I expect you know how to use a map control in Windows Phone 8.1. We’re going to need that later.

Geofencing:

Geofencing is a mechanism where you put a virtual boundary around a specific point of interest and you want to interact with the user when you arrive or leave that specific boundary. You seriously can do pretty amazing stuff with it actually.

Initial Work:

Now, let’s take a more practical approach to this, every time I go into my room, Im supposed to check my mails. But I usually forget that. How cool it would be if my phone reminds me every time I get into my room and tells me to check my mail?  😉

So, let’s go ahead and create a new Windows Phone app and name it GeoFencingTest. Now, lets see how to do that actually.

So, first of all go over your Package.appxmanifest file and go to the Capabilities Tab and select the Location capability.

Geofencing 1

Now, when you are done with that, let’s move on and start cracking. Before going deep lets get a basic UI running. And it looks like following:

GeoFencing - 2

And on the XAML part it looks like the following:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="4*"></RowDefinition>
            <RowDefinition Height="8*"></RowDefinition>
        </Grid.RowDefinitions>
        

        <Grid Grid.Row="0">
            <TextBlock Text="GeoFencing Sample" FontSize="25" Margin="10"/>
        </Grid>

        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>

            <StackPanel Margin="10">
                <TextBlock Text="Longitude" Margin="0,0,0,15"  FontSize="20"></TextBlock>
                <TextBlock Text="Latitude"  Margin="0,0,0,15" FontSize="20"></TextBlock>
                <TextBlock Text="Altitude"  Margin="0,0,0,15" FontSize="20"></TextBlock>
            </StackPanel>

            <StackPanel Grid.Column="1" Margin="10">
                <TextBlock Name="Longitude" Margin="0,0,0,15"  FontSize="20"></TextBlock>
                <TextBlock Name="Latitude"  Margin="0,0,0,15" FontSize="20"></TextBlock>
                <TextBlock Name="Altitude"  Margin="0,0,0,15" FontSize="20"></TextBlock>
            </StackPanel>

        </Grid>

        <Grid Grid.Row="2" Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="4*"></ColumnDefinition>
                <ColumnDefinition Width="7*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>

            <CheckBox Content="Single Use"></CheckBox>
            <TextBlock Grid.Row="1" FontSize="20" Text="Dwell Time" Margin="0,8,0,0"/>
            <TextBox Grid.Column="1" Name="DwellTime"  Grid.Row="1"/>

            <Button x:Name="CreateGeoFencingButton" Grid.Row="2" Grid.ColumnSpan="2" Content="Create GeoFence" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
            
        </Grid>

    </Grid>

Before going in deep on what is for what let’s get some basic stuff done. Let’s get our current location first.

Since you guys have seen in the last articles, it’s fairly easy to do so. All I’m doing here is get the current location using Geolocator and put the Latitude, Longitude and Altitude into the textblocks defined in the MainPage.xaml.

    public sealed partial class MainPage : Page
    {
        Geolocator geolocator = new Geolocator();
        CancellationTokenSource CancellationTokenSrc = new CancellationTokenSource();

        public MainPage()
        {
            this.InitializeComponent();

            this.NavigationCacheMode = NavigationCacheMode.Required;
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {

            InitializeLocation();

        }

        async private void InitializeLocation()
        {
            try
            {

                geolocator = new Geolocator();

                CancellationTokenSrc = new CancellationTokenSource();
                CancellationToken token = CancellationTokenSrc.Token;

                var position = await geolocator.GetGeopositionAsync(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(30)).AsTask(token);

                Longitude.Text = position.Coordinate.Point.Position.Longitude.ToString();
                Latitude.Text = position.Coordinate.Point.Position.Latitude.ToString();
                Altitude.Text = position.Coordinate.Point.Position.Altitude.ToString();
            }

            catch (Exception)
            {
                if (geolocator.LocationStatus == PositionStatus.Disabled)
                {
                    ShowMessage("Location Services are turned off");
                }

            }
            finally
            {
                CancellationTokenSrc = null;
            }

        }

        private async void ShowMessage(string message)
        {
            MessageDialog dialog = new MessageDialog(message);
            await dialog.ShowAsync();
        }
    }

The only thing that is interesting here is having a CancellationToken generated from a CancellationTokenSource. Usually these are used if you really want control over your task after it’s being fired, the rest of the procedure should look familiar as it’s a basic Geolocator usage to find current geolocation.

Setting up a GeoFence:

Now comes the setting up the actual GeoFence part and even this is fairly simple. The method looks like the following:

        Geofence geofence = null;
        int DefaultDwellTimeInSeconds = 0;
        private void SetupGeofence()
        {
            //Set up a unique key for the geofence
            string GeofenceKey = "My Home Geofence";
            BasicGeoposition GeoFenceRootPosition = GetMyHomeLocation();

            double Georadius = 500;

            // the geocircle is the circular region that defines the geofence
            Geocircle geocircle = new Geocircle(GeoFenceRootPosition, Georadius);

            bool singleUse = (bool)SingleUse.IsChecked;

            //Selecting a subset of the events we need to interact with the geofence
            MonitoredGeofenceStates GeoFenceStates = 0;

            GeoFenceStates |= MonitoredGeofenceStates.Entered;
            GeoFenceStates |= MonitoredGeofenceStates.Exited;

            // setting up how long you need to be in geofence for enter event to fire
            TimeSpan dwellTime;

            dwellTime = DwellTime.Text!=""? ParseTimeSpan(DwellTime.Text) : TimeSpan.FromSeconds(DefaultDwellTimeInSeconds);

            // setting up how long the geofence should be active
            TimeSpan GeoFenceDuration;
            GeoFenceDuration = TimeSpan.FromDays(10);

            // setting up the start time of the geofence
            DateTimeOffset GeoFenceStartTime = DateTimeOffset.Now;

            geofence = new Geofence(GeofenceKey, geocircle, GeoFenceStates, singleUse, dwellTime, GeoFenceStartTime, GeoFenceDuration);

            //Add the geofence to the GeofenceMonitor
            GeofenceMonitor.Current.Geofences.Add(geofence);

        }

        private TimeSpan ParseTimeSpan(string DwellTimeInSeconds)
        {
            return TimeSpan.FromSeconds(Double.Parse(DwellTimeInSeconds));

        }

        private static BasicGeoposition GetMyHomeLocation()
        {
            BasicGeoposition GeoFenceRootPosition;
            GeoFenceRootPosition.Latitude = 23.742766;
            GeoFenceRootPosition.Longitude = 90.417566;
            GeoFenceRootPosition.Altitude = 0.0;
            return GeoFenceRootPosition;
        }

Now, there’s quiet a lot to catch up.

The first one is definitely the GeofenceKey. Put a GeofenceKey in position just to identify a specific Geofence. I put s generic string to identify my home geofence.

Then I called up GetMyHomeLocation() method to set the geofence center to the location of my room. This will identify the center of our desired Geofence.

And the next thing to define is a radius that will define the radius of our circular geofence. We are using Geocircle object to define our geofence here, thus we are using a center and a radius in meters to defined our geofence. You can definitely see the next thing we did is define our geocircle. Actually the Geofence takes a Geoshape object to define it’s radius and currently only Geocircle is supported.

            string GeofenceKey = "My Home Geofence";
            BasicGeoposition GeoFenceRootPosition = GetMyHomeLocation();

            double Georadius = 500;

            // the geocircle is the circular region that defines the geofence
            Geocircle geocircle = new Geocircle(GeoFenceRootPosition, Georadius);

Now, we have put a checkbox in our XAML named SingleUse. This is supposed to define whether our geofence would be used for only one time or not. You can set up temporary one time used geofence using this.


bool singleUse = (bool)SingleUse.IsChecked;

Now, the next thing we did is we defined how our desired geofence would be treated. Which events are we considering with this geofence? Should this be fired only when we get into that geofence or should this will only be considered when we exit the geofence, or even we remove the geofence? We can actually select all or a subset of that group. So we used Logical OR operator to add up all the cases we want our geofence to react upon.


            MonitoredGeofenceStates GeoFenceStates = 0;

            GeoFenceStates |= MonitoredGeofenceStates.Entered;
            GeoFenceStates |= MonitoredGeofenceStates.Exited;

Let’s move on into the time domain now. Now we need to figure how long we need to stay in our favourite geofence to trigger up it’s desired events. I put a textbox in front to define that in seconds, if that is defined, we fire out events from our geofence in that number of seconds and if not we fire out events in our default dwell time defined by 0 seconds.

            // setting up how long you need to be in geofence for enter event to fire
            TimeSpan dwellTime;

            dwellTime = DwellTime.Text!=""? ParseTimeSpan(DwellTime.Text) : TimeSpan.FromSeconds(DefaultDwellTimeInSeconds);

You can even define how long a geofence should be up and running. Like I want my rooms geofence to be running for next 10 days.


            // setting up how long the geofence should be active
            TimeSpan GeoFenceDuration;
            GeoFenceDuration = TimeSpan.FromDays(10);

Even with that thing defined, one question remains, even though it will run for 10 days, when does the geofence gets activated so it can start counting to that defined 10 days. Answer is right below:


// setting up the start time of the geofence
            DateTimeOffset GeoFenceStartTime = DateTimeOffset.Now;

Well, the only thing left to do is create our desired geofence and add it to GeofenceMonitor.


            geofence = new Geofence(GeofenceKey, geocircle, GeoFenceStates, singleUse, dwellTime, GeoFenceStartTime, GeoFenceDuration);
            
            //Add the geofence to the GeofenceMonitor
            GeofenceMonitor.Current.Geofences.Add(geofence);

Now, that’s how you create your own geofence and add it to the GeofenceMonitor. Now you might wonder that whether you’d really have to define all that parameters to create a geofence. Not actually, the littlest you can do is define an Id and a Geocircle to start your geofence. 😉 The rest of the values would take the default assumption.

Handling GeoFence Notifications on Foreground:

We have all our background work to create a geofence but we wont be able to do a single thing unless we test our geofence. Now, we have already defined that our geofence would trigger events after the aforementioned dwellTime and will trigger events if I enter or leave it. So, I definitely have to attach a event handler at a certain place. Don’t I?

Before that, let’s create the method stub for the click event handler for CreateGeoFencingButton and start SetupGeofence() method inside it. If you don’t know how to do that, select the CreateGeoFencingButton in XAML, go to properties, select events tab and double click on the click textbox.


private void CreateGeoFencingButton_Click(object sender, RoutedEventArgs e)
{
SetupGeofence();
}

Then let’s add the handler in the end of SetupGeofence()


GeofenceMonitor.Current.GeofenceStateChanged += Current_GeofenceStateChanged;

Now, let’s say we want to show a message every time I enter and exit my room’s geofence. The handler might look like the following:

        private async void Current_GeofenceStateChanged(GeofenceMonitor sender, object args)
        {
            var Reports = sender.ReadReports();

            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                foreach (GeofenceStateChangeReport report in Reports)
                {
                    GeofenceState state = report.NewState;

                    Geofence geofence = report.Geofence;

                    if (state == GeofenceState.Entered)
                    {
                        ShowMessage("You are pretty close to your room");

                    }
                    else if (state == GeofenceState.Exited)
                    {

                        ShowMessage("You have left pretty far away from your room");
                    }
                }
            });
        }

There you go! That’s how you set up your geofence in an app running in the foreground.

Setting up GeoFence on the background:

Now, this is where things get a little bit tricky. There’s a number of steps involved in this.

  1. Create a Windows Runtime Component Project and add it to your solution.
  2. Change Package.appxmanifest to declare the BackgroundTask that has been created.
  3. Register the BackgroundTask
  4. Handle Geofence Notifications from Background Task

Create a Windows Runtime Component Project:

Usually, background tasks are windows runtime component so you have to add one to your solution. Please go over to your solution and add a Windows Runtime Project.

GeoFencing - 3

GeoFencing - 4

After the new project is created go over class1.cs and rename it to your liking . I renamed it to BackgroundTask and added IBackgroundTask interface to the class and implemented it. I didn’t write anything afterwards. Let’s just keep it like that for a while and move to our GeofenceTest Project. Go over references and   add the newly created GeofenceTask.BackgroundGeofenceTask project to it. So our Wp8.1 project now has reference to the GeofenceTask.BackgroundGeofenceTask project.

Change Package.appxmanifest to declare the BackgroundTask:

Now let’s scoot over to our Package.appxmanifest file and go over Declarations tab. From Available Declarations combobox select BackgroundTask and select loction under the Properties list.

GeoFencing - 5

Now on the Entry point field please put the full qualified assembly name of your newly created background task. Don’t get confused. It’s fairly easy. Let’s have a look at BackgroundGeofenceTask class first.

namespace GeofenceTask
{
    public sealed class BackgroundGeofenceTask : IBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            throw new NotImplementedException();
        }
    }
}

Now put the name as (namespace.classname) format so that leaves us with GeofenceTask.BackgroundGeofenceTask

GeoFencing - 6

Register the BackgroundTask:

Now comes the part where you register the BackgroundTask. We need to register the background task to the app so it knows it to invoke it on time.  The registering method looks like following:

private async Task RegisterBackgroundTask()
        {
            BackgroundAccessStatus backgroundAccessStatus = await BackgroundExecutionManager.RequestAccessAsync();

            var geofenceTaskBuilder = new BackgroundTaskBuilder
            {
                Name = "My Home Geofence",
                TaskEntryPoint = "GeofenceTask.BackgroundGeofenceTask"
            };

            var trigger = new LocationTrigger(LocationTriggerType.Geofence);
            geofenceTaskBuilder.SetTrigger(trigger);

            var geofenceTask = geofenceTaskBuilder.Register();
            geofenceTask.Completed += GeofenceTask_Completed;

            switch (backgroundAccessStatus)
            {
                case BackgroundAccessStatus.Unspecified:
                case BackgroundAccessStatus.Denied:
                    ShowMessage("This application is denied of the background task ");
                    break;
            }

        }

Let’s see what we have in here now. The very first one is get the BackgroundAccessStatus. That helps us to know whether our application is capable of accessing BackgroundTasks. Now, let’s move up and use BackgroundTaskBuilder to create the background task we intend to create. Now, if you look at the TaskEntryPoint we provided there, you’d now realize why we created our Background Task project before and added it to the manifest. Because we are using the same entry point name here. I put a name to identify the background task. This is needed as if there is another background task with the same name you would be thrown an exception. If you want to know whether there is another BackgroundTask with the same name you can iterate through BackgroundTaskRegistration.AllTasks and then make sure whether there is one with the same name or not. You can use a method like the following to do so:

        public static bool IsTaskRegistered(string TaskName)
        {
            var Registered = false;
            var entry = BackgroundTaskRegistration.AllTasks.FirstOrDefault(keyval => keyval.Value.Name == TaskName);

            if (entry.Value != null)
                Registered = true;

            return Registered;
        }

I haven’t used that in this solution but feel free to definitely use this. You can even unregister to make sure that your Background Task is unregistered to open up places for others

        public static void Unregister(string TaskName)
        {
            var entry = BackgroundTaskRegistration.AllTasks.FirstOrDefault(keyval => keyval.Value.Name == TaskName);

            if (entry.Value != null)
                entry.Value.Unregister(true);
        }

The next thing that comes off is defining the LocationTrigger for the BackgroundTaskBuilder object. So, we defined a new LocationTrigger of type LocationTriggerType.Geofence and used SetTrigger method to set it up. Then we used BackgroundTaskBuilder object to register the task and we instantiated a GeofenceTask_Completed event handler for that too.

At the bottom you’d see a switch with backgroundAccessStatus and putting a MessageDialog when it’s denied or unspecified.

Handle Geofence Notifications from Background Task:

So, lets move to our Windows Runtime Component which actually is the Background Task project and implement the IBackgroundTask interface. A method named

public void Run(IBackgroundTaskInstance taskInstance)

would pop up, we’d put our regular geofence event handling in there like there in the Foreground example. Now it looks like following when we are handling it from background

    public sealed class BackgroundGeofenceTask : IBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            var Reports = GeofenceMonitor.Current.ReadReports();
            var SelectedReport =
                Reports.FirstOrDefault(report => (report.Geofence.Id == "My Home Geofence") && (report.NewState == GeofenceState.Entered || report.NewState == GeofenceState.Exited));

            if (SelectedReport==null)
            {
                return;
            }

            //We are creating a toast this time as this is running in Background
            var ToastContent = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);

            var TextNodes = ToastContent.GetElementsByTagName("text");

            if (SelectedReport.NewState == GeofenceState.Entered)
            {
                TextNodes[0].AppendChild(ToastContent.CreateTextNode("You are pretty close to your room"));
                TextNodes[1].AppendChild(ToastContent.CreateTextNode(SelectedReport.Geofence.Id));
            }
            else if(SelectedReport.NewState == GeofenceState.Exited)
            {
                TextNodes[0].AppendChild(ToastContent.CreateTextNode("You are pretty close to your room"));
                TextNodes[1].AppendChild(ToastContent.CreateTextNode(SelectedReport.Geofence.Id));
            }

            var settings = ApplicationData.Current.LocalSettings;

            if (settings.Values.ContainsKey("Status"))
            {
                settings.Values["Status"] = SelectedReport.NewState;
            }
            else
            {
                settings.Values.Add(new KeyValuePair<string, object>("Status", SelectedReport.NewState.ToString()));
            }

            var Toast = new ToastNotification(ToastContent);
            var ToastNotifier = ToastNotificationManager.CreateToastNotifier();
            ToastNotifier.Show(Toast);

        }
    }

Now, this looks a wee bit different from the first example. The first change that is noticable is that we are now selecting the reports based on our geofence id and the report’s new state. As we are in background now, we are using our geofence id to get the proper geofence from the whole list. And as now we are in background we are using a Toast Notification instead of a MessageDialog to show our notification.

Handling it from the App Side:

Now you guys might get confused what to do with the foreground code we made earlier. Do we need Current_GeofenceStateChanged event handler anymore. Now here we might have to be a bit careful. Now if we want our app to react different when it is on foreground and make some UI changes it needed then we have to use GeofenceTask_Completed event rather than Current_GeofenceStateChanged. And there’s another thing to be added. We get our GeofenceMonitor Reports using  GeofenceMonitor.Current.ReadReports() and this step can only be done once for every change. So if your background reads it first then, your foreground app would not be able to read it. So, we have to save it somewhere when the background reads it so our foreground app can read it from there.

So we are using ApplicationData.Current.LocalSettings to save our state in the Background Task. If you look closely you’d find the following snippet:


            var settings = ApplicationData.Current.LocalSettings;

            if (settings.Values.ContainsKey("Status"))
            {
                settings.Values["Status"] = SelectedReport.NewState;
            }
            else
            {
                settings.Values.Add(new KeyValuePair<string, object>("Status", SelectedReport.NewState.ToString()));
            }

So, in our app side GeofenceTask_Completed event handler we’d read the status and fill up a new textblock.

Thus we added a new set of textblocks in the MainPage.xaml


<TextBlock Grid.Row="2" Text="Status" FontSize="25"/>
<TextBlock Grid.Row="2" Grid.Column="1" Name="Status"  FontSize="25"/>

Now, our GeofenceTask_Completed event handler looks like the following:

        private async void GeofenceTask_Completed(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
        {
            if (sender != null)
            {
                // Update the UI with progress reported by the background task
                await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    try
                    {
                        // If the background task threw an exception, display the exception
                        args.CheckResult();

                        var settings = ApplicationData.Current.LocalSettings;

                        // get status
                        if (settings.Values.ContainsKey("Status"))
                        {
                            Status.Text = settings.Values["Status"].ToString();
                        }

                    }
                    catch (Exception ex)
                    {
                        ShowMessage(ex.Message);
                    }
                });
            }

        }

Now, if you look closely you will see all we did here is get the “Status” object from our ApplicationData.Current.LocalSettings and posted it on the TextBlock named Status. 🙂

Now, all we have left to do is do a little change in UI and add an extra button to set up the Geofence background task. Now it looks like the following:

GeoFencing - 8

I created the method stub for the new buttons click event too and it looks like the following:

        private async void CreateBackgroundServiceButton_Click(object sender, RoutedEventArgs e)
        {
            if (IsTaskRegistered("My Home Geofence"))
            {
                Unregister("My Home Geofence");
            }
            await RegisterBackgroundTask();
        }

Testing the App on the Emulator:

You can definitely test the app on the emulator. Like always click the debug button with a emulator selected as the device and when it opens go to additional tools button and open up the location tab. Select a close location to your geofence, I did select a close one to my room and the status changed itself to “Entered”. It was that easy! You can even try the foreground example in the same way!

GeoFencing - 7

The source code is here.

Stay frosty! Hope this would help.

Advertisements

Windows Phone 8.1 for Newbies – Navigation of Windows Phone 8.1 – Frame, Page and SuspensionManager

Windows Phone for Newbies সিরিজে এবার আমাদের বিষয় Windows Phone 8.1 এর নেভিগেশন মডেল। আমরা আজকে দেখবো আপনি একটি নতুন একটি অ্যাপ এ নেভিগেশন কিভাবে handle করবেন। আরো দেখবো আসলে নেভিগেশন কি করে হয়।

শুরুতে আমাদের দরকার একটি নতুন অ্যাপ। তো চলে যান আপনার Visual Studio তে। আমি ব্যবহার করছি Visual Studio 2013 Community Edition. আসুন খুলে নেই নতুন একটি প্রজেক্ট। চলে যান ফাইল -> নিউ প্রজেক্ট

Navigation-1

এখানে দেখতে পাবেন আপনার জন্যে বেশ কয়েকটি প্রজেক্ট টেমপ্লেট আছে। Blank, Pivot, Hub app এই তিনটি টেমপ্লেট আছে। আপনি যদি Pivot অথবা hub টেমপ্লেট সিলেক্ট করেন তাহলে NavigationHelper, SuspensionManager এর মতো Helper Class গুলো পেয়ে যাবেন। কিন্তু এখনকার জন্যে সেগুলো আপনার জন্যে একটু বেশি হয়ে যাবে। আসুন আমরা blank template নিয়ে এগিয়ে যাই।

তো তৈরী করে ফেলুন একটি Blank App. আমি আগেই বলেছি আমরা কিছু Helper Class পাবোনা এই টেমপ্লেট এ। একটি ছোট হ্যাক আছে সেগুলো এই টেমপ্লেট এ নিয়ে আসার। প্রথমে আপনি MainPage.xaml পেজটি Solution explorer থেকে delete করে দিন। এবার Solution Explorer এ সলুশ্যন এর নাম এর ওপরে right click করে Add-> New Item এ চলে যান।

Navigation-2

এরপর সিলেক্ট করুন Basic Page. দেখবেন নিচের মতো একটি পপ আপ ভেসে উঠবে।

Navigation-3

Navigation-4

এখন দেখবেন আপনার প্রজেক্টের Common Folder এ নিচের class গুলো যোগ হয়ে গিয়েছে।

Navigation-5

আমাদের আজকের বিষয় যেহেতু Navigation আমরা শুধু NavigationHelper আর SuspensionManager নিয়ে কথা বলবো।

SuspensionManager এবং Frame:

Windows Phone 8.1 এ আপনার UI টি আসলে বসে থাকে একটি Frame UI control এর উপর এবং এই Frame টি জানে কিভাবে এক Page হতে অন্য Page এ নেভিগেট করতে হয়। এখানেই উইন্ডোজ ফোন ৮.১ উইন্ডোজ ফোন ৮ থেকে আলাদা। এখানে একটি page এ একাধিক Frame থাকতে পারে। আপনি চাইলেই পারেন একটি Single Page Application বানাতে। এবং যেহেতু একটি Frame এ আপনার Navigation History (কোন পেইজ থেকে কোন পেইজে গিয়েছেন তার লিস্ট) থাকে সেহেতু একটি Page এ multiple frame থাকলে multiple navigation history ও থাকতে পারে। তবে তার কথায় এখন যাচ্ছিনা।

আসুন আমরা দেখি Frame কোথায় তৈরী হয়। এটি তৈরী হয় Application.OnLaunched (app.xaml.cs) এ। এটি Window.content property তে দেখে যে main UI টি তৈরী হয়েছে কিনা। না হলে তৈরী করে নেয়। এভাবেই আপনার root frame তৈরী হয়।

protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            Frame rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();

                // TODO: change this value to a cache size that is appropriate for your application
                rootFrame.CacheSize = 1;

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    // TODO: Load state from previously suspended application
                }

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            if (rootFrame.Content == null)
            {
                // Removes the turnstile navigation for startup.
                if (rootFrame.ContentTransitions != null)
                {
                    this.transitions = new TransitionCollection();
                    foreach (var c in rootFrame.ContentTransitions)
                    {
                        this.transitions.Add(c);
                    }
                }

                rootFrame.ContentTransitions = null;
                rootFrame.Navigated += this.RootFrame_FirstNavigated;

                // When the navigation stack isn't restored navigate to the first page,
                // configuring the new page by passing required information as a navigation
                // parameter
                if (!rootFrame.Navigate(typeof(MainPage), e.Arguments))
                {
                    throw new Exception("Failed to create initial page");
                }
            }

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

এখন একটু ভালোমতো খেয়াল করে দেখবেন frame একটি content control (এর একটি content property) আছে আর কি। এর বেশ কিছু method আছে যেমন (Navigate, CanNavigate) এবং এটি আপনার Navigation BackStack manage করে। কিন্তু যে দুটো ফাংশন নিয়ে আমি কথা বলতে চাই তা হচ্ছে

  • GetNavigationState
  • SetNavigationState

মনে করি আমাদের অ্যাপ এ ৩টি পেইজ আছে, প্রত্যেক পেইজে navigate করার সময় একটি param পাঠানো যায়। তাহলে navigation stack টা দাঁড়ায় এরকম Page 1 ( param 1 ) –> Page 2 ( param 2 ) –> Page 3 ( param 3 )

এখন আমরা যদি চাই কোন Page এর Frame এর state জানতে আমরা GetNavigationState() ব্যবহার করতে পারি। আমরা চাইলে Frame টি destroy করতে পারি এবং নতুন করে recreate করতে পারি। এবং চাইলে Frame টি যেখানে ছিলো সেইখানে তাকে রেখে আসতে পারি SetNavigationState() ফাংশনটি ব্যবহার করে।

যদি আমাদের navigation parameter এ সব দেয়া থাকে (যেটা navigate করার সময় আমরা পাঠিয়েছিলাম) তাহলে আমরা যে কোন Frame যে কোন অবস্থা থেকে পুনরায় তৈরী করতে পারবো। এটি কাজে লাগে তখন যখন আমাদের কোন অ্যাপ OS থেকে terminate হয়ে যায় এবং আমরা অ্যাপটিকে ঠিক আগের জায়গা থেকে শুরু করাতে চাই। আর এই কাজটি করতে পারে SuspensionManager class.

এখন SuspensionManager এ আমাদের Frame restore করতে চাইলে একে জানতে হবে Frame কোনটি। তাই Grid, Hub এইসব প্রজেক্ট টেমপ্লেট এ SuspensionManager এ Frame register করা থাকে। আপনি যদি blank template ব্যবহার করে থাকেন তাহলে আপনার OnLaunched মেথডে নিচের কোডটুকু যোগ করে ফেলুন।


Frame rootFrame = Window.Current.Content as Frame;

// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();

// MT: Register the Frame with the SuspensionManager.
SuspensionManager.RegisterFrame(rootFrame, "rootFrameKey");

তাহলে আসুন দেখি আমরা এতোক্ষণ কি দেখলাম।

navigation -5

এই register করার মাধ্যমে SuspensionManager জেনে নেয় এই Frame টি তে প্রয়োজনের সময় সে ফেরত আসতে পারবে।

SuspensionManager কিভাবে Frame restore করে

আসুন দেখি কিভাবে app terminate হয়ে যাবার পর ও আমরা একই Frame এ ফেরত যেতে পারি। আপনার Application.OnLaunched override এ এই কোডটুকু already আছে:


if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}

সাথে এইটুকু যোগ করে নিলেই হলো। এখন আপনার অ্যাপ Terminate হয়ে গেলেও user তার আগের Frame এ ফেরত যেতে পারবে। News app এর feature হিসেবে চমৎকার।


if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
await SuspensionManager.RestoreAsync();
}

এটি একটি async call কারণ SuspensionManager আপনার ফোনের Storage এ গিয়ে Frame navigation history খুঁজে বের করে এবং Frame টি কে SetNavigationState method এর মাধ্যমে তার পুরনো জায়গায় ফেরত দিয়ে আসে।

navigation - 6

মনে রাখবেন সব সময় এই কাজটি করবার দরকার নেই । অনেক আগে একটি অ্যাপ বন্ধ হয়ে গেলে সেটিকে আবার সেই পুরনো স্টেট এ নিয়ে যাবার দরকার নেই। আপনি চাইলেই এরকম একটি কোড সেগমেন্ট রাখতে পারেন যেখানে একটি নির্দিষ্ট সময় বা ইউজার বললে অ্যাপটি আর Frame restore করবেনা।

মজার ব্যাপার হচ্ছে restore করতে হলে state save করতে হয়। আমরা register এবং restore করেছি। save করিনি। state save না করলে restore কি করে হবে বলুন? 🙂

SuspensionManager কিভাবে App Suspension এর সময় State save করে

যখনি আপনার অ্যাপ টি Suspend হচ্ছে (terminate নয়) তখন ই আপনার অ্যাপটির SuspensionManager এর state disk এ save করলে পরে আপনি অ্যাপটি চালু করলে সে আবার পুনরায় আগের অবস্থায় Frame টি কে নিয়ে যাবে।

navigation - 7

যেহেতু এটি Suspension এর ইস্যু তাই আমাদের Application.OnSuspending handler লিখতে হবে এবং এটিতে আপনি Frame State save করতে পারেন এভাবে:


private async void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();

await SuspensionManager.SaveAsync();

deferral.Complete();
}

উপরের ছবিটি খেয়াল করে দেখলে বুঝবেন SuspensionManager একটি Global set of state রাখে নিজের কাছে যেটি আপনি SuspensionManager.SessionState হতে ব্যবহার করতে পারেন। এই Global State এর মধ্যে থাকে একটি Dictionary থাকে যাতে প্রতিটি Frame থাকে এবং তার মধ্যে প্রতিটি Page এর State এর একটি Dictionary থাকে যা NavigationHelper class ব্যবহার করে। আমরা এটি নিয়ে একটু পরে দেখবো।

কিছু নতুন Page যোগ করা:

আমি জানি অনেকেই সরাসরি এই জিনিসটি দেখতে চান কিন্তু যেহেতু Windows Phone 8.1 একটু আলাদা Windows Phone 8 থেকে তাই Frame এবং Page এর ব্যপারে কিছু না বললে ই নয়। আপনি যদি পুরোটা আগে পড়ে থাকেন তাহলে আপনি অবশ্যই উইন্ডোজ ফোন ৮.১ এর নেভিগেশন মডেল বেশ ভালোভাবে জানেন।

আমরা চলে যাই আবার Visual Studio 2013 এ। আমরা উপরে লেখা উপায়ে আরো দুটি Basic Page (Blank page নয়) যোগ করবো। প্রতিটি ক্ষেত্রেই আমি Add New Item dialog টি ব্যবহার করেছি।

আমার যোগ করা Page গুলোর নাম SecondPage এবং ThirdPage

আমার MainPage.xaml টি নিচের মতো:


<Page x:Name="pageRoot"
x:Class="BlankApp.SecondPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BlankApp"
xmlns:common="using:BlankApp.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid Background="Green">
<Grid.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition />
</TransitionCollection>
</Grid.ChildrenTransitions>
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<TextBlock x:Name="pageTitle"
Style="{StaticResource HeaderTextBlockStyle}"
Grid.Column="1"
IsHitTestVisible="false"
TextWrapping="NoWrap"
VerticalAlignment="Bottom"
Margin="0,0,30,40" />
</Grid>
<StackPanel Grid.Row="1"
Margin="120,0,0,0">
<TextBox x:Name="txtMainPageOne"
Text="Not Set" />
<Button Content="Navigate Second Page"
Click="OnNavigateSecondPage" />
</StackPanel>
</Grid>
</Page>

সবচেয়ে কাজে লাগার Method টি হচ্ছে OnNavigateSecondPage যেটি আপনাকে Second Page এ নিয়ে যাবে।


private void OnNavigateSecondPage(object sender, RoutedEventArgs e)
{
this.Frame.Navigate(typeof(SecondPage), this.txtMainPageOne.Text);
}

খেয়াল করে দেখবেন txtMainPageOne টেক্সটবক্স এ যা ছিলো তা আমি parameter হিসেবে SecondPage.xaml এ পাঠিয়ে দিয়েছি।

SeondPage.xaml দেখতে এরকম:


<Page x:Name="pageRoot"
x:Class="BlankApp.SecondPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BlankApp"
xmlns:common="using:BlankApp.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid Background="Green">
<Grid.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition />
</TransitionCollection>
</Grid.ChildrenTransitions>
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<TextBlock x:Name="pageTitle"
Style="{StaticResource HeaderTextBlockStyle}"
Grid.Column="1"
IsHitTestVisible="false"
TextWrapping="NoWrap"
VerticalAlignment="Bottom"
Margin="0,0,30,40" />
</Grid>
<StackPanel Grid.Row="1"
Margin="120,0,0,0">
<TextBox x:Name="txtSecondPageOne"
Text="Not Set" />
<Button Content="Navigate Thid Page"
Click="OnNavigateThirdPage" />
</StackPanel>
</Grid>
</Page>

তার মানে MainPage থেকে পাঠানো parameter আমি access করতে পারবো navigationHelper_LoadState method এ।


private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
this.pageTitle.Text = (string)e.NavigationParameter;
}

private void OnNavigateThirdPage(object sender, RoutedEventArgs e)
{
this.Frame.Navigate(typeof(ThirdPage), this.txtSecondPageOne.Text);
}

দেখতেই পাচ্ছেন SeconPage থেকে ThirdPage এও যাবার সময় আমি একটি parameter পাঠিয়ে দিচ্ছি যা txtSecondPageOne textbox এ লেখা। এবং NavigationHelper class এখন আমাদের কাজে লাগলো কারণ এটি মাত্রই আমাদের Navigation Parameter e হতে MainPage.xaml হতে পাঠানো parameter টি লোড করলো।

আমাদের ThirdPage.xaml টি হলো:


<Page x:Name="pageRoot"
x:Class="BlankApp.ThirdPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BlankApp"
xmlns:common="using:BlankApp.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid Background="Orange">
<Grid.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition />
</TransitionCollection>
</Grid.ChildrenTransitions>
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<TextBlock x:Name="pageTitle"
Style="{StaticResource HeaderTextBlockStyle}"
Grid.Column="1"
IsHitTestVisible="false"
TextWrapping="NoWrap"
VerticalAlignment="Bottom"
Margin="0,0,30,40" />
</Grid>
</Grid>

</Page>

এখন খেয়াল করে দেখবেন আপনার অ্যাপটি নেভিগেট করার সময় প্যারামিটার ঠিকমতো পাস করে নিয়ে যাচ্ছে কিন্তু ব্যাক বাটন চাপলে অ্যাপটি বন্ধ হয়ে যাচ্ছে।

navigation - 8

 

এর কারণ এবং বের হবার উপায় আমরা দেখবো পরের পোস্ট এ। সেখানে আরো দেখবো কিভাবে পেজ ক্যাশ করতে হয়। 🙂

 

 

Recording Audio in Windows Phone 8.1

First Words

This article points to a simple, fast way to record audio in Windows Phone 8.1. As the old Microphone class is no longer available, Windows Phone 8.1 came with new MediaCapture class to consolidate the media recording part for audio and video together. So, why waiting, let’s see how to do it!

Let’s get busy

I jotted a very simple GUI for this that has three simple buttons named “Capture Audio”, “Stop Capture” and “Play Capture”.  And it looks as follows:

CaptureAudio1

If you guys want to have a closer look at the XAML behind, peek a look:

<Page
    x:Class="CaptureSoundTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CaptureSoundTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <MediaElement x:Name="playbackElement1" ></MediaElement>
        <Button x:Name="CaptureButton" Content="Capture Audio" HorizontalAlignment="Center" Margin="0,184,0,0" VerticalAlignment="Top" Click="CaptureButton_Click" Width="145"/>
        <Button x:Name="StopCaptureButton" Content="Stop Capture" HorizontalAlignment="Center" Margin="0,284,0,0" VerticalAlignment="Top" Click="StopCaptureButton_Click" Width="145"/>
        <Button x:Name="PlayRecordButton" Content="Play Capture" HorizontalAlignment="Center" Margin="0,380,0,0" VerticalAlignment="Top" Click="PlayRecordButton_Click" Width="145"/>

    </Grid>
</Page>

It’s pretty basic, I didn’t do much here without keeping the buttons in place. There’s a MediaElement named playbackElement1 to play back the recorded video.

Now, it’s time to populate the button events here named CaptureButton_Click, StopCaptureButton_Click and PlayRecordButton_Click

Initializing and Capturing audio

To capture audio, first we need to initialize the recording. Before digging into that, let’s just initialize some variables that we might need on the journey.

private MediaCapture _mediaCaptureManager;
private StorageFile _recordStorageFile;
private bool _recording;
private bool _userRequestedRaw;
private bool _rawAudioSupported;

The MediaCapture class here is the one responsible here to capture audio and video both here. And StorageFile instance will help us store, locate and play the recorded media on our device.

So, let’s have a look how our recording initialization method looks like:

private async void InitializeAudioRecording()
{

   _mediaCaptureManager = new MediaCapture();
   var settings = new MediaCaptureInitializationSettings();
   settings.StreamingCaptureMode = StreamingCaptureMode.Audio;
   settings.MediaCategory = MediaCategory.Other;
   settings.AudioProcessing = (_rawAudioSupported &amp;&amp; _userRequestedRaw) ? AudioProcessing.Raw : AudioProcessing.Default;

   await _mediaCaptureManager.InitializeAsync(settings);

   Debug.WriteLine("Device initialised successfully");

   _mediaCaptureManager.RecordLimitationExceeded += new RecordLimitationExceededEventHandler(RecordLimitationExceeded);
    _mediaCaptureManager.Failed += new MediaCaptureFailedEventHandler(Failed);
}

So, the very first thing done here is the initialization of the MediaCapture class. Every media capture is associated with a MediaCaptureInitializationSettings instance. We’ve set the StreamingCaptureMode to Audio to make sure this is only audio recording rather than video. There were two bool variables declared before named _rawAudioSupported and _userRequestedRaw. Although I didn’t hook them up from GUI, you guys can defintiely try hooking them up from GUI and thus you can give the app a choice to record raw or pointing it to default audio processing.

The next thing to do is initializing the _mediaCaptureManager with the settings provided. I have included two event handlers for RecordingLimitExceeded and Failed. You guys can write your own handlers of course in these cases.

Now, usually you can put the InitializeAudioRecording method where your app initializes. I put it here on MainPage constructor.

Now, let’s capture audio, MediaCapture class made it really easy indeed.

private async void CaptureAudio()
{
   try
   {
     Debug.WriteLine("Starting record");
     String fileName = "record.m4a";

     _recordStorageFile = await KnownFolders.VideosLibrary.CreateFileAsync(fileName, CreationCollisionOption.GenerateUniqueName);

     Debug.WriteLine("Create record file successfully");

     MediaEncodingProfile recordProfile = MediaEncodingProfile.CreateM4a(AudioEncodingQuality.Auto);
     await _mediaCaptureManager.StartRecordToStorageFileAsync(recordProfile, this._recordStorageFile);

     Debug.WriteLine("Start Record successful");

     _recording = true;
    }
    catch (Exception e)
    {
      Debug.WriteLine("Failed to capture audio");
    }
}

If you have a good look on the code you will see we declared a filename first. And then we created a file in our VideosLibrary using KnownFolders.VideosLibrary.CreateFileAsync that actually points to the media and video library of windows phone 8.1 with a given filename and we also gave a CreationCollisionOption.GenerateUniqueName that says it will create a unique name in case of collision. The next thing to do is creating a recording profile using MediaEncodingProfile class and you can get all possible recording profiles for videos and audios from here. I created a M4a audio profile using MediaEncodingProfile.CreateM4a(recordProfile, this._recordStorageFile) method.

All we have left to do is now start the recording, so  let’s go ahead and start the recording. And it’s easy as pie with _mediaCaptureManager.StartRecordToStorageFileAsync(recordProfile, this._recordStorageFile)

Stopping Recording

Stopping the recording is quiet easy too. All you have to do is call _mediaCaptureManager.StopRecordAsync()

private async void StopCapture()
{

     if (_recording)
     {
          Debug.WriteLine("Stopping recording");
          await _mediaCaptureManager.StopRecordAsync();
          Debug.WriteLine("Stop recording successful");
          _recording = false;
     }

}

Playing Recording

Playing a recording kind of consists of two tasks. First we have to open the recorded file and then play it. If you guys remember we used StorageFile _recordStorageFile to define our file that will hold the recording.

private async void PlayRecordedCapture()
{
     if (!_recording)
     {
         var stream = await _recordStorageFile.OpenAsync(FileAccessMode.Read);
         Debug.WriteLine(&quot;Recording file opened&quot;);
         playbackElement1.AutoPlay = true;
         playbackElement1.SetSource(stream, _recordStorageFile.FileType);
         playbackElement1.Play();
     }
}

So, let’s open up the file and assign the resultant stream to a source of a MediaElement on XAML. In this case that one is playBackElement1

 

Voila! You’re done, now you can play your recording. All you have to do now is assign these three methods to proper button events. For the full source please download the demo project above.

Windows Phone 8.1 for newbies: Episode 2 – Hello Xaml!

উইন্ডোজ ফোন সম্পূর্ণ নতুনদের জন্যে তৈরী করা সিরিজের দ্বিতীয় ভিডিও এটি। আজকের বিষয় Hello Xaml. আশা করি কেমন লাগছে জানাতে ভুলবেন না।

ইউটিউব প্লেলিস্টটি পাচ্ছেন এইখানে

Windows Phone For Newbies: Episode 1 – Introduction

আপনি যদি আগে কখনোই উইন্ডোজ ফোনে কাজ না করে থাকেন তাহলে এই ভিডিও সিরিজ টি আপনার জন্যে। এটি উইন্ডোজ ফোন ৮.১ এর একটি বাংলা টিউটোরিয়াল সিরিজ যা আপনাকে একদম শুরু থেকে উইন্ডোজ ফোনে কাজ করার জন্য সম্যক সকল কিছু জানিয়ে দেবে।

এটি সিরিজটির প্রথম পর্ব

ইউটিউব প্লেলিস্টটি পাচ্ছেন এইখানে

প্রোজেক্ট সোর্স কোড পাচ্ছেন এইখানে

Making a quick rating user control for Windows Phone 8.1

First Words

Usually when someone is working on Windows Phone 8.1 and usually if he’s the guy who just came from Windows Phone 8 development, the first thing he misses is all the toolkits lying around like the Windows Phone Toolkit or Coding4Fun Toolkits. Thus, missing a rating control is not new. So, let’s get our hands dirty and make a little 5 starred rating control fast enough to solve our problem at least to a minimum extent.

Design First!

Okay, to do this thing fast, we might need to make at least a little bit of groundwork in design, I’m going to use Microsoft Expression Design for it. You guys can get the free version from here. It’s pretty lightweight and we can practically export stuff straight to Blend for Visual Studio!

So, let’s go over Microsoft Expression Blend and create a new document. Take a size to your preference as you need it. Let’s draw some stars! To get started, look on the left toolbar and select Polygons.

1

After you select polygons, drag your mouse pointer in the artboard to draw anything that pops off. Now, it might be any kind of polygon, starting from a triangle, like the following came up with too much of points.

2

To rectify this, we need to make sure it has 5 points and an inner depth above 40% and close to 50% so it looks like a star, you can modify the existing one you made or set the proper settings and make a new one. To do that, look on the right on Edit Polygon dialog and change the points to 5 and move the innder depth to 47%. If you want to create a new one you will see the dialog name is Create Polygon. You can go for anything in between 45% tp 50%, I used 47% in this case.

3 4

 

 

Now, with the proper settings set with the polygon give it a yellowish shade and no borders please. And copy the stars and set them side by side so you can have 5 stars in a line that resembles a regular rating control. So, in the artboard it might look pretty close to the one following:

5

Now, what we want to do is put these together on a gradient so it resembles more to the rating control. But before we go do that, we need to make the stars act as a compound path together. So, lets go ahead and select all of them and select Object -> Compound Path -> Make so all the stars can work as a compound path.

6

Now, it’s time to put a gradient over it. Select a yellow to white gradient and put it on your rating control from left to right. Now, there comes a trick, at first you will see the color melted down in the middle, in a rating control we need a clear difference of color to make sure that it looks like one.

7

Now, click on the gradient bar in the middle to put a gradient stop in there.

8

Now, drag the midpoints (the dots over the gradient bar) and put them together to make the color difference more prominent.

9

Now, if we move the Gradient Stop now, we will see the color changes are more prominent and the starts look a lot like rating control now.

10

Now let’s go over Visual Studio and create a Windows Phone 8.1 Project. Let’s put a Right Click on the solution name and add a user control.

11

12

Now, lets select the user control and open it on blend. Let’s select all the stars, copy it and just paste it on the grid of the user control. To your surprise, this thing will just get copied to blend just fine. And let’s make the height of the user control to 40 and the width to 200 just to get the user control to appropriate proportion. I copied a set of stars and put the background to a very light gray color and put it behind the ones we copied at first so it looks a bit more prominent even in a white background.

13

Now, the xaml for the usercontrol will look somewhat like the following:

<UserControl
    x:Class="RatingControlTest.usercontrol"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
 
    d:DesignHeight="40"
    d:DesignWidth="200">

    <Grid>
        <Path Stretch="Fill"
              Data="M -1346.6,-259.247L -1358.76,-305.451L -1321.61,-335.489L -1369.31,-338.197L -1386.39,-382.816L -1403.71,-338.285L -1451.43,-335.823L -1414.43,-305.594L -1426.83,-259.454L -1386.65,-285.302L -1346.6,-259.247 Z M -1067.9,-259.247L -1080.07,-305.451L -1042.91,-335.489L -1090.61,-338.197L -1107.7,-382.816L -1125.01,-338.285L -1172.73,-335.823L -1135.73,-305.594L -1148.13,-259.453L -1107.95,-285.301L -1067.9,-259.247 Z M -1207.25,-259.247L -1219.41,-305.451L -1182.26,-335.489L -1229.96,-338.197L -1247.05,-382.816L -1264.36,-338.285L -1312.08,-335.823L -1275.08,-305.594L -1287.48,-259.453L -1247.3,-285.301L -1207.25,-259.247 Z M -928.552,-259.247L -940.718,-305.451L -903.562,-335.489L -951.264,-338.197L -968.35,-382.816L -985.666,-338.285L -1033.38,-335.823L -996.382,-305.594L -1008.79,-259.453L -968.602,-285.301L -928.552,-259.247 Z M -789.204,-259.247L -801.369,-305.451L -764.214,-335.489L -811.916,-338.197L -829.002,-382.816L -846.318,-338.285L -894.034,-335.823L -857.033,-305.594L -869.437,-259.453L -829.254,-285.301L -789.204,-259.247 Z "  
              UseLayoutRounding="False"
              HorizontalAlignment="Left"
              VerticalAlignment="Top"
              Height="Auto" Width="Auto" Fill="#FFDEDEDE"/>
        <Path Stretch="Fill"
              Data="M -1346.6,-259.247L -1358.76,-305.451L -1321.61,-335.489L -1369.31,-338.197L -1386.39,-382.816L -1403.71,-338.285L -1451.43,-335.823L -1414.43,-305.594L -1426.83,-259.454L -1386.65,-285.302L -1346.6,-259.247 Z M -1067.9,-259.247L -1080.07,-305.451L -1042.91,-335.489L -1090.61,-338.197L -1107.7,-382.816L -1125.01,-338.285L -1172.73,-335.823L -1135.73,-305.594L -1148.13,-259.453L -1107.95,-285.301L -1067.9,-259.247 Z M -1207.25,-259.247L -1219.41,-305.451L -1182.26,-335.489L -1229.96,-338.197L -1247.05,-382.816L -1264.36,-338.285L -1312.08,-335.823L -1275.08,-305.594L -1287.48,-259.453L -1247.3,-285.301L -1207.25,-259.247 Z M -928.552,-259.247L -940.718,-305.451L -903.562,-335.489L -951.264,-338.197L -968.35,-382.816L -985.666,-338.285L -1033.38,-335.823L -996.382,-305.594L -1008.79,-259.453L -968.602,-285.301L -928.552,-259.247 Z M -789.204,-259.247L -801.369,-305.451L -764.214,-335.489L -811.916,-338.197L -829.002,-382.816L -846.318,-338.285L -894.034,-335.823L -857.033,-305.594L -869.437,-259.453L -829.254,-285.301L -789.204,-259.247 Z "  
              UseLayoutRounding="False"
              HorizontalAlignment="Left"
              VerticalAlignment="Top"
              Height="Auto" Width="Auto">
            <Path.Fill>
                <LinearGradientBrush StartPoint="3.55262e-007,0.500002" EndPoint="1,0.500001">
                    <GradientStop Color="#FFFFCC00" Offset="0.6"/>
                    <GradientStop Color="#06FCFCFC" Offset="0.6"/>
                </LinearGradientBrush>
            </Path.Fill>
        </Path>

    </Grid>
</UserControl>

Code Comes Along

Now if we remember stuff clearly, when we moved the gradient stop in blend it looked like the rating bar value is changing. We just need to emulate that in code, that’s it. So, all we need to is change the Offset of <GradientStop> ‘s in LinearGradientBrush entry inside <Path.Fill>. So, we have to databind the offset from backend so we can access it from the usercontrol. And please remember this have to be 0.0 to 1 of course as thats the range for the offset value.

I renamed my user control to RatingControl and let’s move back to the code backend of RatingControl.xaml

public partial class RatingControl : UserControl
    {
        public RatingControl()
        {
            this.InitializeComponent();
        }

        DependencyProperty RateValueProperty = DependencyProperty.Register("RateValue", typeof(double), typeof(RatingControl), new PropertyMetadata(0.1, UpdateValue));

        private static void UpdateValue(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RatingControl control = d as RatingControl;
            control.Value = (double)e.NewValue;
        }

        private double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        private static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(double), typeof(RatingControl), new PropertyMetadata(0.1));

        public double RateValue
        {
            get
            {
                return (double)GetValue(RateValueProperty);
            }
            set
            {
                SetValue(RateValueProperty, value);
            }
        }

    }

Now, our job was to hook up the Value provided from backend. Now I used two DependencyProperty here instead of one because we are going to attach the value in the xaml using a element binding. The first DependencyProperty RateValueProperty has a PropertyMetadata of 0.1 and a function is attached to update the value in the xaml control (update the offset from the xaml).

And Value is the actual DependencyProperty Attached to the control and as it’s private it’s accessible only from inside. So we are exposing it using the RateValueProperty and update it with the Value Dependency Property. You can only use Value and expose it if you want.

Now all we have to do is hook it up in the xaml:

Let’s add a name to the Rating Control. Lets just add x:Name=”UserControl” in the UserControl Tag. And let’s tag the GradientStop property Offset on the ElementName “UserControl”. So, let’s modify the GradientStop tags as following:

<Path.Fill>
     <LinearGradientBrush StartPoint="3.55262e-007,0.500002" EndPoint="1,0.500001">
          <GradientStop Color="#FFFFCC00" Offset="{Binding Value, ElementName=UserControl}"/>
          <GradientStop Color="#06FCFCFC" Offset="{Binding Value, ElementName=UserControl}"/>
      </LinearGradientBrush>
</Path.Fill>

And voila! We are done!

Using the control

Using the control is pretty easy, add xmlns:local=”using:RatingControlTest” in the page tag and use it just like the following:

<Page
    x:Class="RatingControlTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:RatingControlTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <local:RatingControl Width="200" Height="40" RateValue="0.5"></local:RatingControl>
    </Grid>
</Page>

And it will look like the following:

14

Demo Project:

The demo project is here so you can test it yourself. Hope this helps! 😉

বাংলায় উইন্ডোজ ফোন — Introduction to MVMMLight

আবার ফিরে এলাম অনেকদিন পর। আজকের ভিডিওটি mvvmlight toolkit এর ওপর। mvvmlight অসম্ভব জনপ্রিয় একটি mvvm toolkit. আসুন দেখে নেই mvvm এর বেসিক কিছু কাজ কিভাবে এটি দিয়ে খুব সহজেই করে নেয়া যায়।