Tag Archives: QueryDescriptor

UXGridView CTP3: Feature-complete Grid for Silverlight & WPF

I hope you’ve enjoyed the second CTP of our upcoming data controls released a couple weeks ago, which includes MVVM-ready data validation and data editing capabilities, as well as customizable editing control architecture. Make sure you also check out Andry’s blog posts covering the editing features in-depth here and here.

Today, I’m excited to announce the third CTP release of our forthcoming UXGridView control. The release includes the expected feature sets according to the CTP milestone described in my earlier post here, plus a handful of nice additions such as enhanced row details, new expander column, and a sophisticated Gmail-style column checker.

Some of the key highlights in this new CTP are discussed in the following.

QueryDescriptor Enhancement

The QueryDescriptor now includes RaiseQueryChanged() method, thanks to Werner Grift for the nice feedback in the LinkedIn group discussion. With the new method, you can programmatically raise the QueryChanged event in your ViewModel, such as when a related view has changed, or when the view needs to be refreshed due to user interaction like button click (called through the delegate command).

Improved Row Details and Hierarchical Grid

The row details feature receives major update in this new CTP release, which now works with the new expander column. The expander column contains a toggle button which is convenient for users to expand or collapse the row details at runtime, see the illustration below.

Row details with expand button

With the new expander column, it’s now possible to use the row details for more advanced business scenarios, for instances, implementing a hierarchical/nested grid such as in Customer-Orders scenario.

Another interesting scenario is the support to load the child data “on demand” based on the expanded item. UXGridView now includes a new property called ExpandedItem, making it possible for you to bind the child data on demand in the ViewModel. Both WCF RIA and DevForce samples are provided to demonstrate how to achieve such scenario.

Nested UXGridView

Notice that the paging and other settings can be applied individually in the child grid for best performance and results.

New Select Column

When I got the CTP build fresh from the lab, this particular new feature looks to be the most impressive. It’s one of my favorite features, and certainly deserves to be one of yours. Take a look at the screenshot below.

Select column with row checker

Although seems common, the select column feature is not available in any other Silverlight/WPF grids in the market – not the one with correct implementation.

The interesting point about this feature is its automatic checked items persistence capability. This means that if you check all the rows in page 1, then go to page 2, the previously checked items will remain persisted. This behavior also applies consistently in all the grid operations such as sorting and grouping. In the above example, we verified the persistence concept by binding the CheckedItems property to a list box which shows all the checked items regardless of the paged view scope.

With the feature correctly implemented along with the MVVM support, it will be useful in many scenarios in business applications – think of web-based mail applications such as Gmail or Hotmail for examples.

Deferred Scrolling Mode

Another nice feature that we managed to include in this CTP release is the deferred scrolling mode. When enabled, dragging the thumb of the vertical scrollbar will not update the view immediately. Instead, an intuitive visual hint will appear near the scrollbar indicating the target row of the scroll position. See the screenshot below for details.

Deferred scrolling with customizable visual hint

This scrolling mode is particularly useful when you have relatively large amount of rows, and also allowing users to quickly scan the information as they scroll through the viewport.

Standards-Compliant User Experiences

As in our good tradition of development, all Intersoft’s UI controls are designed with the best user experiences that conform to the ISO Standards 9241, including the focus and unfocused behavior, keyboard  navigation using arrow keys, and many more.

In this new CTP release, the UXGridView now shows a dimmed focus visual style when the control doesn’t have focus. This enables users to intuitively responding to the user interface based on the visual state presented in the application. The following illustration shows the unfocused visual state of the UXGridView.

ISO-standards UX

Download the CTP3 Bits

In summary, the CTP3 release includes nearly-complete feature sets of UXGridView planned for final release later this month. So far, the latest CTP includes full MVVM support for server data access, server paging and filtering, data validation, editing, customizable edit controls, row details, multiple selection, checker column, deferred scroll mode, flexible appearance and layout settings, and much more. The final release will include more advanced features such as data exporting, data virtualization, and an advanced column filter interface.

Click here to download the CTP3 bits and test-drive the new UXGridView features today. The download package includes latest ClientUI assemblies as well as updated and new samples for both Silverlight and WPF platforms.

Enjoy the new CTP bits! We’d love to hear what you think about the new features available in CTP3, please post your feedback, questions or issues to our community forum.

All the best,
Jimmy

UXGridView Part II: Data Access the MVVM-way with QueryDescriptor

As Jimmy wrote in his blog post, our upcoming Grid control for Silverlight and WPF, UXGridView, allows you to perform data operation with MVVM pattern elegantly using QueryDescriptor. In this post, I will explain some of fundamental concepts of the QueryDescriptor in more practical ways.

Understanding QueryDescriptor

First let’s take a look the QueryDescriptor class below.

QueryDescriptor Class

As you can see, the QueryDescriptor has three properties that hold the information about the query. It also has a QueryChanged event that will be raised when any of the QueryDescriptor’s properties are changed. The following illustration shows some examples on how query information is stored in the QueryDescriptor.

QueryDescriptor queryDescriptor = new QueryDescriptor();

// filtering
// get records that have 
// (UnitPrice >= 0 AND UnitPrice < 50) OR (UnitPrice == 0)
CompositeFilterDescriptorCollection groupFilter1 = 
    new CompositeFilterDescriptorCollection();

groupFilter1.LogicalOperator = FilterCompositionLogicalOperator.And;
groupFilter1.Add(
    new FilterDescriptor() 
    { 
        PropertyName = "UnitPrice", 
        Operator = FilterOperator.IsGreaterThanOrEqualTo, Value = 0 
    }
);
groupFilter1.Add(
    new FilterDescriptor() 
    { 
        PropertyName = "UnitPrice", 
        Operator = FilterOperator.IsLessThan, Value = 50 
    }
);

CompositeFilterDescriptorCollection groupFilter2 = 
    new CompositeFilterDescriptorCollection();

groupFilter2.LogicalOperator = FilterCompositionLogicalOperator.And;
groupFilter2.Add(
    new FilterDescriptor() 
    { 
        PropertyName = "UnitsInStock", 
        Operator = FilterOperator.IsEqualTo, Value = 0 
    }
);

queryDescriptor.FilterDescriptors.LogicalOperator = 
    FilterCompositionLogicalOperator.Or;

queryDescriptor.FilterDescriptors.Add(groupFilter1);
queryDescriptor.FilterDescriptors.Add(groupFilter2);

// paging
// get the record 6 - 10
queryDescriptor.PageDescriptor.PageSize = 5;
queryDescriptor.PageDescriptor.PageIndex = 1;

// sorting
// sort by category ascending then by product id descending
queryDescriptor.SortDescriptors.Add(
    new SortDescriptor() 
    { 
        PropertyName = "CategoryID", 
        Direction = System.ComponentModel.ListSortDirection.Ascending 
    }
);
queryDescriptor.SortDescriptors.Add(
    new SortDescriptor() 
    { 
        PropertyName = "ProductID", 
        Direction = System.ComponentModel.ListSortDirection.Descending 
    }
);    

With our data controls, the QueryDescriptor will be updated automatically whenever users perform data operations through our data controls such as paging, filtering and sorting. So all you need to do here is simply wiring up the QueryDescriptor to the data controls and listen to its QueryChanged event.

In the QueryChanged function delegate, you will need to parse the information in QueryDescriptor to a data operation command for your specific data source. Fortunately, our data provider libraries come with some methods allowing you to easily parse the QueryDescriptor into WCF RIA or DevForce data service.

Next, I will show you how to bind the QueryDescriptor to UXGridView, listen to its QueryChanged and perform the data operation.

Binding QueryDescriptor to UXGridView using MVVM Pattern

First, let’s create the ViewModel for our example.

using System.Collections.Specialized;
using Intersoft.Client.Data.ComponentModel;

namespace UXGridView.Samples.ViewModels
{
    public class ListProductsViewModel : ViewModelBase
    {
        public ListProductsViewModel()
        {
            this._queryDescriptor = new QueryDescriptor();
        }

        private INotifyCollectionChanged _products;
        private QueryDescriptor _queryDescriptor;

        public INotifyCollectionChanged Products
        {
            get { return this._products; }
            set
            {
                if (this._products != value)
                {
                    this._products = value;
                    this.OnPropertyChanged("Products");
                }
            }
        }

        public QueryDescriptor QueryDescriptor
        {
            get
            {
                return this._queryDescriptor;
            }
            set
            {
                if (this._queryDescriptor != value)
                {
                    if (this._queryDescriptor != null)
                        this._queryDescriptor.QueryChanged 
                            -= new System.EventHandler(OnQueryChanged);

                    this._queryDescriptor = value;
                    this._queryDescriptor.QueryChanged 
                        += new System.EventHandler(OnQueryChanged);

                    this.OnPropertyChanged("QueryDescriptor");
                }
            }
        }

        public virtual void LoadProducts()
        {

        }

        private void OnQueryChanged(object sender, System.EventArgs e)
        {
            this.LoadProducts();
        }
    }
}        

Notice that the LoadProducts() is still empty now, we’ll get to that later. Next we will bind this to our UXGridView in our View.

<Intersoft:UXPage
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	mc:Ignorable="d"
	xmlns:Intersoft="http://intersoft.clientui.com/schemas"
        xmlns:ViewModels="clr-namespace:UXGridView.Samples.ViewModels"
	x:Class="UXGridView.Samples.Views.UXGridView.ListProducts"
	Title="ListProducts Page"
	d:DesignWidth="640" d:DesignHeight="480">

    <Grid x:Name="LayoutRoot">
        <Grid.DataContext>
            <ViewModels:ListProductsViewModel/>
        </Grid.DataContext>

        <Grid MaxWidth="700" Margin="12">
            <Intersoft:UXGridView 
                Margin="8" AutoGenerateColumns="False" 
                ItemsSource="{Binding Products}"
                QueryOperation="Server"
                SortDescriptors="{Binding QueryDescriptor.SortDescriptors}"
                PageDescriptor="{Binding QueryDescriptor.PageDescriptor}" 
                PageSize="20" CanUserPage="True">
                <Intersoft:UXGridView.Columns>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Category ID" 
                        Binding="{Binding CategoryID}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Product ID" 
                        Binding="{Binding ProductID}" 
                        Aggregate="Count" FooterFormatString="Count = {0}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Product Name" 
                        Binding="{Binding ProductName}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Unit Price" 
                        Binding="{Binding UnitPrice}" 
                        Aggregate="Avg" FooterFormatString="Avg = {0}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Units In Stock" 
                        Binding="{Binding UnitsInStock}" 
                        Aggregate="Max" FooterFormatString="Max = {0}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Units On Order" 
                        Binding="{Binding UnitsOnOrder}" 
                        Aggregate="Min" FooterFormatString="Min = {0}"/>
                    <Intersoft:UXGridViewTextColumn 
                        Header="Quantity Per Unit" 
                        Binding="{Binding QuantityPerUnit}"/>
                </Intersoft:UXGridView.Columns>
            </Intersoft:UXGridView>
        </Grid>
    </Grid>
</Intersoft:UXPage>

Note that you will need to set the QueryOperation to Server to enable server-side data operation. That’s required because UXGridView also has the capability to manipulate the data at client side, which is the default setting. For now, let’s focus on the server-side query mode.

The QueryChanged event of the QueryDescriptor will be raised when it is bind to any of our data controls such as UXGridView, UXDataFilter or UXDataPager. The event will also be raised whenever there are changes in the QueryDescriptor, so it is the only place where you want to handle all data operations.

Now, let’s start processing this QueryDescriptor and retrieve a piece of data from our repository.

Parsing QueryDescriptor and Retrieving Data from WCF RIA

To parse the QueryDescriptor to WCF RIA, you need to include the Intersoft.Client.Data.Provider.Ria assembly in your project. This data provider assembly provides several methods that will allow you to easily parse the QueryDescriptor to the WCF RIA data service.

Now, let’s parse our QueryDescriptor and write some code to load the data in the LoadProducts() method that we’ve prepared in the previous section.

private NorthwindDomainContext _manager;
private NorthwindDomainContext Manager
{
    get
    {
        if (this._manager == null)
            this._manager = new NorthwindDomainContext();

        return this._manager;
    }
}

public virtual void LoadProducts()
{
    if (Intersoft.Client.Framework.ISControl.IsInDesignModeStatic)
        return;


    var query = this.Manager.GetProductsQuery()
                         .OrderBy(p => p.ProductID)
                         .Parse(this.QueryDescriptor);
    query.IncludeTotalCount = true;


    this.Manager.Load(
        query,
        op =>
        {
            if (op.IsComplete)
            {
                this.Products = new PagedCollectionView(op.Entities);
                if (op.TotalEntityCount != -1)
                    this.QueryDescriptor.PageDescriptor.TotalItemCount 
                        = op.TotalEntityCount;
            }
            else
            {
                // error handling
            }
        },

        true);
}

private void OnQueryChanged(object sender, System.EventArgs e)
{
    this.LoadProducts();
}
        

As you can see, the implementation is very straightforward, you just need to call .Parse(this.QueryDescriptor) to produce a query that WCF RIA can process. Note that IncludeTotalCount is set to True, which is important in paging scenarios as we will need to set the QueryDescriptor.PageDescriptor.TotalItemCount property to the total entity count of the particular query. This enables the data pager UI to determine the total number of pages available.

Parsing QueryDescriptor and Retrieving Data from Dev Force

In addition to the WCF RIA, you can also parse the QueryDescriptor to a DevForce service using similar approaches, with minor adjustments. First, you need to include the Intersoft.Client.Data.Provider.DevForce assembly in your project. This data provider provides similar methods as available in the RIA counterpart. Next, you need to adjust some of the code such as declaring the relavant DevForce type, see below.

private NorthwindEntities _manager;
private NorthwindEntities Manager
{
    get
    {
        if (this._manager == null)
            this._manager = new NorthwindEntities();

        return this._manager;
    }
}

public virtual void LoadProducts()
{
    if (Intersoft.Client.Framework.ISControl.IsInDesignModeStatic)
        return;

    this.Manager.Products
        .OrderBy(p => p.ProductID).Parse(this.QueryDescriptor)
        .ExecuteAsync(
        op =>
        {
            if (op.CompletedSuccessfully)
            {
                this.Products = new PagedCollectionView(op.Results);
            }
            else
            {
                // error handling
            }
        }
        );                 
}

public virtual void GetTotalItemCount()
{
    var queryCount = this.Manager.Products
        .Parse(this.QueryDescriptor, false).AsScalarAsync().Count();

    queryCount.Completed += (o, e) =>
    {
        if (e.Result != -1)
            this.QueryDescriptor.PageDescriptor.TotalItemCount = 
                e.Result;
    };    
}

private void OnQueryChanged(object sender, System.EventArgs e)
{
    this.GetTotalItemCount();
    this.LoadProducts();
}

Notice that you need to retrieve the total item count in two separate calls. This is required since DevForce handles the total item count retrieval differently.

I hope you agree that the QueryDescriptor makes MVVM and data service significantly easier and straightforward to implement. Also, remember that the QueryDescriptor can be shared across multiple controls such as UXDataFilter and UXDataPager, in addition to the UXGridView.

In my next post, I will cover about some nice Grid features that we shipped in the first CTP release. For now, download the CTP1 bits here, and enjoy building your data-centric application the MVVM way.

Best Regards,

Andry