Lesson 3 - Customize Layout of the Collection Views

  • 6 minutes to read

In this lesson, you will learn how to customize the GridControl layout in collection views. The steps below describe how to customize the OrderCollectionView from the DevExpress.OutlookInspiredApp project. Other collection views can be customized in a similar manner.

Step 1 - Remove Unnecessary Columns

Open the Views/Order/OrderCollectionView.xaml file and run the designer. The GridControl definition contains columns generated by Scaffolding Wizards. The columns are bound to all properties that are available in the corresponding data type.

outlook_tut_les3_1

<dxg:GridControl.Columns>
            <dxg:GridColumn IsSmart="True" FieldName="InvoiceNumber" />
            <dxg:GridColumn FieldName="Customer.Name" Header="Customer" ReadOnly="True" />
            <dxg:GridColumn FieldName="Store.AddressLine" Header="Store" ReadOnly="True" />
            <dxg:GridColumn IsSmart="True" FieldName="PONumber" />
            <dxg:GridColumn FieldName="Employee.FullName" Header="Employee" ReadOnly="True" />
            <dxg:GridColumn IsSmart="True" FieldName="OrderDate" />
            <dxg:GridColumn IsSmart="True" FieldName="SaleAmount" />
            <dxg:GridColumn IsSmart="True" FieldName="ShippingAmount" />
            <dxg:GridColumn IsSmart="True" FieldName="TotalAmount" />
            <dxg:GridColumn IsSmart="True" FieldName="ShipDate" />
            <dxg:GridColumn IsSmart="True" FieldName="OrderTerms" />
        </dxg:GridControl.Columns>

Let's remove the following columns to simplify the view: PONumber, Employee, SaleAmount, ShippingAmount, ShipDate and OrderTerms. To do this, select a column to remove (the column header is highlighted) and press the Delete key, or right-click a column and select the corresponding context menu item as shown below.

outlook_tut_les3_2

Step 2 - Remove the Horizontal Scroll Bar

Even though the number of columns has decreased, you can improve the column layout further. The GridControl supports the TableView.AutoWidth option. If this option is enabled, column widths are automatically calculated to fill the available space within GridControl. Open the GridControl smart tag and set the TableView.AutoWidth property to true in the View category.

outlook_tut_les3_3

Step 3 - Change Column Header Captions

To change a column header caption, select a column and use the Header property editor in the smart tag.

outlook_tut_les3_4

Do it for all columns to achieve the following layout.

outlook_tut_les3_5

Step 4 - Sorting Records

Records in the GridControl should be ordered by the InvoceNumber column. The simplest way to do this is to enable sorting for the corresponding grid column. Click the “INVOICE #” column once or twice to sort by the column in ascending or descending mode respectively. To disable sorting, press and hold the Ctrl key and click the column.

outlook_tut_les3_6

This is the fastest, but a grid-level approach to sorting records. In this tutorial, you will learn how to sort records at the data query level in view models. This is a universal method that will work with any control.

To achieve this, let's use the projections mechanism. Change the base constructor invocation in the OrderCollectionViewModel as follows:

protected OrderCollectionViewModel(IUnitOfWorkFactory<IDevAVDbUnitOfWork> unitOfWorkFactory = null)
    : base(unitOfWorkFactory ?? UnitOfWorkSource.GetUnitOfWorkFactory(), x => x.Orders, projection: query => query.OrderBy(x => x.InvoiceNumber)) {
}

The last projection parameter transforms the LINQ query used by the view model to obtain records so that records are ordered by the InvoceNumber property.

outlook_tut_les3_7

Step 5 - Optimizing the Data Query

If you scroll the GridControl through the records, you may notice some sluggishness in the UI. To diagnose this, let's review what queries to the database are performed by the OrderCollectionView module. To do this, open the DevAVDb.cs file. This file contains the DevAVDb class that is a DbContext class descendant describing the database context. Modify its constructor to log query operations.

public DevAVDb() {
        Database.Log = x => {
        if(x.StartsWith("SELECT"))
            Console.WriteLine(x);
        };
    }

Run the application, clear the Visual Studio Output window, and switch to the Sales tab. You will notice that along with the main query that fetches all records ordered by the InvoiceNumber...

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[InvoiceNumber] AS [InvoiceNumber], 
    ...
    FROM [dbo].[Orders] AS [Extent1]
    ORDER BY [Extent1].[InvoiceNumber] ASC

... there are a lot of queries like the one below.

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    ...
    FROM [dbo].[Customers] AS [Extent1]
    WHERE [Extent1].[Id] = @EntityKeyValue1
SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Location] AS [Location]
    ...
    FROM [dbo].[CustomerStores] AS [Extent1]
    WHERE [Extent1].[Id] = @EntityKeyValue1

Scrolling the grid causes more queries to be called. Let's take a look at the following grid columns within the OrderCollectionView:

<dxg:GridColumn FieldName="Customer.Name" Header="COMPANY"/>
        <dxg:GridColumn FieldName="Store.AddressCity" Header="STORE"/>

They refer to the Order.Customer and Order.Store navigation properties:

public virtual Customer Customer { get; set; }
    public virtual CustomerStore Store { get; set; }

By default, navigation properties in Entity Framework are lazy, i.e., they are loaded on demand. It means that two separate queries to the database will be performed for each row displayed in the GridControl until all Customer and CustomerStore records are fetched. This may not cause significant performance problems when developing and debugging the application with a local database server. However in the real environment, when the ping time to a server is noticeable, this may cause serious performance drawbacks. To fix this, include navigation properties into the projection query.

protected OrderCollectionViewModel(IUnitOfWorkFactory<IDevAVDbUnitOfWork> unitOfWorkFactory = null)
        : base(unitOfWorkFactory ?? UnitOfWorkSource.GetUnitOfWorkFactory(), x => x.Orders,
        projection: query => query.Include(x => x.Store).Include(x => x.Customer).OrderBy(x => x.InvoiceNumber)) {
    }

Now, only one query is performed when switching to the Sales module.

SELECT 
    [Project1].[C1] AS [C1], 
    [Project1].[Id] AS [Id], 
    ...
    FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[InvoiceNumber] AS [InvoiceNumber], 
        ...
        1 AS [C1]
        FROM [dbo].[Orders] AS [Extent1]
        LEFT OUTER JOIN [dbo].[CustomerStores] AS [Extent2] ON [Extent1].[StoreId] = [Extent2].[Id]
        LEFT OUTER JOIN [dbo].[Customers] AS [Extent3] ON [Extent1].[CustomerId] = [Extent3].[Id]
    ) AS [Project1]
    ORDER BY [Project1].[InvoiceNumber] ASC

The performance issue has been fixed. However, there is still room for improvement: a lot of unnecessary fields are fetched from the database. In the real environment, as the amount of orders grows, performance may once again degrade. This can be handled by using a separate projection type that includes only necessary properties. This approach will be described in the next lesson.

Step 6 - Customizing Other Collection View Modules

Follow the previously described steps to improve the ProductCollectionView module in the DevExpress.OutlookInspiredApp project.

The result should look like the following.

outlook_tut_les3_8

Do the same for the OrderCollectionView module in the DevExpress.HybridApp project.

outlook_tut_les3_9

Applications that contain the result of this lesson are available here: DevExpress.OutlookInspiredApp and DevExpress.HybridApp.

See Also