Sunday, April 03, 2016

Using NativeScript's ImageCache to cache images and display them in list like controls

In this article I will review one of the very common usage you encounter when developing a mobile application that shows a list of images (i.e. an image gallery) and how you can use {N}’s ImageCache object to enhance the user experience by caching all images so they are loaded only once.

But let us start with the initial sample code. First we will create a class that we will bind for each image in the list:

import observable = require("data/observable");

export class ImageItem extends observable.Observable
{
    constructor(imageSrc : string)
    {
        super();
        this.set("imageSrc", imageSrc);
    }
}

Then we create a very simple page that will display the images:

<Page xmlns="http://schemas.nativescript.org/tns.xsd" 
      navigatingTo="navigatingTo">
    <ListView items="{{ images }}">
      <ListView.itemTemplate>
        <GridLayout>
          <Image src="{{ imageSrc }}" stretch="aspectFill" height="100"/>
        </GridLayout>
      </ListView.itemTemplate>
    </ListView>
</Page>

For simplicity of the example I use the builtin ListView widget, but in case you are creating a real photo gallery you should better use my GridView widget or the ListView widget from Telerik UI for NativeScript.

In the script for the page we have:

import observableArray = require("data/observable-array");
import observable = require("data/observable");
import imageItem = require("./image-item");
import pages = require("ui/page");

export function navigatingTo(args: pages.NavigatedData)
{
    var page = <pages.Page>args.object;
    var model = new observable.Observable();
    var images = new observableArray.ObservableArray<imageItem.ImageItem>();

    images.push(new imageItem.ImageItem("http://foo.com/bar1.jpg"));
    images.push(new imageItem.ImageItem("http://foo.com/bar2.jpg"));
    // ...
    images.push(new imageItem.ImageItem("http://foo.com/bar100.jpg"));

    model.set("images", images);
    page.bindingContext = model;
}

If we try this sample now you will notice that when you scroll down/up each image is downloaded every time it comes into visible range. And if the images are big this makes the ListView looks unresponsive (not to mention that if your user is using cellular data they wont be very happy :)).

Now we will enhance this by adding ImageCache, so once the image is loaded, subsequent loads will happen from the cache. In order to do so we will change a bit the implementation of the ImageItem class:

import observable = require("data/observable");
import imageCache = require("ui/image-cache");
import imageSource = require("image-source");

/* 1 */
var cache = new imageCache.Cache();
cache.maxRequests = 10;
cache.placeholder = imageSource.fromFile("~/images/no-image.png"));

export class ImageItem extends observable.Observable
{
    private _imageSrc: string
    get imageSrc(): imageSource.ImageSource
    {
        var image = cache.get(this._imageSrc); /* 2 */

        if (image)
        {
            return image; /* 3 */
        }

        cache.push(
            {
                key: this._imageSrc
                , url: this._imageSrc
                , completed:
                (image) =>
                {
                    this.notify(
                        {
                            object: this
                            , eventName: observable.Observable.propertyChangeEvent
                            , propertyName: "imageSrc"
                            , value: image
                        }); /* 4 */
                }
            });

        return cache.placeholder; /* 5 */
    }

    constructor(imageSrc : string)
    {
        super();
        this._imageSrc = imageSrc; /* 6 */
    }
}

With /* 1 */ we create an ImageCache object that is global and will be used for all ImageItems. We set up how many simultaneous downloads we allow and what image to display while the image is being loading. We changed the constructor so that instead of using Observable.set method we introduce a private class member that will contain the URL for the image and we initialize it with /* 6 */. Then we add a property imageSrc that first tries to pull the image from the cache with /* 2 */. If this returns an image then we directly return it with /* 3 */. If an image was not returned then it was not cached and we need to cache it first. For key in the cache we use the URL of the image as it uniquely identifies it. Because the cache loads the image asynchronously, while the image is being loaded we return with /* 5 */ the placeholder image that the user will see until the real image has been downloaded. Once the cache completes loading the image with /* 4 */ we notify all bound widgets that the imageSrc property was changed and they should pull the new data. What this will do is force each Image in the ListView to read the imageSrc property again and this time, because the image has been cached, the real image will be returned with /* 3 */.

By using this approach each image will be downloaded only once and any subsequent loads will not make any network traffic and will be returned instantly from the ImageCache.

Friday, March 25, 2016

Unofficial BBC News feed reader app created with NativeScript in couple of hours

Last week Jen Looper from Telerik set a challenge to create an unofficial BBC News reader app built with {N}. After reviewing another app built with ReactNative, I was surprised at how unorganized RN code looked. There is an XCode project and also have some JS styles embedded in the JS.

Knowing that {N} code is much cleaner (as it separates styles and logic) and I really loved the challenge to transform XML to native controls I decided to start on a freetime project and see where it goes. And here is the result:
ios

The Code

Lets start with the simple part first - the main screen that shows the available news items.

<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo">
    <ActionBar title="Feed" backgroundColor="#BB1919" color="#FFFFFF" />

    <GridLayout>
        <ListView items="{{ items }}" itemTap="goToItem" separatorColor="#BB1919">
            <ListView.itemTemplate>
                <GridLayout rows="150,auto,auto" columns="*,*" class="Container">
                    <Image row="0" col="0" colSpan="2" src="{{ imageHref }}" stretch="aspectFill" />
                    <Label row="1" col="0" colSpan="2" class="Title" text="{{ title }}" />
                    <Label row="2" col="0" class="Date" text="{{ lastUpdateDate | diffFormat() }}" /> <!-- 1 -->
                    <Label row="2" col="1" class="Category" text="{{ category.name }}" />
                </GridLayout>
            </ListView.itemTemplate>
        </ListView>
        <ActivityIndicator busy="{{ isLoadingIn }}" />
    </GridLayout>
</Page>

Nothing much special here - we put an ActionBar, style it and define a ListView and a template for it to display the news items. There is one thing worth mentioning - the formatting of the last update <!-- 1 -->. Here I’m using a converter to format the date. The converter is defined globally in the app.ts file so it can be used by all views in the application:

import application = require("application");
import moment = require("moment");

application.resources.diffFormat = (value: Date) => {
    let x = moment(value);
    let y = moment();
    let diffMins = y.diff(x, "minutes");
    let diffHrs = y.diff(x, "hours");

    if (diffMins < 60) {
        return `${diffMins} minutes ago`;
    }
    else {
        return `${diffHrs} hour${(diffHrs > 1 ? "s" : "" )} ago`;
    }
}

But wait isn’t moment a JS library, how are we using it for a native app? The cool thing is that because NativeScript is based on JS you can use many JS libraries out of the box (as long as they do not use anything browser/node specific). Neat, huh?

Now for the interesting part - the view that shows the content of the news item. If you look at the feed-item view and model files there is not much happening there. That’s because the main logic for this is hidden in the libs/parse-helper.ts files. First let me say that {N}’s XML Parser traverses the XML tree in depth. So the best shot we have to map those XML elements to NativeScript controls is to use a stack (or in the JS world - a simple array). So the general idea is when we encounter a start element we create an appropriate {N} object and add it to the stack. So for example paragraphs/crossheads I map to Labels, bold/italic text will be represented as Spans inside the Label, links will also be represented by Spans, but with a special styling and so on. Then when we get to some text depending on what we have at the top of the stack we add the text to that element. Finally when we get to an XML end element we pop one item from the stack and add it to the next one.

private static _handleStartElement(elementName: string, attr: any) {
    let structureTop = ParseHelper.structure[ParseHelper.structure.length - 1];

    switch (elementName) {
        // ...
        case "bold":
            let sb: Span;
            if (structureTop instanceof Span) { /* 1 */
                sb = structureTop;
            }
            else {
                sb = new Span();
                ParseHelper.structure.push(sb);
            }

            sb.fontAttributes = sb.fontAttributes | enums.FontAttributes.Bold;
            break; 

        case "link":
            if (!ParseHelper._urls) {
                ParseHelper._urls = [];
            }
            let link = new Span();
            link.underline = 1;
            link.foregroundColor = new Color("#BB1919");
            ParseHelper.structure.push(link);
            ParseHelper._urls.push({start: (<Label>structureTop).formattedText.toString().length}); /* 2 */
            break;

        case "url": /* 3 */
            let lastUrl = ParseHelper._urls[ParseHelper._urls.length - 1];
            lastUrl.platform = attr.platform;
            lastUrl.href = attr.href;
            break;

        case "caption":
            ParseHelper._isCaptionIn = true; /* 4 */
            break;

        // ...

        case "video":
            let videoSubView = 
                builder.load(fs.path.join(fs.knownFolders.currentApp().path, "view/video-sub-view.xml")); /* 5 */
            let model = ParseHelper._getVideoModel(attr.id);
            videoSubView.bindingContext = model;
            ParseHelper.structure.push(videoSubView);
            break;

        // ...
    }
}
private static _handleEndElement(elementName: string) {
    switch (elementName) {
        // ...
        case "paragraph":
        case "listItem":
        case "crosshead":
            let label: Label = ParseHelper.structure.pop();
            if (ParseHelper._urls) { /* 6 */
                label.bindingContext = ParseHelper._urls.slice();
                ParseHelper._urls = null;
            }
            (<StackLayout>ParseHelper.structure[ParseHelper.structure.length - 1]).addChild(label);
            break;  

        // ...

        case "italic":
        case "bold":
        case "link":
            // Added check for nested bold/italic tags
            if (ParseHelper.structure[ParseHelper.structure.length - 1] instanceof Span) { /* 7 */
                let link: Span = ParseHelper.structure.pop();
                (<Label>ParseHelper.structure[ParseHelper.structure.length - 1]).formattedText.spans.push(link);
            }
            break;

        case "caption":
            ParseHelper._isCaptionIn = false;
            break;      
        // ...
    }
}
private static _handleText(text: string) {
    if (text.trim() === "") return;

    let structureTop = ParseHelper.structure[ParseHelper.structure.length - 1];

    if (structureTop instanceof Label) {
        let span = new Span();
        span.text = text;
        (<Label>structureTop).formattedText.spans.push(span);
    }
    else if (structureTop instanceof Span) {
        (<Span>structureTop).text = text;
        if (ParseHelper._isCaptionIn) { /* 8 */
            ParseHelper._urls[ParseHelper._urls.length - 1].length = text.length;
        }
    }
    else {
        console.log("UNKNOWN TOP", structureTop);
    }
}

Couple of things worth mentioning:

  1. Since we can have tested bold and italic formatting I had to add /*1*/ in order not add multiple spans but instead apply the formatting on the previous span. And also /*7*/ which pops from the stack only if the item is a Span. In case of nested formatting the Span would be inserted to the Label on the first end bold/italic XML element.
  2. For links since we use simple text we need to remember on what positions exactly do links show (/*2*/) what are their properties (/*3*/) and what is the length of the text in the link (/*4*/ and /*8*/). Then once we finish parsing all the items for the Label we set the found urls as binding context for the Label (/*6*/)
  3. For the video I decided to take a different approach. Because for the video we will need a more complex layout - because we have poster image, play button image and then when clicked we should load and show the video, I decided to separate this in a separate file video-sub-view.xml:
<GridLayout height="200" tap="{{ startVideo }}">
    <Image stretch="aspectFill" src="{{ posterHref }}" />
    <Image src="~/images/play-button.png" width="100" stretch="aspectFit" height="200" />
    <ActivityIndicator busy="{{ isLoadingIn }}" />
</GridLayout>

Then on <!--5--> I load the file with the built-in functions provided by {N}. The neat part is that this function returns a View object which is basically the base building block for all controls. And with that view you can do whatever you can do with any other {N} control. In this case I set the bidningContext. The only catch is that the builder requires the full path to load the XML file. So you cannot use relative paths but you must first get the application directory and then add to that the path to your file. For showing and playing the actual video I’m using Brad Martin’s nativescript-videoplayer plugin.

Conclusion

With only a couple of hours {N} allowed me to create a fully functional native app that works seamlessly for both iOS and android.
You can find the full code here.
You can find more about the challenge and other entries here

The dreaded NativeScript 2.0 Android plugin problem and why it will affect everyone not just plugin developers

UPDATE (04/17/2016): Our prayers have been heard and the {N} team decided to continue support of the current plugin structure with AndroidManifest.xml in the plugin’s platforms folder in addition to the new .aar structure. You can read more about it here.


As a {N} plugin developer last week it was brought to my attention that there are major breaking changes coming to the platforms/android folder. You will probably think that as a plugin user this does not affect you in any way. Well actually it does affect you and I will outline below why it probably be a breaking change for any plugins you use that have some platform specific permissions/resources for android.

Let’s first look at what this change is all about: The {N} core team decided to remove the current ability for plugin developers to include required changes to AndroidManifest.xml and/or used resources in the res folder. And instead of that the ONLY way they added for us to do that is via a precompiled .aar file which should be distributed with the plugin.
But why is this such a problem for plugin developers? As an example I will use the nativescript-contacts plugin to which I contributed.

Workflow pre-2.0

This plugin requires some permissions on android to write and read contacts. Initially it was only reading contacts so in its manifest file there was only the READ_CONTACTS permission. I then added saving of contacts which required two more additional permissions WRITE_CONTACTS and GET_ACCOUNTS. So after I wrote the actual code for saving all I had to do was just to add the appropriate XML in the AndroidManifest.xml and submit the pull request. Then the repo owner would clearly see what this change is and why it was needed.

Workflow 2.0+

So I just finished implementing the logic and need to add the appropriate permissions so the plugin is Plug’n’Play for any plugin end-user. In order to do that first the repo owner would have to have created a separate android project that is setup to be compiled as an android library - it can be either in some other repo or in most cases in the same repo but in some separate folder. Ok, I manage to somehow find where the AndroidManifest.xml file is and add the needed permissions. But wait I’m not done yet. Remember we need an .aar file. So in order to get that file I need to compile this project first. Since I don’t have Android Studio we will use simple gradle to compile. So we have the long sought .aar file. But we are not finished yet. This .aar file is located in the build folder of the other project. So we need to move it under the platforms/android folder of our plugin. Now we are finally done and we can submit our PR to the owner. But the work is not yet finished. Now the repo owner needs to verify my pull request. He checks the code and all looks ok, then comes the .aar file. Ok since it is a binary file he sees that it was changed, but what exactly was the change compared to the previous one? Difficult to find out. He would only assume that I did not do anything wrong or bad. You can see what I’m talking about in the nativescript-calendar plugin which was updated to the new and “better” structure.

So to sum it up: for adding up 2 lines in the AndroidManifest.xml file I additionally had to perform manually 4 steps:
1. Find the manifest file “hidden” inside a different folder structure
2. Build the separate project
3. Find the .aar file in the build folder of the separate project
4. Replace the .aar file in the correct plugin folder

In the era where we have self-driving cars, I think that’s a bit to manually :) Not to mention the overhead it creates from developer point of view.

How does this work with other similar frameworks?

For cordova the plugin has a special config.xml file which can include manifest file additions.
For ReactNative developers have special folders where they can put the plugin’s native iOS/Android code and files.

Conclusion

There has been a big discussion about this issue with many plugin authors trying to explain the situation I outlined above and proposing different solutions. I even ended up submitting a PR with one possible fix with the tradeoff to make the build a bit slower. But all of this just hit a brick wall and came to no avail with no reasonable explanation.

So many plugin developers will remove the manifest file to make their plugins compatible for 2.0 and will write in the readme of the plugin that users need to add this and that to their application manifest file. This is where it affects you as an end-user of a plugin. If you oversee these details in the readme (or if you are a Telerik Platform user and add your plugins via the AppBuilder or Visual Studio interface) the plugin will just not work and your application will most probably crash. At this point send all your love to NativeScript :)

Friday, August 27, 2010

Convert RSA public/private key from XML to PEM format (.NET) (Part 2)

In my previous post I've shown how to convert the public key of an XML formatted RSA key to the more widely used PEM format. The only limitation of the solution was that since it utilizes the Cryptographic Next Generation (CNG) algorithms it is usable only on Windows 7 and Windows Server 2008 R2.

So bellow I'll demonstrate a solution that works under all operating systems. Also as an extra the solution bellow can convert the private key as well ;) Both the public and the private keys exported by the functions bellow are parsed by OpenSSL!

You can find the compiled source here.

Enjoy!

C#:
private static byte[] RSA_OID = 
{ 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 }; // Object ID for RSA

// Corresponding ASN identification bytes
const byte INTEGER = 0x2;
const byte SEQUENCE = 0x30;
const byte BIT_STRING = 0x3;
const byte OCTET_STRING = 0x4;

private static string ConvertPublicKey(RSAParameters param)
{
    List<byte> arrBinaryPublicKey = new List<byte>();

    arrBinaryPublicKey.InsertRange(0, param.Exponent);
    arrBinaryPublicKey.Insert(0, (byte)arrBinaryPublicKey.Count);
    arrBinaryPublicKey.Insert(0, INTEGER);

    arrBinaryPublicKey.InsertRange(0, param.Modulus);
    AppendLength(ref arrBinaryPublicKey, param.Modulus.Length);
    arrBinaryPublicKey.Insert(0, INTEGER);

    AppendLength(ref arrBinaryPublicKey, arrBinaryPublicKey.Count);
    arrBinaryPublicKey.Insert(0, SEQUENCE);

    arrBinaryPublicKey.Insert(0, 0x0); // Add NULL value

    AppendLength(ref arrBinaryPublicKey, arrBinaryPublicKey.Count);

    arrBinaryPublicKey.Insert(0, BIT_STRING);
    arrBinaryPublicKey.InsertRange(0, RSA_OID);

    AppendLength(ref arrBinaryPublicKey, arrBinaryPublicKey.Count);

    arrBinaryPublicKey.Insert(0, SEQUENCE);

    return System.Convert.ToBase64String(arrBinaryPublicKey.ToArray());
}

private static string ConvertPrivateKey(RSAParameters param)
{
    List<byte> arrBinaryPrivateKey = new List<byte>();

    arrBinaryPrivateKey.InsertRange(0, param.InverseQ);
    AppendLength(ref arrBinaryPrivateKey, param.InverseQ.Length);
    arrBinaryPrivateKey.Insert(0, INTEGER);

    arrBinaryPrivateKey.InsertRange(0, param.DQ);
    AppendLength(ref arrBinaryPrivateKey, param.DQ.Length);
    arrBinaryPrivateKey.Insert(0, INTEGER);

    arrBinaryPrivateKey.InsertRange(0, param.DP);
    AppendLength(ref arrBinaryPrivateKey, param.DP.Length);
    arrBinaryPrivateKey.Insert(0, INTEGER);

    arrBinaryPrivateKey.InsertRange(0, param.Q);
    AppendLength(ref arrBinaryPrivateKey, param.Q.Length);
    arrBinaryPrivateKey.Insert(0, INTEGER);

    arrBinaryPrivateKey.InsertRange(0, param.P);
    AppendLength(ref arrBinaryPrivateKey, param.P.Length);
    arrBinaryPrivateKey.Insert(0, INTEGER);

    arrBinaryPrivateKey.InsertRange(0, param.D);
    AppendLength(ref arrBinaryPrivateKey, param.D.Length);
    arrBinaryPrivateKey.Insert(0, INTEGER);

    arrBinaryPrivateKey.InsertRange(0, param.Exponent);
    AppendLength(ref arrBinaryPrivateKey, param.Exponent.Length);
    arrBinaryPrivateKey.Insert(0, INTEGER);

    arrBinaryPrivateKey.InsertRange(0, param.Modulus);
    AppendLength(ref arrBinaryPrivateKey, param.Modulus.Length);
    arrBinaryPrivateKey.Insert(0, INTEGER);

    arrBinaryPrivateKey.Insert(0, 0x00);
    AppendLength(ref arrBinaryPrivateKey, 1);
    arrBinaryPrivateKey.Insert(0, INTEGER);

    AppendLength(ref arrBinaryPrivateKey, arrBinaryPrivateKey.Count);
    arrBinaryPrivateKey.Insert(0, SEQUENCE);

    return System.Convert.ToBase64String(arrBinaryPrivateKey.ToArray());
}

private static void AppendLength(ref List<byte> arrBinaryData, int nLen)
{
    if (nLen <= byte.MaxValue)
    {
        arrBinaryData.Insert(0, Convert.ToByte(nLen));
        arrBinaryData.Insert(0, 0x81); //This byte means that the length fits in one byte
    }
    else
    {
        arrBinaryData.Insert(0, Convert.ToByte(nLen % (byte.MaxValue + 1)));
        arrBinaryData.Insert(0, Convert.ToByte(nLen / (byte.MaxValue + 1)));
        arrBinaryData.Insert(0, 0x82); //This byte means that the length fits in two byte
    }

}

Friday, August 06, 2010

Convert RSA public key from XML to PEM format (.NET) (Part 1)

Probably the people working with asymmetric cryptography have struggled for a way to convert the XML format of the RSA public key to the more widely used PEM format. Although there is a solution for the reverse transformation (from PEM to XML) on the following address http://www.jensign.com/opensslkey/opensslkey.cs I have not found anywhere a solution to this problem.

So after a bit of reading and examining the code in the above mentioned link I've come up with a small code that does the conversion and the resulting key is parsed OK from OpenSSL.

NOTE: you will need to download and use the assemblies from http://clrsecurity.codeplex.com/

NOTE2: The code bellow only works under Windows 7 and Windows Server 2008 R2, because it uses the Cryptographic Next Generation (CNG) that were added only to those operating systems.

C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            RSACng rsa = new RSACng();
            X509Certificate2 cert;
            List<byte> arrBinaryPublicKey = new List<byte>();

            byte[] oid = 
            { 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 }; // Object ID for RSA

            //rsa.FromXmlString(xmlFormatedRSAKey);

            cert = rsa.Key.CreateSelfSignedCertificate(new X500DistinguishedName("CN=something"));

            //Transform the public key to PEM Base64 Format
            arrBinaryPublicKey = cert.PublicKey.EncodedKeyValue.RawData.ToList();
            arrBinaryPublicKey.Insert(0, 0x0); // Add NULL value

            CalculateAndAppendLength(ref arrBinaryPublicKey);

            arrBinaryPublicKey.Insert(0, 0x3);
            arrBinaryPublicKey.InsertRange(0, oid);

            CalculateAndAppendLength(ref arrBinaryPublicKey);

            arrBinaryPublicKey.Insert(0, 0x30);
            //End Transformation

            Console.WriteLine();
            Console.WriteLine("-----BEGIN PUBLIC KEY-----");
            Console.WriteLine(System.Convert.ToBase64String(arrBinaryPublicKey.ToArray()));
            Console.WriteLine("-----END PUBLIC KEY-----");

        }

        private static void CalculateAndAppendLength(ref List<byte> arrBinaryData)
        {
            int nLen;
            nLen = arrBinaryData.Count;
            if (nLen <= byte.MaxValue)
            {
                arrBinaryData.Insert(0, Convert.ToByte(nLen));
                arrBinaryData.Insert(0, 0x81); //This byte means that the length fits in one byte
            }
            else
            {
                arrBinaryData.Insert(0, Convert.ToByte(nLen % (byte.MaxValue + 1)));
                arrBinaryData.Insert(0, Convert.ToByte(nLen / (byte.MaxValue + 1)));
                arrBinaryData.Insert(0, 0x82); //This byte means that the length fits in two byte
            }

        }

    }
}


Compiled source available here

Tuesday, January 19, 2010

Binding MaskedText property of Telerik Silverlight RadMaskedTextBox

If you have used Telerik's RadMaskedTextBox for Silverlight most probably in some cases you have needed to bind in TwoWay mode the MaskedText property of the control (for example for Phones, SSNs, etc. that need to be saved with the punctuation). And most probably you were surprised that you cannot do this because the MaskedText property is readonly. We've asked Telerik whether they are considering to implement such a functionality, but currently there is no definite answer to this.

So here is how to extend they RadMaskedTextBox so you can do your work:

VB:
Imports System.Windows.Data
Imports Telerik.Windows

Public Class RadMaskedTextBoxExtended
    Inherits Telerik.Windows.Controls.RadMaskedTextBox

    Private m_bIsValueChangingIn As Boolean = False
    
#Region " Dependency Property "
    Public Shared ReadOnly ValueMaskProperty As DependencyProperty = _
    DependencyProperty.Register("ValueMask" _
                                , GetType(String) _
                                , GetType(RadMaskedTextBoxExtended) _
                                , New PropertyMetadata(AddressOf ValueMaskChanged) _
                                )
#End Region

#Region " Constructor "
    Public Sub New()
        MyBase.New()
        AddHandler Me.ValueChanged, AddressOf OnValueChanged
    End Sub
#End Region

#Region " Properties "
    Public Property ValueMask() As String
        Get
            Return CStr(Me.GetValue(ValueMaskProperty))
        End Get
        Set(ByVal value As String)
            Me.SetValue(ValueMaskProperty, value)
        End Set
    End Property

    Public Property IsValueChangingIn() As Boolean
        Get
            Return m_bIsValueChangingIn
        End Get
        Set(ByVal value As Boolean)
            m_bIsValueChangingIn = value
        End Set
    End Property
#End Region

#Region " Change Tracking Events "
    Private Sub OnValueChanged(ByVal sender As Object, ByVal e As RadRoutedEventArgs)
        m_bIsValueChangingIn = True
        Me.ValueMask = Me.MaskedText
        Dim oBindingExpression As BindingExpression = Me.GetBindingExpression(ValueMaskProperty)
        
        If oBindingExpression IsNot Nothing Then
            oBindingExpression.UpdateSource()
        End If
        m_bIsValueChangingIn = False
    End Sub

    Private Shared Sub ValueMaskChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim txtRadMaskedTextBoxExtended As RadMaskedTextBoxExtended = CType(d, RadMaskedTextBoxExtended)

        If Not Equals(e.OldValue, e.NewValue) _
        AndAlso Not txtRadMaskedTextBoxExtended.IsValueChangingIn _
        Then
            txtRadMaskedTextBoxExtended.Value = e.NewValue
        End If
    End Sub
#End Region

End Class

Then instead of using their control, just use RadMaskedTextBoxExtended and use the ValueMask property to bind the business object property that must contain the masked text.

This worked quite well in my case :)
Enjoy!

Saturday, December 05, 2009

How to hack Telerik Reporting Q3 to be supported under Visual Studio 2010 Beta 2

Several weeks after Visual Studio 2010 Beta 2 has become public Telerik released their Q3 release. And despite the fact that their Silverlight Controls setup fully supports VS 2010 B2, their reporting setup has no support at all! On community questions whether they will support VS 2010, their answer is that their Reporting will not support Beta IDEs and they will announce their support once VS 2010 goes official (Link). This means that there will be no support for VS 2010 for another 3 months in the best case (rumors say that VS 2010 will be released at the end or March 2010).

And seeing more people (including myself) move on VS 2010 B2 for their main environment for development (especially for Silverlight) having at least some support of Telerik Reporting for VS 2010 is a must. Here are some steps that can almost fully integrate Telerik Reporting in VS 2010 (NOTE: All the steps bellow are valid if you have VS 2008 installed and you have already installed the Telerik Reporting setup):

Registering Telerik Reporting Report File Template
  1. Copy "%ProgramFiles%\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates\VisualBasic\Telerik_Reporting_Q3.zip" to the following locations

    "%ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\VisualBasic\Reporting"

    "%ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\VisualBasic"

  2. Copy "%ProgramFiles%\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates\CSharp\Telerik_Reporting_Q3.zip" to the following locations

    "%ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\Reporting"

    "%ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp"

  3. Run the following from an administrative command prompt: "%ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe" /installvstemplates and wait for the command to complete

Adding Telerik Reporting controls to VS Toolbox
  1. While viewing a report in design mode, open the toolbox and add a new tab naming it Telerik Reporting.
  2. Right click the newly created tab and select "Choose Items..."
  3. Wait for the window to show up (it takes some time) and then click "Browse..."
  4. Navigate to "%ProgramFiles\Telerik\Reporting Q3 2009\Bin", select Telerik.Reporting.dll and press "Open"
  5. On the "Choose Toolbox Items" window press OK

Following the above two procedures will enable you to add new reports and drag items from the toolbox to a report in VS 2010.

The things that I have not managed to enable (YET! :)) under VS 2010 B2 is the Data Explorer window. So you will not be able to directly Drag'n'Drop fields from your data source to the report. Instead of that just add a TextBox control and use the Expression function.

Other than that Telerik Reporting works fine under Visual Studio 2010.
Enjoy!

UPDATE (12/08/2009): In order to have the Table and Crosstab Wizards you have to add also the following assembly from GAC to the Toolbox Items:
%SystemDirve%\Windows\assembly\GAC_MSIL\Telerik.Reporting.Design.Components\<ReportingVersion>_a9d7983dfcc261be\Telerik.Reporting.Design.Components.dll