Thursday, June 30, 2011

Silverlight 4 - Programmatically invoking command's CanExecute status

While developing Silverlight 4 applications with Model-View-ViewModel (MVVM), one of the feature that comforts developers is the command object that implements from ICommand. The ICommand interface exposes two methods Execute and CanExecute and an event CanExecuteChanged. As the name implies, the Execute method is the one to be executed when the command is invoked and CanExecute is the method that specifies the execution status - means, it influences whether the command can be executed or not. For getting core details of ICommand, please refer msdn.

Points to be considered while implementing ICommand
Upon implementing the ICommand, simply what we'll be doing is,
  • Get two delegates that matches the Execute and CanExecute methods' signatures(Action<object> and Func<object, bool> resp) as parameters in the constructor and store them in a locally in the class level scope.
  • Then invoke those delegates in the respective methods. That is Action delegate (Action<object>) in the Execute method and the function delegate (Func<object, bool>) in the CanExecute method.
  • In addition to invoking the action delegate (Action<object>), the Execute method need to call the CanExecute method.
  • In addition to invoking the function delegate (Func<object, bool>), the CanExecute method need to raise the CanExecuteChanged event.
There exists several examples in web on implementing ICommand interface and using it. One such is provided by John Papa here, which details more about commanding.

In Silverlight 4, ButtonBase and Hyperlink supports commanding.

The CanExecute method plays its role visually by enabling or disabling the control it(the command object) is bound to. That is the reason why the CanExecute method is called from the Execute method as it'll validate the execution status at the time of execution.

Now comes the core of this post: From the above explanation it is clear that the CanExecute method is called on the command action of the bound control (example: on click of the control). But in some scenarios, we might need to make a strategy somehow that the CanExecute method need to be invoked in order to reevaluate the execution status. Such scenario arises when changes from some other manipulation affects the logic we use to arrive the return value of CanExecute method. Say as a result of some manipulation your ViewModel changes a property which is used in the CanExecute method. So now you need to programmatically achieve reevaluation of the execution status.

Triggering CanExecuteChanged event will do this for you. Hence its good to have a public method in the class that implements the ICommand to raise the CanExecuteChanged event.

Snippet 1

public void RaiseCanExecuteChanged()
{
    if (CanExecuteChanged != null)
    {
        CanExecuteChanged(this, new EventArgs());
    }
}

Hence RaiseCanExecuteChanged is also one of the important point to be considered when implementing the ICommand, as such scenarios are common in applications with MVVM that uses command.

Additional Info
Prism provides this ICommand implementation through DelegateCommand class, which also exposes the RaiseCanExecuteChanged method.

References
http://msdn.microsoft.com/en-us/library/system.windows.input.icommand(v=vs.95).aspx
http://johnpapa.net/5-simple-steps-to-commanding-in-silverlight

Tuesday, June 28, 2011

Silverlight 4 Printing Centered

In one of my previous article, I've explained about printing fit to page in Silverlight 4(Silverlight4 - Printing fit to page). In this article, I'll enhance the PrintUtility(specified in the Snippet 2 of post) to print the element centered in the print area(center of the paper).

Let us recap the idea of printing fit to page. 
  1. The print element is transformed (scaled) to fit to page, when the print element size exceeds the print area - Scaled.
  2. The print element is printed as it is otherwise - Normal.
During scale transformation, the print element is scaled in such a way to maintain the aspect ratio. So it is clear that we need to handle both the scenarios (Scaled & Normal) while centering the print element.

Ok, now we need to identify how much the print element need to be positioned left and top. A simple calculation will do that for us.

Left position = ((total print area width) - (print element width)) / 2, Top position = ((total print area height) - (print element height)) / 2.

Initially I thought of setting the margin to the print element to achieve centering. But after that I got an alternate way from an article. As most of you know three primary transformations are Transformation, Rotation and Scaling. It is the translate transformation that we're going to use for centering.

Change will be there only in the DocToPrint_PrintPage handler with respective calculations and transformation included. Below is the complete code snippet of the PrintUtility.

Snippet 1

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 BusyIndicatorControl busyIndicatorControl;

    private WriteableBitmap printElementImage;

    private ImageBrush printElementImageBrush;

    #endregion

    #region Events

    #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

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

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

            this.borderItem = null;
        }

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

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

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

    private void DocToPrint_PrintPage(object sender, PrintPageEventArgs e)
    {
        double scale = 1;
        TransformGroup transformGroup = new TransformGroup();

        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;

            TranslateTransform translateTransform = new TranslateTransform();
            translateTransform.X = (e.PrintableArea.Width - (this.PrintElement.ActualWidth * scale)) / 2;
            translateTransform.Y = (e.PrintableArea.Height - (this.PrintElement.ActualHeight * scale)) / 2;

            transformGroup.Children.Add(scaleTransform);
            transformGroup.Children.Add(translateTransform);
        }
        else
        {
            TranslateTransform translateTransform = new TranslateTransform();
            translateTransform.X = (e.PrintableArea.Width - this.PrintElement.ActualWidth) / 2;
            translateTransform.Y = (e.PrintableArea.Height - this.PrintElement.ActualHeight) / 2;

            transformGroup.Children.Add(translateTransform);
        }

        this.PrintElement.RenderTransform = transformGroup;
        e.PageVisual = this.PrintElement;
    }

    #endregion

    #region Public Methods

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

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

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

    #endregion

    #region Private Methods

    private BusyIndicatorControl BuildBusyIndicatorControl()
    {
        this.busyIndicatorControl = new BusyIndicatorControl();

        this.busyIndicatorControl.IsBusy = true;
        this.busyIndicatorControl.BusyContent = "Processing For Printing......";

        return this.busyIndicatorControl;
    }

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

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

        this.borderItem.Background = this.printElementImageBrush;
        this.borderItem.Child = this.BuildBusyIndicatorControl();

        return this.borderItem;
    }

    #endregion
}


Thursday, June 23, 2011

Silverlight 4 ComboBox SelectedItem vanishes on ItemsSource update

In Silverlight 4, the SelectedItem is set to null when we update / change (new instance set) the ItemsSource.

Let us take a case of having a readonly property that builds a new list(by performing operations like filtering , sortings, etc... on an existing data) each time the property is read(get).

Snippet 1
 
ObservableCollection<ICheckItem> checkItemCollection;

public ObservableCollection<ICheckItem> CheckItemCollection
{
    get
    {
        return checkItemCollection;
    }
    set
    {
        checkItemCollection = value;
        checkItemCollection.CollectionChanged += (s, e) =>
        {
            NotifyPropertyChanged("SortedCheckItemCollection");
        };
        NotifyPropertyChanged("CheckItemCollection");
        NotifyPropertyChanged("SortedCheckItemCollection");
    }
}

public List<ICheckItem> SortedCheckItemCollection
{
    get
    {
        return CheckItemCollection.OrderBy(ci => ci.Name).ToList();
    }
}
 
Also consider another property that holds the selected item.
 
Snippet 2
 
ICheckItem selectedCheckItem;
public ICheckItem SelectedCheckItem
{
    get
    {
        return selectedCheckItem;
    }
    set
    {
        selectedCheckItem = value;
        NotifyPropertyChanged("SelectedCheckItem");
    }
}
 
Now if we bind the above properties to the ComboBox, initially the ComboBox will hold the list and displays the selected item correctly, provided the SelectedItem property holds a valid object(item) from the ItemsSource(IEnumerable).
 
Snippet 3

<ComboBox ItemsSource="{Binding Path=SortedCheckItemCollection}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedCheckItem, Mode=TwoWay}" />
<br />

But after that, the next time when the property change notification for "SortedCheckItemCollection" is raised(by some means), the ItemsSource is provided with a new List instance by the "get" accessor(see Snippet 1). At this point of time, the SelectedItem will become null and no item will be displayed selected in the ComboBox.
 
To overcome this issue(in my real case), I planned to go with the custom control derived from the ComboBox. In the beginning, I thought of providing a dependency property that works in sync with the SelectedItem property and make workaround for the scenario where the issue arises. But while developing code for that, I identified(by debugging) that only after the execution of "OnItemsChanged" handler, the SelectedItem is lost.
So I revamped my logic by overriding the "OnItemsChanged".
In that I'll,
  • Persist the SelectedItem locally,
  • Call the base.OnItemsChanged and
  • Then reset the SelectedItem with the locally persisted object.

Snippet 4

/// <summary>
/// PersistenceComboBox is same as ComboBox in addition will handle the SelectedItem when the ItemSource is changed.
/// Workaround for issue: ComboBox unable to handle the SelectedItem on ItemSource change. 
/// </summary>
public class PersistenceComboBox : ComboBox
{
    /// <summary>
    /// Updates the current selected item when the <see cref="P:System.Windows.Controls.ItemsControl.Items"/> collection has changed.
    /// </summary>
    /// <param name="e">Contains data about changes in the items collection.</param>
    protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        object persistenceSelectedItem = null;

        foreach (object item in this.ItemsSource)
        {
            if (item.Equals(this.SelectedItem))
            {
                persistenceSelectedItem = item;
            }
        }

        base.OnItemsChanged(e);

        if (this.SelectedItem != persistenceSelectedItem)
        {
            this.SelectedItem = persistenceSelectedItem;
        }
    }

}

So the change in the XAML(Snippet 3) will be,
 
Snippet 5 
 
<customControl:PersistenceComboBox ItemsSource="{Binding Path=SortedCheckItemCollection}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedCheckItem, Mode=TwoWay}" />
<br /> 

Now, the PersistenceComboBox(custom control) can be used in places where we require the SelectedItem to be retained even after updating the ItemsSource.
Creative Commons License
This work by Tito is licensed under a Creative Commons Attribution 3.0 Unported License.