Intersoft ClientUI Documentation
Editing Data with UXGridView

This topic provides a comprehensive overview of data editing in UXGridView, discusses about data validation in depth, and explains how to handle the data creation, update and delete using MVVM pattern. For information about UXGridView and its features in general, see UXGridView Overview.

Enable Data Editing

Data editing in UXGridView involves three fundamental processes, Create, Update and Delete (CUD). You can enable each editing feature by setting the property CanUserAddRows, CanUserEditRows and CanUserDeleteRows respectively.

Example Title
Copy Code
<Intersoft:UXGridView CanUserAddRows="True" CanUserDeleteRows="True" CanUserEditRows="True"/>

A NewRow element will appear in the top of the UXGridView user interface when you set the CanUserAddRows property to true, such as shown in the following illustration.

In addition, you can also delete and edit the selected item by setting both the CanUserEditRows and CanUserDeleteRows properties to true.

Mouse and Keyboard Gesture

You can edit a row through a number of input devices and gestures such as using mouse or keyboard. The following list describes the configuration that you can customize to edit a record using mouse/keyboard.

Using Mouse Gesture

The mouse gesture is determined by the EditMouseGesture property. The following options are available to customize the edit mouse gesture.

Using Keyboard Gesture

The keyboard gesture is determined by several properties such as EditKeyGesture, EnterKeyAction and EditEnterKeyAction. Each property has specific action that you can customize. The following list details the action of each property.

EditKeyGesture:

EnterKeyAction:

EditEnterKeyAction:

Beside these options, you can also use several common keystrokes during editing such as listed below.

Handling the CUD operation

To handle the CUD operation, UXGridView provides several command-related properties that you can bind to your ViewModel to execute a method depending on the actions. These command-related properties are listed as follows:

You need to bind these command properties to your ViewModel and handle each of the action. Beside these command, UXGridView also provide HasChanges that you can use to determine whether there are existing changes in the data.

The following code example shows how to define and bind the editing commands from ViewModel to UXGridView.

View Model
Copy Code
using System.Collections;
using Intersoft.Client.Framework;
using Intersoft.Client.Framework.Input;
using Intersoft.Client.UI.Data;
using UXGridView.Samples.ModelServices;

namespace UXGridView.Samples.ViewModels
{
    public class ServerEditingViewModel : ViewModelBase
    {
        public ServerEditingViewModel()
            : base()
        {
            this.Manager = new NorthwindDomainContext();
            this.QueryDescriptor = new QueryDescriptor();
            
            this.ExportCommand = new DelegateCommand(ExecuteExportCommand);
            this.DeleteRowCommand = new DelegateCommand(ExecuteDeleteRow);
            this.InsertRowCommand = new DelegateCommand(ExecuteInsertRow);
            this.PrepareNewRowCommand = new DelegateCommand(ExecutePrepareNewRow);
            this.UpdateCellCommand = new DelegateCommand(ExecuteUpdateCell);
            this.UpdateRowCommand = new DelegateCommand(ExecuteUpdateRow);
            this.RejectRowCommand = new DelegateCommand(ExecuteRejectRow);
            this.RejectChangesCommand = new DelegateCommand(ExecuteRejectChanges);
            this.SaveChangesCommand = new DelegateCommand(ExecuteSaveChanges);
            this.ValidateRowCommand = new DelegateCommand(ExecuteValidateRow);
        }

        #region Fields

        private bool _hasChanges;
        private bool _isBusy;
        private bool _isRefreshed;
        private object _newProduct;
        private IEnumerable _products;
        private QueryDescriptor _queryDescriptor;
        
        #endregion

        #region Properties

        private NorthwindDomainContext Manager { get; set; }

        public IEnumerable 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");
                }
            }
        }

        #endregion

        #region Selection and Editing Properties

        public object NewProduct
        {
            get { return this._newProduct; }
            set
            {
                if (this._newProduct != value)
                {
                    this._newProduct = value;
                    this.OnPropertyChanged("NewProduct");
                }
            }
        }      

        public bool IsBusy
        {
            get { return this._isBusy; }
            set
            {
                if (this._isBusy != value)
                {
                    this._isBusy = value;
                    this.OnPropertyChanged("IsBusy");
                }
            }
        }

        public bool IsRefreshed
        {
            get { return this._isRefreshed; }
            set
            {
                if (this._isRefreshed != value)
                {
                    this._isRefreshed = value;
                    this.OnPropertyChanged("IsRefreshed");
                }
            }
        }

        public bool HasChanges
        {
            get { return _hasChanges; }
            set
            {
                if (_hasChanges != value)
                {
                    _hasChanges = value;
                    OnPropertyChanged("HasChanges");
                }
            }
        }

        #endregion

        #region Commands

        public DelegateCommand DeleteRowCommand { get; set; }
        public DelegateCommand InsertRowCommand { get; set; }
        public DelegateCommand PrepareNewRowCommand { get; set; }
        public DelegateCommand UpdateCellCommand { get; set; }
        public DelegateCommand UpdateRowCommand { get; set; }
        public DelegateCommand RejectRowCommand { get; set; }
        public DelegateCommand RejectChangesCommand { get; set; }
        public DelegateCommand SaveChangesCommand { get; set; }
        public DelegateCommand ValidateRowCommand { get; set; }

        #endregion

        #region Methods

        public void SaveChanges()
        {

        }

        public void ExecuteDeleteRow(object parameter)
        {

        }

        public void ExecuteInsertRow(object parameter)
        {

        }

        public void ExecutePrepareNewRow(object parameter)
        {
            
        }

        public void ExecuteUpdateCell(object parameter)
        {
            
        }

        public void ExecuteValidateRow(object parameter)
        {
            
        }

        public void ExecuteUpdateRow(object parameter)
        {
            
        }

        public void ExecuteRejectRow(object parameter)
        {
            
        }

        public void ExecuteRejectChanges(object parameter)
        {
            
        }

        public void ExecuteSaveChanges(object parameter)
        {
            
        }

        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 Intersoft.Client.Data.ComponentModel.PagedCollectionView(op.Entities);
                       this.QueryDescriptor.PageDescriptor.TotalItemCount = op.TotalEntityCount;
                   }
                   else
                   {
                       MessageBox.Show(op.Error.ToString());
                   }
               },

               true);
        }

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

        #endregion
    }
}
View
Copy Code
<Intersoft:UXGridView
    AutoGenerateColumns="False" QueryOperation="Server"
    CanUserPage="True" PageSize="20"
    RowHeaderVisibility="Visible"
    IsBusy="{Binding IsBusy, Mode=TwoWay}"
    IsRefreshed="{Binding IsRefreshed, Mode=TwoWay}"
    ItemsSource="{Binding Products}"
    SortDescriptors="{Binding QueryDescriptor.SortDescriptors, Mode=TwoWay}"
    PageDescriptor="{Binding QueryDescriptor.PageDescriptor}"
    GroupFootersVisibility="Visible" GroupByBoxVisibility="Visible"
    CanUserAddRows="True"
    CanUserDeleteRows="True"
    CanUserEditRows="True"
    NewItem="{Binding NewProduct, Mode=TwoWay}"
    ValidateRowCommand="{Binding ValidateRowCommand}"
    InsertRowCommand="{Binding InsertRowCommand}"
    DeleteRowCommand="{Binding DeleteRowCommand}"
    PrepareNewRowCommand="{Binding PrepareNewRowCommand}"
    UpdateCellCommand="{Binding UpdateCellCommand}"
    UpdateRowCommand="{Binding UpdateRowCommand}"
    SaveChangesCommand="{Binding SaveChangesCommand}"
    RejectRowCommand="{Binding RejectRowCommand}"
    RejectChangesCommand="{Binding RejectChangesCommand}"
    HasChanges="{Binding HasChanges}">

    <Intersoft:UXGridView.GroupDescriptors>
        <Intersoft:UXGridViewGroupDescriptor PropertyName="CategoryID"/>
    </Intersoft:UXGridView.GroupDescriptors>

    <Intersoft:UXGridView.Columns>
        <Intersoft:UXGridViewTextColumn Header="Category ID" Binding="{Binding CategoryID}"/>
        <Intersoft:UXGridViewTextColumn Header="Product ID" Binding="{Binding ProductID}"
                                            IsReadOnly="True" Aggregate="Count" FooterFormatString="Count = {0}"/>
        <Intersoft:UXGridViewTextColumn Header="Product Name" Binding="{Binding ProductName}"/>
        <Intersoft:UXGridViewTextColumn Header="Units In Stock" Binding="{Binding UnitsInStock}" 
                                            Aggregate="Max" FooterFormatString="Max = {0}"/>
        <Intersoft:UXGridViewTextColumn Header="Unit Price" Binding="{Binding UnitPrice}" 
                                            Aggregate="Avg" FooterFormatString="Avg = {0:n2}"/>
        <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>

After the commands are bound to the ViewModel, the actual CUD operations are completely delegated to you for further processing.

If you prefer to automatically update the records after each CUD operation, you can perform the server operation in the InsertRowCommand, UpdateRowCommand and DeleteRowCommand respectively, and perhaps followed with the RefreshCommand to refresh the data. However, if you prefer a batch update, you can notify the UXGridView by setting the HasChanges property to true, and later call the SaveChanges method to save all changes in one server call.

The batch update capability might not be available in all data providers such as WCF RIA. When server query is enabled in WCF RIA such as paging, sorting, and filtering; WCF RIA always override the existing changes with the fresh data from the database. This behavior is due to WCF RIA not supporting client-side caching. In this case, you may want to perform immediate update and/or refresh after each CUD operation.

Handling the CUD operation can be a tedious task in the RIA development due to the repetitive processes. ClientUI provides generic templates that you can use to quickly handle the CUD operation in efficient way. To learn more how to handle the CUD operation, see Walkthrough: Handle CUD Operation using UXGridView and WCF RIA and Walkthrough: Handle CUD Operation using UXGridView and DevForce.

The following examples show how to handle each data operation in MVVM pattern through the commands implementation.

Create Operation

To implement create operation using MVVM pattern, you handle the PrepareNewRow and InsertRow command in the ViewModel, shown in the example below.

CS
Copy Code
public void ExecuteInsertRow(object parameter)
{
    this.NewProduct = null;

    if (!this.IsBatchUpdate)
        this.SaveChanges();
    else
        this.HasChanges = true;
}

public void ExecutePrepareNewRow(object parameter)
{

    // It's possible to initialize the new row with default values
    // Example:
    // product.ProductName = "New Product";

    this.NewProduct = this.EditableProductsSource.Create();
    this.EditableProductsSource.Insert(this.NewProduct);
}    
The prepare new row is required to initializes the new item object.

Update Operation

To implement the update operation using MVVM pattern, you handle the UpdateRow command in the ViewModel, shown in the example below.

CS
Copy Code
public void ExecuteUpdateCell(object parameter)
{
    object[] updateCellParameters = (object[])parameter;
    object product = updateCellParameters.GetValue(0);
    string property = updateCellParameters.GetValue(1).ToString();

    // perform cell-level validation if required
}

public void ExecuteUpdateRow(object parameter)
{
    if (!this.IsBatchUpdate)
        this.SaveChanges();
    else
        this.HasChanges = true;
}

Delete Operation

To implement the delete operation using MVVM pattern, you handle the DeleteRow command in the ViewModel, shown in the example below.

CS
Copy Code
public void ExecuteDeleteRow(object parameter)
{
    this.EditableProductsSource.Delete(parameter as IList);

    if (!this.IsBatchUpdate)
        this.SaveChanges();
    else
        this.HasChanges = true;
}

Row Validation and Cancellation

Before the changes are committed, you may want to perform data validation to the particular changes. You can implement the data validation by handling the ValidateRow command in the ViewModel. In addition, you can also add custom logic when the row changes is cancelled by handling the RejectRow command. Both scenarios are illustrated in the example below.

CS
Copy Code
public void ExecuteValidateRow(object parameter)
{
        Product product = (Product)parameter;

        product.ValidationErrors.Clear();
                    
        if (product.CategoryID < 1 || product.CategoryID > 8)
            product.ValidationErrors.Add(new ValidationResult("Specified CategoryID is not existed", new string[] { "CategoryID" }));

        if (product.UnitPrice < 0)
            product.ValidationErrors.Add(new ValidationResult("Unit Price can not be less than 0", new string[] { "UnitPrice" }));

        if (product.UnitsInStock < 0)
            product.ValidationErrors.Add(new ValidationResult("Units in Stock can not be less than 0", new string[] { "UnitsInStock" }));

        if (product.UnitsOnOrder < 0)
            product.ValidationErrors.Add(new ValidationResult("Units on Order can not be less than 0", new string[] { "UnitsOnOrder" }));
}

public void ExecuteRejectRow(object parameter)
{
    if (parameter != null)
        this.EditableProductsSource.RejectChanges(parameter);

    this.NewProduct = null;
}

Batch Update

To implement batch update using MVVM pattern, you handle the RejectChanges and SaveChanges command such as shown in the following example.

CS
Copy Code
public void ExecuteRejectChanges(object parameter)
{
    this.EditableProductsSource.RejectChanges();
    this.HasChanges = false;
}

public void ExecuteSaveChanges(object parameter)
{
    // if users click on the save changes while the new row is being edited,
    // presume the new row isn't intended
    if (this.NewProduct != null)
        this.EditableProductsSource.RejectChanges(this.NewProduct);

    this.SaveChanges();
}

For guided step-by-step instructions to implement data operation in UXGridView, see Walkthrough: Handle CUD Operation using UXGridView and WCF RIA and Walkthrough: Handle CUD Operation using UXGridView and DevForce.

IsReadOnly and IsReadOnly Binding

You can specify whether a column is editable through the IsReadOnly property in UXGridViewColumn. In more advanced scenarios, you may also want to disable editing on certain columns based on the value of other columns. For examples, if a product has been discontinued, editing in all other columns should be disabled.

To enforce the read only setting to a certain row in this scenario, you can use the IsReadOnlyBinding that is available in both UXGridView and UXGridViewBoundColumn level. The IsReadOnlyBinding at UXGridView level is used to evaluate whether the user can edit the row, while the IsReadOnlyBinding at UXGridViewBoundColumn level is used to evaluate whether the user can edit the cell.

The following code shows how to implement the IsReadOnlyBinding to achieve the above scenario.

XAML
Copy Code
<Intersoft:UXGridView IsReadOnlyBinding="{Binding Discontinued}">
              
    <Intersoft:UXGridView.Columns>
        <Intersoft:UXGridViewCheckBoxColumn Header="Discontinued" Binding="{Binding Discontinued}"/>
        <Intersoft:UXGridViewTextColumn Header="Category ID" Binding="{Binding CategoryID}" IsReadOnlyBinding="{Binding Discontinued}"/>
        <Intersoft:UXGridViewTextColumn Header="Product ID" Binding="{Binding ProductID}" IsReadOnly="True" Aggregate="Count" FooterFormatString="Count = {0}"/>
        <Intersoft:UXGridViewTextColumn Header="Product Name" Binding="{Binding ProductName}"/>                    
    </Intersoft:UXGridView.Columns>

</Intersoft:UXGridView>

Lost Focus Action

When you lost focus from an edited row, by default UXGridView will commit the current edit operation you can change this behavior by changing the LostFocusAction property and NewRowLostFocusAction property to CancelEdit.

This setting will force UXGridView to cancel the current edit operation when the focus is lost from the edited row. NewRowLostFocusAction property determines the action for new row object, while LostFocusAction determines the action for existing rows.

New Row Position

The rows of UXGridView are determined by the number of items specified in items source. If the number of items in items source is less than the available screen estate it will show blank white space.

Setting the EmptyRowsVisibility to Visible enables UXGridView to show empty rows in this blank white space area, to give a better look and feel.

When CanUserAddRows is enabled and EmptyRowsVisibility is set to Visible, you can change the new row position to bottom through NewRowPosition property.

Note that some features are not compatible when CanUserAddRows is enabled and EmptyRowsVisibility is set to Visible such as paging and grouping.

The following are several ways to enter edit mode when the new row position is set to Bottom:

Cell Locking

Cell locking is a feature that allows you to lock all current editing cell when you need to retrieve a data synchronously after editing a cell in UXGridView.

To enforce the cell locking, you need to set SuspendTabAction property to True of UXGridViewUpdateCellInfo object that available as command parameter from UpdateCellCommand.

The following sample shows how to implement cell locking which locks the editing when it needs to retrieve certain data in asynchronous manner.

CS
Copy Code
private Dictionary<Guid, UXGridViewUpdateCellInfo> WorkingOperation
{
    get
    {
        if (_workingOperation == null)
            _workingOperation = new Dictionary<Guid, UXGridViewUpdateCellInfo>();
        return _workingOperation;
    }
}

protected override void ExecuteUpdateCell(object parameter)
{
    object[] updateCellParameters = (object[])parameter;

    UXGridViewUpdateCellInfo updateCellInfo = updateCellParameters.GetValue(2) as UXGridViewUpdateCellInfo;
    if (updateCellInfo != null)
    {
        Order_Detail orderDetail = updateCellInfo.Item as Order_Detail;
        if (orderDetail != null)
        {
            if (updateCellInfo.PropertyName == "Quantity")
            {
                updateCellInfo.SuspendTabAction = true; // suspend the action
                orderDetail.IsDiscountCellBusy = true; // notify the UXGridViewCell through IsCellBusyBinding that the cell is currently busy
                this.GetDiscountAsync(updateCellInfo); // get the data async
            }            
        }
    }
}


private void GetDiscountAsync(UXGridViewUpdateCellInfo updateCellInfo)
{
    string typeName = "ClientUI._2012R2.Preview.DomainModel.OrderDetailBusinessService, ClientUI.2012R2.Preview.DomainModel";
    string methodName = "GetDiscount";
    Guid token = Guid.NewGuid();

    this.WorkingOperation.Add(token, updateCellInfo);

    Order_Detail orderDetail = updateCellInfo.Item as Order_Detail;
    if (orderDetail != null)
        this.DataSource.Context.InvokeServerMethodAsync(typeName, methodName, OnGetDiscountAsyncCompleted, token, orderDetail.Quantity);            
}


private void OnGetDiscountAsyncCompleted(InvokeServerMethodOperation e)
{
    Guid token = (Guid)e.UserState;

    if (this.WorkingOperation.ContainsKey(token))
    {
        UXGridViewUpdateCellInfo info = this.WorkingOperation[token] as UXGridViewUpdateCellInfo;
        if (info != null)
        {
            Order_Detail orderDetail = info.Item as Order_Detail;
            orderDetail.Discount = (float)e.Result;
            orderDetail.InvalidateSubTotal();
            this.InvalidateTotal();

            this.WorkingOperation.Remove(token);
            orderDetail.IsDiscountCellBusy = false; // notify UXGridView through IsCellBusyBinding that the process is completed
            info.SuspendTabAction = false; // tells UXGridView to resume the pending action
        }
    }
}

 

See Also