Saturday, May 28, 2011

WCF Duplex Message Exchange Pattern - Simple sample

This post just explains how to create a simple WCF service of Duplex MEP(Message Exchange Pattern).
  • Two Service contracts need to be created.
  • First, create a Callback contract which the client will implemnt. The service will use this Callback contract to call the client back.
  • Then as for other WCF services create a Service contract and associate the Callback contract to this Service contract using the CallbackContract property of the Service contract attribute.
  • Specify the Operation contracts as "OneWay".
  • Session is required(enabled by default).
  • Get a channel to the current client instance using OperationContext.Current.GetCallbackChannel<>() to call the callback methods.
  • Configure the service with the wsDualHttpBinding.
Snippet 1 - Service Contract

namespace DuplexWcfService1
{
    [ServiceContract]
    public interface IAddHandler
    {
        [OperationContract(IsOneWay = true)]
        void Resultant(double result);
    }

    [ServiceContract(Namespace = "DuplexWcfService1", CallbackContract = typeof(IAddHandler))]    
    public interface IAddService
    {
        [OperationContract(IsOneWay = true)]
        void Add(double param1, double param2);
    }
}

Snippet 2 - Service Implementation

namespace DuplexWcfService1
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
    public class AddService : IAddService
    {
        public void Add(double param1, double param2)
        {
            double result = param1 + param2;

            IAddHandler handler = OperationContext.Current.GetCallbackChannel<IAddHandler>();
            handler.Resultant(result);
        }
    }
}

Snippet 3 - Configuration

<system.serviceModel>
  <services>
    <service name="DuplexWcfService1.AddService">
      <endpoint address="AddService" binding="wsDualHttpBinding" contract="DuplexWcfService1.IAddService" />
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:2290/"/>
        </baseAddresses>
      </host>        
    </service>
  </services>
  
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
        <serviceMetadata httpGetEnabled="true"/>
        <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
        <serviceDebug includeExceptionDetailInFaults="false"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>

I've posted the above sample snippets with namespace for valid reasons. In my configuration I've specified the "name" property of the "service" element as fully qualified name of my service implementation.

If I failed to do so, like if I mentioned "AddService" instead of "DuplexWcfService1.AddService" then, the error "Contract requires Duplex, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it." will be raised.
Hence be cautious on specifying the values for properties in the configuration.

Now create a console application for consuming the above created service. For that,
  • First add the service reference to the service.
  • Create an implementation for the Callback contract.
  • Call the operation to infer the Duplex MEP.
Snippet 4 - Callback implementation

public class AddHandler : AddServiceServiceReference1.IAddServiceCallback
{

    public void Resultant(double result)
    {
        Console.WriteLine("add result : " + result.ToString());            
    }
}

Snippet 5 - Consuming the service

class Program
{
    static void Main(string[] args)
    {
        InstanceContext instanceContext = new InstanceContext(new AddHandler());
        AddServiceServiceReference1.AddServiceClient serviceClient = new AddServiceServiceReference1.AddServiceClient(instanceContext);

        serviceClient.Add(10, 90);
        Console.ReadKey();
    }
}

In order to understand the implementation easily, I've used primitive data types. Use custom types and scale up the scenario when you try on your own. 

Thursday, May 26, 2011

Silverlight 4 - Bind to a property in the level of parent or Ancestor

In Silverlight, most of us will use the MVVM pattern for which we will create a ViewModel and bind it to the respective View.

In such cases, there might arise a scenario in which we need to bind a property for an element or control which is embeded inside another control. Let us take a real case scenario in order to understand the problem clearer.
Consider that our ViewModel contains two properties (CustomCollection, ButtonVisibility) of which one of type ObservableCollection(CustomCollection) and other one of type Visibility(ButtonVisibility).

Now in the View, first we need to bind the ObservableCollection to a ListBox as follows.

Snippet 1

<Grid x:Name="LayoutRoot" Background="White">
    <ListBox ItemsSource="{Binding Path=CustomCollection}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding Path=CustomName}" />
                    <Button Content="Sample Button"></Button>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>    
</Grid>

Then if i want to set the ButtonVisibility property to the button control inside the DataTemplate of the ListBox, then there should be some way to specify that the property belongs to the ancestor and not the current. Unfortunately we can't directly specify this in Silverlight 4.

In order to achieve this we can do an alternate by using ElementName property of Binding. In MVVM, we will be assigning the ViewModel's instance to the View's DataContext. Hence any root level elemnt can be made use. In our case I'll take the "LayoutRoot" element(which is created by default).

Snippet 2

<Grid x:Name="LayoutRoot" Background="White">
    <ListBox ItemsSource="{Binding Path=CustomCollection}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding Path=CustomName}" />
                    <Button Content="Sample Button" Visibility="{Binding Path=DataContext.ButtonVisibility, ElementName=LayoutRoot}"></Button>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

You can use any valid element name with relevant DataContext you need for the property to bind.

Wednesday, April 27, 2011

Access/Edit files inside Silverlight XAP

While developing Silverlight applications, there were times where we might need to change the contents of a file that resides inside the Silverlight XAP from our ASP.Net hosting application. The ASP.Net (hosting application) holds the XAP file in the ClientBin folder.

One way of editing includes following steps:

  • Since XAP file is nothing but a ZIP file, we need to use any .net library for manipulating zip files. One such library is DotNetZip. Use it to retrieve the contents of XAP file.
  • Access and do related modifications of the file you need to update and save it in the application folder temporarily.
  • Remove the original file from the XAP file.
  • Add the temporary file into the XAP file.
  • Finaly, delete the temporary file.

Consider XAP file consists of xml file of format that we need to update :


<ConfigData>
  <Id>
    InitialTestId1
  </Id>
  <CategoryList>
    <Category ID="01">
      <MainCategory>Category 1</MainCategory>
      <Description>Description 1.</Description>
      <Active>true</Active>
    </Category>
  </CategoryList>
...........................
</ConfigData>


Snippet

In order to change the value of "Id" and remove all "CategoryList" element:
private void EditXAP(string xapFileName, string xmlFileToBeEdited)
{

    string clientBinPath = Server.MapPath(Request.ApplicationPath) + "/ClientBin/";
    string xapFilePath = clientBinPath + xapFileName;
    string templConfigFilePath = clientBinPath + xmlFileToBeEdited;
    Stream configXmlStream = new MemoryStream();
    ZipFile zipFile;

    zipFile = ZipFile.Read(xapFilePath);

    if (zipFile.EntryFileNames.Contains(xmlFileToBeEdited))
    {
        ZipEntry zipEntry = zipFile[xmlFileToBeEdited];
        zipEntry.Extract(configXmlStream);
        configXmlStream.Seek(0, 0);
        XDocument xDocument = XDocument.Load(configXmlStream);

        xDocument.Element("ConfigData").Element("Id").SetValue("AlteredId1");
        xDocument.Element("ConfigData").Elements("CategoryList").Remove();

        xDocument.Save(templConfigFilePath);

        zipFile.RemoveEntry(zipEntry);
        zipFile.AddFile(templConfigFilePath, "");
        zipFile.Save();

        FileInfo tempFile = new FileInfo(templConfigFilePath);
        if (tempFile.Exists)
        {
            tempFile.Delete();
        }
    }
}

Above specified snippet will update the XML file inside the Silvelight XAP.

References
http://dotnetzip.codeplex.com/ 

Tuesday, March 29, 2011

ObservableCollection Filtering

ObservableCollection missed out features
In silverlight projects, we used to work with ObservableCollection for well known reasons. While working so, we can also notice some of the functionalities like AddRange, filtering... missed out, which is required for us at some situations. Similar case arose in my project where I need to provide an observable collection with filtering ability.

Alternate for ObservableCollection filtering
Since we don't have filter option in ObservableCollection, I analysed the effort needed for writing a class which has the basic behaviours of ObservableCollection along with filtering capability. Marc Ridey in his blog have created a FilteredObservableCollection for WPF which I adopted to create one.

Now concentrating on the expected features, we need an object that,
  • holds collection of generic items (T)
  • is able to notify when needed (added, removed, property change notification from the items...)
  • is able to bind with ItemsControl
  • is able to filter.

I decided to just expose the required features. The class should be able to add and remove item(T) even if it doesn't comply with the filtering criteria. But upon iterating, it should expose only the filtered items. If our object is able to notify on collection changed and able to enumerate with filtering ability, then we're 75% nearer to the ObservableCollection with filtering ability.

Find the code below for FilterableNotifiableCollection, that gives the basic features of maintaining a collection and exposes only the filtered items upon iterating it.

Snippet 1
public class FilterableNotifiableCollection<T> : INotifyCollectionChanged, INotifyPropertyChanged, IEnumerable<T>, IEnumerable
{
    #region Class Level Variables

    private ObservableCollection<T> sourceCollection;
    private Predicate<T> currentFilter;

    #endregion

    #region Events

    private event NotifyCollectionChangedEventHandler collectionChanged;
    private event PropertyChangedEventHandler propertyChanged;

    public event NotifyCollectionChangedEventHandler CollectionChanged
    {
        add { collectionChanged += value; }
        remove { collectionChanged -= value; }
    }
    public event PropertyChangedEventHandler PropertyChanged
    {
        add { propertyChanged += value; }
        remove { propertyChanged -= value; }
    }

    #endregion

    #region Properties

    public Predicate<T> Filter
    {
        get { return currentFilter; }
        set
        {
            currentFilter = value;
            this.RefreshCollection();
        }
    }

    public ObservableCollection<T> CoreCollection
    {
        get
        {
            return this.sourceCollection;
        }
    }

    public T this[int index]
    {
        get
        {
            if (this.currentFilter == null)
            {
                return this.sourceCollection[index];
            }
            else
            {
                int currentIndex = 0;
                for (int i = 0; i < this.sourceCollection.Count; i++)
                {
                    T indexitem = this.sourceCollection[i];
                    if (this.currentFilter(indexitem))
                    {
                        if (currentIndex.Equals(index))
                        {
                            return indexitem;
                        }
                        else
                        {
                            currentIndex++;
                        }
                    }
                }
                throw new ArgumentOutOfRangeException();
            }
        }
        set
        {
            if (this.currentFilter == null)
            {
                this.sourceCollection[index] = value;
            }
            else if (!this.currentFilter(value))
            {
                throw new InvalidOperationException("Value doesn't match the filter criteria.");
            }
            else
            {
                bool valueNotSet = true;
                int currentIndex = 0;
                for (int i = 0; i < this.sourceCollection.Count; i++)
                {
                    T indexitem = this.sourceCollection[i];
                    if (this.currentFilter(indexitem))
                    {
                        if (currentIndex.Equals(index))
                        {
                            this.sourceCollection[i] = value;
                            valueNotSet = false;
                            break;
                        }
                        else
                        {
                            currentIndex++;
                        }
                    }
                }

                if (valueNotSet)
                {
                    throw new ArgumentOutOfRangeException();
                }

            }
        }
    }

    #endregion

    #region Constructors

    public FilterableNotifiableCollection(ObservableCollection<T> collectionParam)
    {
        this.sourceCollection = collectionParam;
        currentFilter = null;
        this.sourceCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(SourceCollection_CollectionChanged);
        ((INotifyPropertyChanged)sourceCollection).PropertyChanged += new PropertyChangedEventHandler(SourceCollection_PropertyChanged);
    }

    #endregion

    #region Event Handlers

    void SourceCollection_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (propertyChanged != null)
        {
            propertyChanged(this, e);
        }
    }

    void SourceCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (collectionChanged != null)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    break;
                case NotifyCollectionChangedAction.Remove:
                    collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    break;
                case NotifyCollectionChangedAction.Replace:
                    collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    break;
                case NotifyCollectionChangedAction.Reset:
                    collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    break;
                default:
                    break;
            }
        }

    }

    #endregion

    #region Public Methods

    public void Add(T item)
    {
        this.sourceCollection.Add(item);
    }

    public void Remove(T item)
    {
        this.sourceCollection.Remove(item);
    }

    public int IndexOf(T item)
    {
        if (currentFilter == null)
            return this.sourceCollection.IndexOf(item);
        else
        {
            int count = 0;
            for (int i = 0; i < this.sourceCollection.Count; i++)
            {
                T indexitem = this.sourceCollection[i];
                if (currentFilter(indexitem))
                {
                    if (indexitem.Equals(item))
                        return count;
                    else
                        count++;
                }
            }
            return -1;
        }
    }

    public void Clear()
    {
        if (currentFilter == null)
        {
            this.sourceCollection.Clear();
        }
        else
        {
            for (int i = 0; i < this.sourceCollection.Count; )
            {
                T item = this.sourceCollection[i];

                if (currentFilter(item))
                {
                    this.sourceCollection.RemoveAt(i);
                }
                else
                {
                    i++;
                }
            }
        }

    }

    public bool Contains(T item)
    {
        if (currentFilter != null && currentFilter(item) == false)
        {
            return false;
        }

        return this.sourceCollection.Contains(item);
    }

    public int Count
    {
        get
        {
            if (currentFilter == null)
            {
                return this.sourceCollection.Count;
            }
            else
            {
                int count = 0;
                for (int i = 0; i < this.sourceCollection.Count; i++)
                {
                    T item = this.sourceCollection[i];

                    if (currentFilter(item))
                    {
                        count++;
                    }
                }

                return count;
            }
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new FilterableEnumerator(this, this.sourceCollection.GetEnumerator());
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return new FilterableEnumerator(this, this.sourceCollection.GetEnumerator());
    }

    public IEnumerable<T> GetFilteredEnumerableGeneric()
    {
        return new FilterableEnumerable(this, this.sourceCollection.GetEnumerator());
    }

    public IEnumerable GetFilteredEnumerable()
    {
        return new FilterableEnumerable(this, this.sourceCollection.GetEnumerator());
    }

    #endregion

    #region Private Methods

    private void RefreshCollection()
    {
        if (collectionChanged != null)
        {
            collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }

    #endregion

    #region Inner Classes

    private class FilterableEnumerable : IEnumerable<T>, IEnumerable
    {
        private FilterableNotifiableCollection<T> filterableNotifiableCollection;
        private IEnumerator<T> enumerator;

        public FilterableEnumerable(FilterableNotifiableCollection<T> filterablecollection, IEnumerator<T> enumeratorParam)
        {
            filterableNotifiableCollection = filterablecollection;
            enumerator = enumeratorParam;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new FilterableEnumerator(filterableNotifiableCollection, enumerator);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new FilterableEnumerator(filterableNotifiableCollection, enumerator);
        }
    }

    private class FilterableEnumerator : IEnumerator<T>, IEnumerator
    {
        private FilterableNotifiableCollection<T> filterableObservableCollection;
        private IEnumerator<T> enumerator;

        public FilterableEnumerator(FilterableNotifiableCollection<T> filterablecollection, IEnumerator<T> enumeratorParam)
        {
            filterableObservableCollection = filterablecollection;
            enumerator = enumeratorParam;
        }

        public T Current
        {
            get
            {
                if (filterableObservableCollection.Filter == null)
                    return enumerator.Current;
                else if (filterableObservableCollection.Filter(enumerator.Current) == false)
                    throw new InvalidOperationException();
                else
                    return enumerator.Current;
            }
        }

        public void Dispose()
        {
            enumerator.Dispose();
        }

        object IEnumerator.Current
        {
            get { return this.Current; }
        }

        public bool MoveNext()
        {
            while (true)
            {
                if (enumerator.MoveNext() == false)
                    return false;
                if (filterableObservableCollection.Filter == null
                    || filterableObservableCollection.Filter(enumerator.Current) == true)
                    return true;
            }
        }

        public void Reset()
        {
            enumerator.Reset();
        }
    }

    #endregion

}

The main part I struggled to finalize is the NotifyCollectionChangedAction for which I've used Reset for all the actions. In reference to the msdn article, I took advantage of the following sentence.  You should report Reset in cases where the number of individual Add / Remove / Replace actions necessary to properly report changes in a collection becomes excessive. For example, you should report Reset if a list was completely re-ordered based on some operation such as sorting.
 
This can be further tuned by implementing IList. But in order to make the class simple and just wanted to provide the functionality that we're going to use, I restricted with this. Also I've exposed a property called CoreCollection which returns the original collection, so that if we need the whole collection without filtering, we can make use of that.

Apply Filter
For implementing filter over the FilterableNotifiableCollection, specify a predicate for the Filter property.
 
Snippet 2
private void CheckFilterableNotifiableCollection()
{
    FilterableNotifiableCollection<ContactInfo> contactListTest = new FilterableNotifiableCollection<ContactInfo>(new ObservableCollection<ContactInfo>
    {
        new ContactInfo {ID=111, Email="Balaji@Test.com", Phone="206-555-0108"},
        new ContactInfo {ID=112, Email="Jebarson@Test.com", Phone="206-555-0298"},
        new ContactInfo {ID=113, Email="Jegan@Test.com", Phone="206-555-1130"},
        new ContactInfo {ID=114, Email="RamaPrasad@Test.com", Phone="206-555-0521"}
    }
    );

    contactListTest.Filter = new Predicate<ContactInfo>(FilterByIdGreateThan112);

    listBox.ItemsSource = contactListTest;

}

public bool FilterByIdGreateThan112(ContactInfo contactInfoParam)
{
    if (contactInfoParam.ID > 112)
    {
        return true;
    }
    else
    {
        return false;
    }
}
 
References
http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(v=vs.95).aspx
http://blog.mridey.com/2009/05/filtered-observablecollection.html

Thursday, March 24, 2011

Silverlight4 - Printing fit to page

RAVAGE

In my project one of the feature we need to provide is the ability to print a page which holds multiple custom widgets. Printing ability need to be provided in two stages. One for printing the individual widget and the other one for printing the entire page with multiple widgets. Initially I started with developing the provision for printing individual widgets and achieved it simply than what I expected, using the print API in Silverlight 4.

SIMPLE PRINT

SNIPPET 1

public void Print(UIElement elementToBePrinted, string documentName)
{
    PrintDocument docToPrint = new PrintDocument();

    docToPrint.PrintPage += (s, args) =>
    {
        args.PageVisual = elementToBePrinted;
    };

    docToPrint.Print(documentName);
}

I tried printing widgets of normal sizes using the above code which worked and tried the same for printing the entire page with multiple widgets where I got caught. Upon printing entire page, some part will exceed the print area based on the paper size resulting in partial area got print. Simply speaking it won't fit into print area.

We can tranform the FrameworkElement to fit the paper size but upon doing so the element will be scaled visually while printing and the user experience will not be good. Upon searching the web I found that people who experienced similar issues have done workarounds something similar to the one specified in a blog by Jacek Jura.

Workaround is hiding the scaled screen and showing a custom text like "Priniting in progress". I implemented similar workaround and still my tech lead is not satisfied. Hence, I pushed out to find a better alternate for this.

After trying with several options I compromised with one workaround. Idea is getting a snapshot of the element / page and showing the snapshot by hiding the scaled / transformed element while printing. Yes as most of might guess, its what our actors do to trick out video surveillance (in movies like MissionImpossible, Speed, etc....).

I altered the code to fit my needs. Until the completion of the printing process, the UI thread will be blocked and hence the user can't perform actions on UI till then. Hence showing snapshot at that time won't deviate the user experience.

Pros and Cons

The advantage is, user will experience the original view(visually) even when the element to be printed is scaled.
Disadvantage is, since we're showing the snapshot, it'll be slightly blur, but compromisable.

Snippet 2

public class PrintUtility
{
    #region Class Level Variables

    private const string NullReferenceExceptionMessage = "PrintElement is null : Please specify the element to be printed.";
    private Grid parentGrid;
    private Border borderItem;
    private WriteableBitmap printElementImage;
    private ImageBrush printElementImageBrush;

    #endregion

    #region Properties

    public FrameworkElement PrintElement { get; private set; }

    public string DocumentName { get; private set; }

    public FrameworkElement TabletUIElement { get; set; }

    #endregion

    #region Constructors

    public PrintUtility(FrameworkElement elementToBePrinted, string documentName)
    {
        this.PrintElement = elementToBePrinted;
        this.DocumentName = documentName;
    }

    #endregion

    #region Event Handlers

    void DocToPrint_EndPrint(object sender, EndPrintEventArgs e)
    {
        this.PrintElement.RenderTransform = null;

        if (this.borderItem != null)
        {
            if (parentGrid.Children.Contains(this.borderItem))
            {
                parentGrid.Children.Remove(this.borderItem);
            }
            this.borderItem = null;
        }

        this.parentGrid = null;
        this.printElementImage = null;
        this.printElementImageBrush = null;
    }

    void DocToPrint_BeginPrint(object sender, BeginPrintEventArgs e)
    {
        FrameworkElement maskElement = this.TabletUIElement ?? this.PrintElement;

        if (maskElement.Parent is Grid)
        {
            parentGrid = (Grid)maskElement.Parent;
            parentGrid.Children.Add(BuildBorderWithImageFill());
        }
    }

    void DocToPrint_PrintPage(object sender, PrintPageEventArgs e)
    {
        double scale = 1;

        if (e.PrintableArea.Height < this.PrintElement.ActualHeight)
        {
            scale = e.PrintableArea.Height / this.PrintElement.ActualHeight;
        }

        if (e.PrintableArea.Width < this.PrintElement.ActualWidth && e.PrintableArea.Width / this.PrintElement.ActualWidth < scale)
        {
            scale = e.PrintableArea.Width / this.PrintElement.ActualWidth;
        }

        if (scale < 1)
        {
            ScaleTransform scaleTransform = new ScaleTransform();
            scaleTransform.ScaleX = scale;
            scaleTransform.ScaleY = scale;

            this.PrintElement.RenderTransform = scaleTransform;
        }

        e.PageVisual = this.PrintElement;
    }

    #endregion

    #region Public Methods

    public void Print()
    {
        if (this.PrintElement != null)
        {
            PrintDocument docToPrint = new PrintDocument();

            docToPrint.PrintPage += new EventHandler<PrintPageEventArgs>(DocToPrint_PrintPage);
            docToPrint.BeginPrint += new EventHandler<BeginPrintEventArgs>(DocToPrint_BeginPrint);
            docToPrint.EndPrint += new EventHandler<EndPrintEventArgs>(DocToPrint_EndPrint);

            docToPrint.Print(this.DocumentName);
        }
        else
        {
            throw new NullReferenceException(NullReferenceExceptionMessage);
        }

    }

    #endregion

    #region Private Methods

    private Border BuildBorderWithImageFill()
    {
        printElementImageBrush = new ImageBrush();
        borderItem = new Border();

        printElementImage = new WriteableBitmap(this.TabletUIElement ?? this.PrintElement, null);
        printElementImageBrush.ImageSource = printElementImage;

        borderItem.Background = printElementImageBrush;
        borderItem.Child = new TextBlock() { Text = "Printing...", HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center };

        return borderItem;
    }

    #endregion

}

As a prerequisite, we need a simple Grid immediately surrounding the printable element so as to place the snapshot in the Grid.

SUMMARY

Also I tried with creating an image from the printable element and scale the image to print. The resultant quality is good but not as good as printing the element directly. You can also try that if it suits for you. Snippet for creating an image of the printable element, scale the created image and then print the scaled image is specified below.

Snippet 3

public class PrintUtilityImageScaling
{
    #region Class Level Variables

    private const string NullReferenceExceptionMessage = "PrintElement is null : Please specify the element to be printed.";
    
    #endregion

    #region Properties

    public FrameworkElement PrintElement { get; private set; }

    public string DocumentName { get; private set; }

    #endregion

    #region Constructors

    public PrintUtilityImageScaling(FrameworkElement elementToBePrinted, string documentName)
    {
        this.PrintElement = elementToBePrinted;
        this.DocumentName = documentName;
    }

    #endregion

    #region Event Handlers

    void DocToPrint_PrintPage(object sender, PrintPageEventArgs e)
    {
        double scale = 1;
        WriteableBitmap writeableBitmap;

        if (e.PrintableArea.Height < this.PrintElement.ActualHeight)
        {
            scale = e.PrintableArea.Height / this.PrintElement.ActualHeight;
        }

        if (e.PrintableArea.Width < this.PrintElement.ActualWidth && e.PrintableArea.Width / this.PrintElement.ActualWidth < scale)
        {
            scale = e.PrintableArea.Width / this.PrintElement.ActualWidth;
        }

        if (scale < 1)
        {
            ScaleTransform scaleTransform = new ScaleTransform();
            scaleTransform.ScaleX = scale;
            scaleTransform.ScaleY = scale;

            writeableBitmap = new WriteableBitmap(this.PrintElement, scaleTransform);
            writeableBitmap.Invalidate();
        }
        else
        {
            writeableBitmap = new WriteableBitmap(this.PrintElement, null);
        }

        Image image = new Image();
        image.Source = writeableBitmap;

        Grid imageGrid = new Grid();
        imageGrid.Children.Add(image);

        e.PageVisual = imageGrid;
    }

    #endregion

    #region Public Methods

    public void Print()
    {
        if (this.PrintElement != null)
        {
            PrintDocument docToPrint = new PrintDocument();

            docToPrint.PrintPage += new EventHandler<PrintPageEventArgs>(DocToPrint_PrintPage);
            
            docToPrint.Print(this.DocumentName);
        }
        else
        {
            throw new NullReferenceException(NullReferenceExceptionMessage);
        }

    }

    #endregion

}

I prefer Snippet 2 over Snippet 3. But again its upto you to decide based on the scenario.

REFERENCES

http://jacekjura.blogspot.com/2010/11/silverlight-printing-fit-to-page.html

Monday, March 21, 2011

jQuery event subscription and UpdatePanel(AsyncPostBack)

We usually bind the events for controls to be handled with jQuery in the $(document).ready. But when I tried to try the same for the controls inside the UpdatePanel, I caught up with an issue. The event hooking works fine until the AsyncPostBack happens, but not after that. Its clear that the initial subscription is nomore valid, for which I searched for that and found out in the web.

PageRequestManager

The endRequest event of the PageRequestManager will provide the right space to solve the issue. We need to resubscribe to the events again upon AsyncPostBack. As per the msdn documentation, the endRequest event is Raised after an asynchronous postback is finished and control has been returned to the browser.
The following snippet will do this for you.

Snippet 1

$(document).ready(function () {
    initiateBinding();

    Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);

});

function EndRequestHandler(sender, args) {
    initiateBinding();
}

function initiateBinding() {
    //Do the subscription here.
}


Have specified an example below which describes the above specified solution.

Snippet 2

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="PageRequestManager.aspx.cs"
    Inherits="PageRequestManager" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.5.1.min.js"></script>
    <script type="text/javascript">

        $(document).ready(function () {
            initiateBinding();

            Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);

        });

        function initiateBinding() {
            $("input[id$='ClientClickButton']").click(function (event) {
                event.preventDefault();

                alert("Click event handling from jQuery");
            });
        }
        
        function EndRequestHandler(sender, args) {
            if (args.get_error() == undefined) {
                initiateBinding();
            }
            else {
                var errorMessage = args.get_error().message;
                args.set_errorHandled(true);

                //Do error handling here.
                alert("Error : " + errorMessage);
            }
        }
        
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div>
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Button ID="ClientClickButton" runat="server" Text="Client Click Button" />
                <br />
                <br />
                <asp:Button ID="AsyncPostBackTestButton" runat="server" Text="Async PostBack Test Button" />
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="AsyncPostBackTestButton" EventName="Click" />
            </Triggers>
        </asp:UpdatePanel>
    </div>
    </form>
</body>
</html>


REFERENCE
http://msdn.microsoft.com/en-us/library/bb383810.aspx

Monday, February 28, 2011

Check / Uncheck all check boxes in ASP.Net using jQuery

jQuery make our scripting(javascript) life simple. One of the powerful things with jQuery is its Selectors. In ASP.Net while displaying list of data using GridView, DataList, ListView or Repeater control, one of the frequently expected thing is the check all option which is expected to happen on the client side.

To achieve those two things need to be done.

Case 1 : Upon the click of the header check box (CheckAll), all the row check boxes' status need to be changed based on the header check box. Case 2 : Upon click of the row check boxes, header check box status need to be set as checked when all of the row check boxes are checked sand vice versa. Following are the sample code to achieve that.

Snippet 1

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CheckAllWithJQuery.aspx.cs" Inherits="CheckAllWithJQuery" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>CheckAll check boxes in DataList</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.5.1.min.js"></script>
    <script type="text/javascript">

        $(document).ready(function () {
            initiateCheckAllBinding();            
        });

        function initiateCheckAllBinding() {
            var headerCheckBox = $("input[id$='HeaderCheckBox']");
            var rowCheckBox = $("#StudentTable input[id*='RowItemCheckBox']");

            headerCheckBox.click(function (e) {                
                rowCheckBox.attr('checked', headerCheckBox.is(':checked'));
            });

            // To select CheckAll when all Item CheckBox is selected and 
            // to deselect CheckAll when an Item CheckBox is deselected.     
            rowCheckBox.click(function (e) {
                var rowCheckBoxSelected = $("#StudentTable input[id*='RowItemCheckBox']:checked");

                if (rowCheckBox.length == rowCheckBoxSelected.length) {
                    headerCheckBox.attr("checked", true);
                }
                else {
                    headerCheckBox.attr("checked", false);
                }
            });
            $("table[id$='StudentDataList'] input[id*='RowItemCheckBox']")
        }       
         
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:Repeater ID="StudentRepeater" runat="server">
            <HeaderTemplate>
                <table border="1" cellpadding="5" cellspacing="0" id="StudentTable">
                    <tr>
                        <td>
                            <asp:CheckBox ID="HeaderCheckBox" runat="server" />
                        </td>
                        <td>
                            <b>Id</b>
                        </td>
                        <td>
                            <b>Name</b>
                        </td>
                    </tr>
            </HeaderTemplate>
            <ItemTemplate>
                <tr>
                    <td>
                        <asp:CheckBox ID="RowItemCheckBox" runat="server" />
                    </td>
                    <td>
                        <asp:Literal ID="IdLiteral" runat="server" Text='<%# Eval("Id") %>'></asp:Literal>
                    </td>
                    <td>
                        <asp:Literal ID="NameLiteral" runat="server" Text='<%# Eval("Name") %>'></asp:Literal>
                    </td>
                </tr>
            </ItemTemplate>
            <FooterTemplate>
                </table>
            </FooterTemplate>
        </asp:Repeater>
    </div>
    </form>
</body>
</html>

In the above snippet you find that I've used a repeater control which I've specified a template in such a way the final will output will be of a table structure with id "StudentTable". Also I've specified the header check box with id "HeaderCheckBox" and row check box with id "RowItemCheckBox".

In jQuery, in order to get the instance of the header check, I've used "Attribute Ends With Selector".
$("input[id$='HeaderCheckBox']")
Similarly for getting the instances of row check boxes, I've used "Descendant Selector", that is row item check box selector that is descendant of element with id as "StudentTable".
$("#StudentTable input[id*='RowItemCheckBox']")
This works fine for the above provided template. What need to be done if we have a GridView or DataList? Just we need to identify the html rendering of that control and tweek the selector matching that. To be more clear, in general, GridView and DataList will be rendered as html table by default. Think of a case that you have a DataList with id "StudentDataList", then you can modify the selector as
$("table[id$='StudentDataList'] input[id*='RowItemCheckBox']")
SUMMARY
To achieve Case 2, simply I'm evaluating based on the count of number of row check boxes with that of the number selected row check boxes.

REFERENCE
http://api.jquery.com/category/selectors/
http://www.devcurry.com/2009/08/check-uncheck-all-checkboxes-in-aspnet.html
 
Creative Commons License
This work by Tito is licensed under a Creative Commons Attribution 3.0 Unported License.