Using WMP and Quicktime in Silverlight (Revisited)

November 6, 2011 at 10:34 am | Posted in Silverlight | 1 Comment

In 2007, I was working on a Silverlight application in which I wanted to change the rate of speech playback without affecting the pitch.  At that time, the media element available to Silverlight did not have a rate control while Windows Media Player works wonderfully for mp3 files.  I found that I could actually use WMP in a Silverlight application by embedding the player in the HTML file that hosted the Silverlight control and then controlling the player by using a bridge between managed C# and JavaScript.  The technique was documented in this blog post.

Since then, changes to Silverlight and browsers that have broken this code.

I resurrected the old code base and ported it to Silverlight 4.  The code can be tested here.  And you can download a zip file of the web project here.  The code is still a bit dated and ‘hackish’ but has been tested on IE9, Chrome 15 and Firefox 3.6 with the WMP plug-in installed.  For some reason, the volume control does not work in Chrome.  I also forced Chrome to use the Quicktime player instead of WMP.  It works, but Quicktime does not preserve the pitch when the rate is changed so it will sound weird at slow speeds.  I did not test on a Mac.

Silverlight 5 is supposed to have a new media element that has a rate control function.  If it works as well as WMP, then this approach will no longer be necessary.

Using maps in a Windows 8 Metro application

October 29, 2011 at 2:46 pm | Posted in HTML/JavaScript, Metro, Windows 8 | Leave a comment

There are many uses for detailed, interactive maps in mobile and desktop applications.  The basic and advanced mapping functionality can be supplied by Google or Bing map APIs.  This post will show how to integrate these maps into a Windows 8 Developer Preview application.

Currently, there is no map control for Metro development so one solution is to use a JavaScript map API in an element that can be hosted in the application.  Security restrictions make this approach tricky and, as far as I know, it is only possible to do this using the HTML/JavaScript programming environment.

The security issue is that the main application in the local context may not load scripts that are external to the host. Of course, for both Google and Bing map APIs require an external script load.  The solution is to use an iframe element in the body of the main app that refers to a local html page that operates in a web context and is able to load foreign scripts.  The postMessage() method is used to communicate between the two pages.

In the following example a simple application displays a map, a button and a status output.  The Google map API is used but it could easily be converted to use the Bing map API.  The map displays two markers: one on Paris and the other, Rouen, France.  Clicking the button sends a command to the map page.  The command is to zoom to a set of coordinates (for Paris).  If you click on one of the markers on the map, the city name is set back to the parent page (the main app) and is displayed in the status output.

For purposes of this example, the messaging system is very simple.  It would be possible to create a more generalized function that could be used for a variety of such applications.

Here are the main files:

First, the default.html containing the main display.  There is an iframe element which refers to another local file, map.html.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Map Help Example</title>
    <!-- WinJS references -->
    <link rel="stylesheet" href="/winjs/css/ui-dark.css" />
    <script src="/winjs/js/base.js"></script>
    <script src="/winjs/js/ui.js" type="text/javascript"></script>
    <script type="text/javascript" src="winjs/js/binding.js"></script>
    <script src="/winjs/js/animations.js" type="text/javascript"></script>
    <script src="/winjs/js/uicollections.js" type="text/javascript"></script>
    <script src="/winjs/js/wwaapp.js"></script>
    <!-- WinWebApp2 references -->
    <link rel="stylesheet" href="/css/default.css" />
    <script src="/js/default.js"></script>
    
	
</head>
<body>
    <div id="appBody">
        <h1 id="head_title" class = "win-title">
            Map Example</h1>
        <div id="status">
            Hello</div>
        <button type="submit" id="paris">Paris</button>
        <div id="mapSection">
            <iframe id="mapIframe" src="ms-wwa-web:///map.html"></iframe>
        </div>
    </div>
</body>
</html>

This is the map.html which holds the container for the actual map.  In addition to the map.js script, it loads the Google map API.

<!--This file is used to render the map API and will be included in an iFrame of the main html.-->
<!DOCTYPE html>
<html>
<head>

    <title>Map</title>
    <link href="winjs/css/ui-light.css" rel="stylesheet" type="text/css" />
    <!-- We can include remote scripts because it is rendered in the web context (ms-wwa-web:///map.html)  -->
    <script src="http://maps.googleapis.com/maps/api/js?sensor=false" type="text/javascript"></script>
    <script src="js/map.js" type="text/javascript"></script>
    <style type="text/css">
       
        #map
        {
          position:absolute;
          width:100%;
          height:100%;
        }

    </style>
</head>
<body>
    <div id="map"></div>
</body>
</html>

Here is the default.js which manages the main program UI. Note that the command is an object that is serialized to a string so that it can be transmitted to the map using postMessage(). There is also a function to receive a simple message from the map page to display on the status line.


(function () {
    'use strict';
    
    Debug.enableFirstChanceException(true);

    function id(elementId) {
        return document.getElementById(elementId);
    }

    WinJS.Application.onmainwindowactivated = function (e) {
        if (e.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
            
            WinJS.UI.processAll();

            //Button event listener
            paris.addEventListener("click", changeLocation, false);
        }
    }

    WinJS.Application.start();

    //Button event handler which sends a command to the map to zoom to Paris.
    function changeLocation(e) {

        id("status").innerText = "Zooming to Paris";

        //Construct a message to send
        var msg = {
            command: 'zoomTo',
            latitude: 48.863811,
            longitude: 2.351761,
            zoom: 8
        };

        //Convert message object to string and send to the map control.
        var msgS = JSON.stringify(msg);
        document.frames['mapIframe'].postMessage(msgS, "ms-wwa-web://" + document.location.host);
    }

    //Receive message from map and displays to status output
    window.addEventListener("message", receiveMessage, false);
    function receiveMessage(event) {
        if (event.origin === "ms-wwa-web://" + document.location.host) {
            id("status").innerText = "Hello from " + event.data;
        }
    }
})();

Here is the map.js which controls the map. It receives the string message and converts it back to an object where it is interpreted and operated on. Map events such as the clicking of a marker are handled by sending a message back to the parent page.



//This script handles all of the controls to the map API
(function () {
    'use strict';

    var map;

    //Process messages from main script
    window.addEventListener("message", receiveMessage, false);
    function receiveMessage(event) {
        if (event.origin === "ms-wwa://" + document.location.host) {

            //Return the message string to an object
            var messageObject = JSON.parse(event.data);

            //If message is to zoom, change the location and zoom level
            if (messageObject.command == "zoomTo") {
                var newCenter = new google.maps.LatLng(messageObject.latitude, messageObject.longitude);
                var newOptions = {
                    zoom: messageObject.zoom,
                    center: newCenter,
                    mapTypeId: google.maps.MapTypeId.ROADMAP
                };
                map.setOptions(newOptions);
            }
        }
    }

    //This function sends a message back to the parent window.
    function sendMessageBack(city) {
        window.parent.postMessage(city, "ms-wwa://" + document.location.host);
    }

    function initialize() {
        //initialize the map
        var latlng = new google.maps.LatLng(38.96, -96.78);
        var myOptions = {
            zoom: 4,
            center: latlng,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };
        map = new google.maps.Map(document.getElementById("map"),
        myOptions);

        //Add a couple of pushpins with event handlers
        var parisLocation = new google.maps.LatLng(48.863811, 2.351761);
        var marker1 = new google.maps.Marker({
            position: parisLocation,
            map: map,
            title: "This is Paris"
        });
        google.maps.event.addListener(marker1, 'click', function () {
            sendMessageBack("Paris");
        });

        var rouenLocation = new google.maps.LatLng(49.4467, 1.085889);
        var marker2 = new google.maps.Marker({
            position: rouenLocation,
            map: map,
            title: "This is Rouen"
        });
        google.maps.event.addListener(marker2, 'click', function () {
            sendMessageBack("Rouen");
        });
    }

    document.addEventListener("DOMContentLoaded", initialize, false);
})();

Finally, here is the default.css file which defines the layout of the main application elements.


#appBody
{
    width: 100%;
    height: 100%;
    display: -ms-grid;
    -ms-grid-rows: 100px 85px 520fr 60px;
    -ms-grid-columns: 42px 405px 9px 846fr 64px;
}

#head_title
{
    margin-bottom: -8px;
    margin-top: 0px;
    -ms-grid-column: 2;
    -ms-grid-row-align: end;
    -ms-grid-column-span: 5;
}


#mapSection
{
    -ms-grid-column: 4;
    -ms-grid-row: 1;
    -ms-grid-row-span: 4;
    -ms-grid-column-span: 2;
}

#mapIframe
{
    width: 100%;
    height: 100%;
}

#status
{
    -ms-grid-column: 2;
    -ms-grid-row: 2;
    -ms-grid-row-align: center;
}

#paris
{
    -ms-grid-column: 2;
    -ms-grid-row: 3;
    width: 59px;
    height: 35px;
}

Data binding made simple

September 1, 2011 at 9:51 pm | Posted in Silverlight, Uncategorized, WPF | Leave a comment

Data binding is a very important topic for WPF and Silverlight applications and Microsoft has created a very rich set of functionality in the frameworks to make it work in a variety of ways.  The problem is there are so many ways of doing things that I find the documentation from different sources to be quite confusing.

Today, I was working on a little application which should be very amenable to the use of data binding UI controls (listboxes and textboxes) to data objects.  After being away from WPF for a time, I was struggling to relearn exactly the best way to do it.  The MSDN documentation is thorough but is TMI at times.

I finally ran across a tutorial that captures the essence of what I needed to do in this application as well as others that I have worked on in the past.  If you want a clear example of how to implement data binding for either Silverlight or WPF, I recommend you check out this post. 

Synchronizing media with animation in Silverlight

November 4, 2010 at 9:22 am | Posted in Silverlight | 1 Comment

I am working on a project which consists of several rather long animations that I am building in Blend.  I ran into a problem in adding sound clips that should be played in specific points in the timeline.  Since storyboard only has one event (the end), there was no clear way of starting MediaElement at any specific point in a timeline.

I was directed to a couple of sites for solutions to this problem:

Solution 1

Solution 2

But I came up with a variation that fits my application a little better.

1. Add a textbox to the artboard with an opacity of zero.

2. Add a TextChanged handler for this textbox.

3. In the handler set up ‘if else’ constructs that launch media playback if a specific string is detected in the Text property.

4. In the timeline add keyframes on the textbox that change the Text property to your trigger strings at any point in the animation.

5. For the MediaElement, I set the AutoPlay to true.  Then whenever I change the media source property (in the TextChanged handler), the sound is played.

With this technique, a storyboard is started with various animations.  One of the items animated is the invisible textbox Text field.  When the animation changes the text to a specific keyword, the event handler for the textbox fires and the text field is compared with a list of keywords that trigger the loading of a corresponding media source to the MediaElement which then plays it.

Find the color of a point in a LinearGradientBrush

January 28, 2008 at 7:54 am | Posted in .Net, C#, Silverlight, WPF | 5 Comments

I became intrigued by the problem of finding the color at any point of a rectangle that has been filled with a LinearGradientBrush.  There is are techniques for doing this by essentially sampling the pixel at a rendered point but I wanted to approach the problem algorithmically.

I developed the following method which receives a rectangle object that has been filled with a LinearGradientBrush and a point relative to the upper left corner of the rectangle and returns the calculated color at that point.  There are no restrictions on the Start/Stop Points or the number of GradientStops.  The ColorInterpolationMode can be either SRgbLinearInterpolation (default) or ScRgbLinearInterpolation.  The GradientSpreadMethod is required to be the default (Pad).

There are two methods below:

Color GetColorAtPoint(Rectangle r, Point p) – finds the color at any point of a LinearGradientBrush filled rectangle.

private Double dist(Point px, Point po, Point pf) – helper method for GetColorAtPoint.

To use, copy both methods to your class and call GetColorAtPoint() with a rectangle and point.

//Calculates the color of a point in a rectangle that is filled
//with a LinearGradientBrush.
private Color GetColorAtPoint(Rectangle theRec, Point thePoint)
{
    //Get properties
    LinearGradientBrush br = (LinearGradientBrush)theRec.Fill;
 
    double y3 = thePoint.Y;
    double x3 = thePoint.X;
 
    double x1 = br.StartPoint.X * theRec.ActualWidth;
    double y1 = br.StartPoint.Y * theRec.ActualHeight;
    Point p1 = new Point(x1, y1); //Starting point
 
    double x2 = br.EndPoint.X * theRec.ActualWidth;
    double y2 = br.EndPoint.Y * theRec.ActualHeight;
    Point p2 = new Point(x2, y2);  //End point
 
    //Calculate intersecting points 
    Point p4 = new Point(); //with tangent
 
    if (y1 == y2) //Horizontal case
    {
        p4 = new Point(x3, y1);
    }
 
    else if (x1 == x2) //Vertical case
    {
        p4 = new Point(x1, y3);
    }
 
    else //Diagnonal case
    {
        double m = (y2 - y1) / (x2 - x1);
        double m2 = -1 / m;
        double b = y1 - m * x1;
        double c = y3 - m2 * x3;
 
        double x4 = (c - b) / (m - m2);
        double y4 = m * x4 + b;
        p4 = new Point(x4, y4);
    }
 
    //Calculate distances relative to the vector start
    double d4 = dist(p4, p1, p2);
    double d2 = dist(p2, p1, p2);
 
    double x = d4 / d2;
 
    //Clip the input if before or after the max/min offset values
    double max = br.GradientStops.Max(n => n.Offset);
    if (x > max)
    {
        x = max;
    }
    double min = br.GradientStops.Min(n => n.Offset);
    if (x < min)
    {
        x = min;
    }
 
    //Find gradient stops that surround the input value
    GradientStop gs0 = br.GradientStops.Where(n => n.Offset <= x).OrderBy(n => n.Offset).Last();
    GradientStop gs1 = br.GradientStops.Where(n => n.Offset >= x).OrderBy(n => n.Offset).First();
 
    float y = 0f;
    if (gs0.Offset != gs1.Offset)
    {
        y = (float)((x - gs0.Offset) / (gs1.Offset - gs0.Offset));
    }
 
    //Interpolate color channels
    Color cx = new Color();
    if (br.ColorInterpolationMode == ColorInterpolationMode.ScRgbLinearInterpolation)
    {
        float aVal = (gs1.Color.ScA - gs0.Color.ScA) * y + gs0.Color.ScA;
        float rVal = (gs1.Color.ScR - gs0.Color.ScR) * y + gs0.Color.ScR;
        float gVal = (gs1.Color.ScG - gs0.Color.ScG) * y + gs0.Color.ScG;
        float bVal = (gs1.Color.ScB - gs0.Color.ScB) * y + gs0.Color.ScB;
        cx = Color.FromScRgb(aVal, rVal, gVal, bVal);
    }
    else
    {
        byte aVal = (byte)((gs1.Color.A - gs0.Color.A) * y + gs0.Color.A);
        byte rVal = (byte)((gs1.Color.R - gs0.Color.R) * y + gs0.Color.R);
        byte gVal = (byte)((gs1.Color.G - gs0.Color.G) * y + gs0.Color.G);
        byte bVal = (byte)((gs1.Color.B - gs0.Color.B) * y + gs0.Color.B);
        cx = Color.FromArgb(aVal, rVal, gVal, bVal);
    }
    return cx;
}
 
//Helper method for GetColorAtPoint
//Returns the signed magnitude of a point on a vector with origin po and pointing to pf
private double dist(Point px, Point po, Point pf)
{
    double d = Math.Sqrt((px.Y - po.Y) * (px.Y - po.Y) + (px.X - po.X) * (px.X - po.X));
    if (((px.Y < po.Y) && (pf.Y > po.Y)) ||
        ((px.Y > po.Y) && (pf.Y < po.Y)) ||
        ((px.Y == po.Y) && (px.X < po.X) && (pf.X > po.X)) ||
        ((px.Y == po.Y) && (px.X > po.X) && (pf.X < po.X)))
    {
        d = -d;
    }
    return d;
}

The method works by projecting the input point onto the vector that is described by the LinearGradientBrush Start/Stop points.  There are special cases for horizontal and vertical lines.  The width of the line is calculated as well as the relative distance of the projected point from the StartPoint (using the dist() method).  The GradientStops are ordered and two stops are found that are the closest the projected point.  The color channel values are interpolated based on the relative position of the projected point and these two stops.  The values are assembled into a color and returned.

You can download a demo of this method here.

No Events Tab in WPF Designer — No Problem!

January 12, 2008 at 11:52 am | Posted in Cider, Visual Studio 2008, WPF | Leave a comment

When I first encountered ‘Cider’, the designer for WPF, I was somewhat shocked and disappointed that the Properties window did not have an Events tab as in the Windows Forms designer where one can easily browse the available events and simply double-click to hook one up to an automatically generated event handler stub.  Was I going to have to now rely on the documentation to find the events and carefully type in the connection code and handler?

I said, “Very inconvenient!  Too much work.  I’ll stick to Forms!”  From the forums, I see that I was not alone.

Fortunately, Intellisense makes this a non-issue.  Just know the technique:

In Xaml, put your cursor after the control’s tag (Button, Ellipse, Grid etc) and press the spacebar.  Intellisense will list all of the properties and events that are available.  Note the events are marked with the lightning bolt.  Select an event from the list and hit the tab key twice.  This will add the event to the control’s attribute list and add an event handler stub in your code behind. 

Simple.

Note that the effort is almost identical to using the Events tab in Windows Forms.  The only difference is that you have to scroll through a combined list of properties and events and instead of double clicking, you ‘double tab’.

Or, if you wish, in the code behind, in the window (or page) constructor, type the name of the control (button1 for example) then a ‘.’.  Intellisense will list all properties, methods and events.  Select the event.  Then type ‘+=’ and hit the tab key twice.  Your event will be hooked up along with an event handler stub.  (This is the same as designing with Forms.)

Of course there are some common default events that can be hooked up just by double clicking on the object in the designer.  That is, if you double click on a button in the designer, you get a Button_Click event hookup.

The only thing that is really lacking is the little synopsis of what the event does that is provided by the Properties/Events window in the Forms designer; however, the event names are usually pretty self explanatory.

Hooking up events in the WPF designer are no big deal thanks to the Intellisense support and a little knowledge of how to use it.

Next Page »

Blog at WordPress.com. | Theme: Pool by Borja Fernandez.
Entries and comments feeds.

Follow

Get every new post delivered to your Inbox.