Tag Archives: Editing

UXGridView Part 5: Using Custom Editing Controls

In the previous post, I’ve covered the basics of data validation and editing using UXGridView the MVVM-way. In this post, I will show you how to extend UXGridView’s editing experiences by customizing the available column types, the editing template and the editing template selector.

Defining Columns Manually

First of all, you need to set the AutoGenerateColumns property of the UXGridView control to false.

<Intersoft:UXGridView AutoGenerateColumns="False">                
</Intersoft:UXGridView>
    

Next, you specify each column manually as follows.

<Intersoft:UXGridView AutoGenerateColumns="True">
    <Intersoft:UXGridView.Columns>
        <Intersoft:UXGridViewCheckBoxColumn
            Header="Discontinued"
            Binding="{Binding Discontinued}"/>
        <Intersoft:UXGridViewComboBoxColumn 
            Header="Category ID" Width="120"
            DisplayMemberPath="CategoryName" ValueMemberPath="CategoryID" 
            Binding="{Binding CategoryID}"                                                         
            ValueListSource="{Binding Categories,
            Source={StaticResource CategoriesViewModel}}"/>
        <Intersoft:UXGridViewTextColumn 
            Header="Product ID" 
            Binding="{Binding ProductID}" 
            IsReadOnly="True"/>
        <Intersoft:UXGridViewTextColumn 
            Header="Product Name"
            Binding="{Binding ProductName}"/>
        <Intersoft:UXGridViewTextColumn 
            Header="Units In Stock" 
            Binding="{Binding UnitsInStock}"/>
        <Intersoft:UXGridViewTextColumn 
            Header="Unit Price" 
            Binding="{Binding UnitPrice}"/>
        <Intersoft:UXGridViewTextColumn 
            Header="Units On Order" 
            Binding="{Binding UnitsOnOrder}"/>
        <Intersoft:UXGridViewTextColumn 
            Header="Quantity Per Unit"
            Binding="{Binding QuantityPerUnit}"/>
    </Intersoft:UXGridView.Columns>                               
</Intersoft:UXGridView>
    

The results look like the following screenshot.

Dropdown column in UXGridView

Notice that I used three different types of column.

  • UXGridViewTextColumn

    The column has a textual representation for data display and a UXTextBox for data editing control.

  • UXGridViewCheckBoxColumn

    The column has a check box representation to display the data and a UXCheckBox for data editing control.

  • UXGridViewComboBoxColumn

    The column has a textual representation to display the data, a dropdown button to display the data list, and a UXComboBox for data editing control.

Using UXGridViewTextColumn and UXGridViewCheckBoxColumn is pretty straightforward, you just need to set the Binding and Header properties, and you’re all set. For UXGridViewComboBoxColumn, you need to specify the ValueListSource which is required for the combo box data source, along with the DisplayMemberPath and ValueMemberPath.

In this sample, I assigned the ValueListSource  for the UXGridViewComboBoxColumn from a static resource.

<Grid.Resources>
    <ViewModels:CategoriesViewModel x:Key="CategoriesViewModel"/>        
</Grid.Resources>
    

The Categories ViewModel looks like the following code.

using System.Collections.Specialized;
using Intersoft.Client.Data.ComponentModel;
using UXGridView.Samples.ModelServices;

namespace UXGridView.Samples.ViewModels
{
    public class CategoriesViewModel : ViewModelBase
    {
        public CategoriesViewModel()
        {
            this.CategoriesSource = CategoriesRepository.Instance;
            this.LoadCategories();
        }

        private INotifyCollectionChanged _Categories;

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

        public IDataRepository CategoriesSource { get; set; }

        public virtual void LoadCategories()
        {
            this.CategoriesSource.GetData
            (
                (Categories) =>
                {
                    this.Categories = new PagedCollectionView(Categories);
                },
                (error) =>
                {

                }
            );
        }
    }
}

    

Notice that the CategoriesViewModel simply retrieves the data from the CategoriesSource which used repository pattern for loosely-coupled data access. To learn more about data repository pattern, see the ProductsRepository example in my previous blog post.

Using Custom Editing Controls

In many business scenarios, you’re often asked to provide more advanced editing controls in the GridView rather than just a plain textbox, such as DateTimePicker, Slider, MaskedInput, etc. Luckily, it’s fairly easy to implement in UXGridView, thanks to the solid editing architecture. All you need to do is simply creating a data template that contains the input control of your choice, and assign it to the CellEditingTemplate property of the UXGridView.

The following examples show how to customize the editing control of each column through the provided CellEditingTemplate property.

<Intersoft:UXGridViewTextColumn 
    Header="Units In Stock" 
    Binding="{Binding UnitsInStock}">
    <Intersoft:UXGridViewTextColumn.CellEditingTemplate>
        <DataTemplate>
            <Intersoft:UXNumericUpDown 
                Maximum="9999" 
                Value="{Binding UnitsInStock, Mode=TwoWay}" 
                HorizontalAlignment="Stretch" 
                VerticalAlignment="Stretch"/>
        </DataTemplate>
    </Intersoft:UXGridViewTextColumn.CellEditingTemplate>
</Intersoft:UXGridViewTextColumn>

<Intersoft:UXGridViewTextColumn Header="Unit Price" Binding="{Binding UnitPrice}"> <Intersoft:UXGridViewTextColumn.CellEditingTemplate> <DataTemplate> <Intersoft:UXCurrencyEditor EditMask="c2" UseEditMaskAsDisplayMask="True" Value="{Binding UnitPrice, Mode=TwoWay}"/> </DataTemplate> </Intersoft:UXGridViewTextColumn.CellEditingTemplate> </Intersoft:UXGridViewTextColumn>
<Intersoft:UXGridViewTextColumn Header="Units On Order" Binding="{Binding UnitsOnOrder}"> <Intersoft:UXGridViewTextColumn.CellEditingTemplate> <DataTemplate> <Intersoft:UXSliderBar Value="{Binding UnitsOnOrder, Mode=TwoWay}" SmallChange="10" LargeChange="20" Maximum="100"/> </DataTemplate> </Intersoft:UXGridViewTextColumn.CellEditingTemplate> </Intersoft:UXGridViewTextColumn>

The following examples show how to use UXDateTimePicker to edit a date time column. Notice that you can bind the BirthDate to the column and directly specify the string format in the binding expression.

<Intersoft:UXGridViewTextColumn 
    Header="Birth Date" 
    Binding="{Binding BirthDate, StringFormat=MM/dd/yyyy}">
    <Intersoft:UXGridViewTextColumn.CellEditingTemplate>
        <DataTemplate>
            <Grid Background="White" Margin="1">
                <Intersoft:UXDateTimePicker 
                    Value="{Binding BirthDate, Mode=TwoWay}" 
                    EditMask="MM/dd/yyyy" 
                    BorderThickness="0"                                                              
                    UseEditMaskAsDisplayMask="True" 
                    VerticalAlignment="Center" 
                    HorizontalAlignment="Stretch"/>
            </Grid>
        </DataTemplate>
    </Intersoft:UXGridViewTextColumn.CellEditingTemplate>
</Intersoft:UXGridViewTextColumn>
<Intersoft:UXGridViewTemplateColumn Header="Address" Binding="{Binding Address}" Width="100"> <Intersoft:UXGridViewTemplateColumn.CellTemplate> <DataTemplate> <TextBlock TextWrapping="Wrap" Text="{Binding Address}" Margin="4"/> </DataTemplate> </Intersoft:UXGridViewTemplateColumn.CellTemplate> <Intersoft:UXGridViewTemplateColumn.CellEditingTemplate> <DataTemplate> <Intersoft:UXTextBox AcceptsReturn="True" TextWrapping="Wrap" Text="{Binding Address, Mode=TwoWay}"/> </DataTemplate> </Intersoft:UXGridViewTemplateColumn.CellEditingTemplate> </Intersoft:UXGridViewTemplateColumn>

As seen in the example above, UXGridView provides a template column named UXGridViewTemplateColumn. This column allows you to change both display and editing appearance. In the example, the UXGridViewTemplateColumn uses a wrapable TextBlock for the display, and a multi-line TextBox for data editing.

You can define literally any kind of input controls that support MVVM in the CellEditingTemplate, including our latest advanced input controls such as UXCalendar, UXDomainUpDown, UXDateTimeEditor, and so forth.

Using Cell Editing Template Selector

A rather unique scenario is where the users would like to have different editing control for the same column which depends on the data being edited. This can be achieved elegantly in UXGridView by using CellEditingTemplateSelector. It works similar to any other template selectors, so the first thing you need to do is creating a template selector class that derived from DataTemplateSelector.

using System.Windows;
using Intersoft.Client.Framework;
using Intersoft.Client.UI.Data;

namespace UXGridView.Samples.Selectors
{
    public class CellEditingSelector: DataTemplateSelector
    {
        public DataTemplate NumericEditor { get; set; }
        public DataTemplate SliderEditor { get; set; }

        public override DataTemplate SelectTemplate(object item, 
                 DependencyObject container)
        {            
            UXGridViewCell cell = container as UXGridViewCell;
            UXGridViewRowBase rowBase = cell.OwningRowBase;
            UXGridViewRow row = rowBase as UXGridViewRow;

            switch (cell.OwningColumn.PropertyName)
            {
                case "UnitsInStock":
                    if (rowBase.IsNewRow || (row != null && row.Index % 2 != 0))
                        return this.SliderEditor;
                    else
                        return this.NumericEditor;                    
            }

            return null;
        }
    }
}

The above code basically selects the editing control differently depending on whether the row is a normal or an alternating row. Notice that the conditioning is scoped to UnitsInStock column only.

Finally, let’s assign the template selector to the UXGridView, see the code below.

<Grid.Resources>
    <DataTemplate x:Key="NumericEditor">
        <Intersoft:UXNumericUpDown Maximum="100"
                   Value="{Binding UnitsInStock, Mode=TwoWay}"
                   HorizontalAlignment="Stretch"
                   VerticalAlignment="Stretch"/>
    </DataTemplate>
    <DataTemplate x:Key="SliderEditor">
        <Intersoft:UXSliderBar
                   Value="{Binding UnitsInStock, Mode=TwoWay}"
                   SmallChange="10" LargeChange="20" Maximum="100"/>
    </DataTemplate>
<Selectors:CellEditingSelector x:Key="CellEditingSelector" NumericEditor="{StaticResource NumericEditor}" SliderEditor="{StaticResource SliderEditor}"/> </Grid.Resources> <Intersoft:UXGridView CellEditingTemplateSelector= "{StaticResource CellEditingSelector}">

As the result, editing the cells of UnitsInStock for the normal row will show a UXNumericUpDown control. See the illustration below.

UXNumericUpDown for normal row editing

While editing the cells for the alternating row of the same column will show a UXSliderBar control instead.

UXSliderBar for alternate row editing

That’s all for now, I hope this post gives you some cool ideas on using the available column types and customizing the editing controls in UXGridView. All samples I used in this blog post are available in our latest CTP release. Click here to download it now and get yourself familiar with the MVVM implementation in the UXGridView, starting from the data retrieval, handling the data editing, to customizing editing controls.

Best Regards,

Andry

UXGridView Part 4: Data Editing the MVVM-way with Commands

In the previous post, Jimmy announced the latest CTP for UXGridView with the CUD (Create, Update and Delete) capability. In this post, I will provide the step-by-step instructions to work with these new features.

Enabling the CUD capability

First of all, let’s enable the CUD (Create, Update, Delete) capability in UXGridView. You can enable these features by simply setting the CanUserAdd, CanUserEdit, and CanUserDelete properties to True.

<Intersoft:UXGridView CanUserAdd="True" 
                      CanUserDelete="True" 
                      CanUserEdit="True"/>
        

Notice that there will be a NewRow element at the top of the Grid when you set the CanUserAdd property to True.

Furthermore, you can also delete or edit the selected item(s) when the CanUserEdit or CanUserDelete properties are set to True.

To edit a row you can use either mouse or keyboard. The following list shows you some configuration that you can change to edit a record using mouse / keyboard.

To edit a row using mouse:

  • EditMouseGesture
    • Single Click

      Directly begin edit to the current selected cell.

    • Second Click

      Begin edit to the current selected cell after the row is selected.

    • Double Click

      Directly begin edit to the current selected cell when double click fires.

To edit a row using keyboard:

  • EditKeyGEditKeyGesture
    • F2

      Directly begin edit to the current selected cell using F2 key.

    • Any Keystroke

      Directly begin edit to the current selected cell from any key stroke.

  • EnterKeyAction
    • EnterEdit

      Directly begin edit to the current selected cell using Enter key.

    • MoveToNextRow

      Move to next row (does not edit the cell).

  • EditEnterKeyAction
    • CommitAndMovetoNextRow

      Commit the changes at current edited row and move to the next row.

    • ExitEdit

      Exit the cell edit of the current edited cell.

    • MoveToNextEditableCell

      Move to next editable cell. (Will move to next row’s cell when it reach the end of the row)

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

  • Tab / Shift + Tab

    To move to next / previous editable cell.

  • Shift + Enter

    Commit the changes at current edited row and stay in the current selection

  • Delete

    Delete the current selected record(s)

  • Escape

    Cancel current changes. If currently you are in an active edit cell, it will cancel the cell changes. If you are in current active edit row, it will cancel the row changes.

Handling the CUD operation

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

  • PrepareNewRowCommand

    Called when you begin edit at the NewRow element. Used to initialized the NewRowItem.

  • ValidateRowCommand

    Validate the row before the row is committed.

  • InsertRowCommand

    Called when a new row is committed. You can directly save the changes and/or refresh the UXGridView if necessary.

  • UpdateCellCommand

    Called when the cell is committed.

  • UpdateRowCommand

    Called when an existing row is committed. You can directly save the changes and/or refresh the UXGridView if necessary.

  • DeleteRowCommand

    Called when a row is deleted. You can directly save the changes and / or refresh the UXGridView if necessary.

  • RejectRowCommand

    Called when the changes in the row is cancelled. This command is used to reject the changes in the data entity if required (such as in DevForce).

  • SaveChangesCommand

    Called when the save changes command is executed. You handle this command to save all the pending changes made in the UXGridView.

  • RejectChangesCommand

    Called when the reject changes command is executed. You handle this command to reject all the pending changes made in the UXGridView.

These command properties can be bound to your ViewModel using delegate command. Next, I will show you how to bind these commands along with some important properties that are necessary for the CUD operation.

Binding the CUD Commands to UXGridView using MVVM Pattern

Let’s create the ViewModel for our example. First, define the delegate commands and the required selection properties, then instantiate them in the constructor.

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 : ServerSideOperationViewModel
    {        
        public ServerEditingViewModel()
            : base()
        {            
            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 _isRefreshed;        
        private object _newProduct;
        private object _selectedProduct;
        private IEnumerable _selectedProducts;

        #endregion

        #region EditableProductsSource

        private IEditableDataRepository EditableProductsSource
        {
            get
            {
                return this.ProductsSource as IEditableDataRepository;
            }
        }

        #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 object SelectedProduct
        {
            get { return this._selectedProduct; }
            set
            {
                if (this._selectedProduct != value)
                {
                    this._selectedProduct = value;
                    this.OnPropertyChanged("SelectedProduct");
                }
            }
        }

        public IEnumerable SelectedProducts
        {
            get { return this._selectedProducts; }
            set
            {
                if (this._selectedProducts != value)
                {
                    this._selectedProducts = value;
                    this.OnPropertyChanged("SelectedProducts");
                }
            }
        }

        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
    }
}
    

Next, we will bind these commands to the UXGridView.

<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:IntersoftModel="clr-namespace:Intersoft.Client.Data.Component
Model.CollectionViews;assembly=Intersoft.Client.Data.ComponentModel" xmlns:ViewModels="clr-namespace:UXGridView.Samples.ViewModels" x:Class="UXGridView.Samples.Views.ServerSideOperation.Editing" Title="Editing Page" d:DesignWidth="1024" d:DesignHeight="800"> <Grid x:Name="LayoutRoot"> <Grid.DataContext> <ViewModels:ServerEditingViewModel/> </Grid.DataContext>
<Intersoft:DockPanel> <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" CanUserAdd="true" CanUserDelete="true" CanUserEdit="true" NewItem="{Binding NewProduct, Mode=TwoWay}" SelectedItem="{Binding SelectedProduct, 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> </Intersoft:DockPanel> </Grid> </Intersoft:UXPage>

Handling the CUD Operation in ViewModel Using DevForce

After the commands are bound to the ViewModel, it is up to you how you want to handle the CUD operation.

If you prefer to automatically update the records after each CUD operation, you can do that in the InsertRowCommand, UpdateRowCommand and DeleteRowCommand respectively, and probably followed up by 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 perform the batch update.

This batch update capability might not be available in all data providers such as WCF RIA. When you enable server query in WCF RIA such as paging, sorting, and filtering; you always get a new fresh data from the database regardless of the changes in the client. This behavior is due to the WCF RIA not supporting client-side caching. In this case, you might want to do automatic update and/or refresh after each CUD operation. There are samples that show how to do this in our CTP package.

Next, I will show you how to handle the CUD Operation in the ViewModel. To save time, I will only cover the one using DevForce which allows you to enable batch update.

Since the sample used ProductsRepository to encapsulate all data operation, I’ll show you first what I’m doing in the ProductsRepository.

using System;
using System.Collections;
using System.ComponentModel;
using IdeaBlade.EntityModel;
using IdeaBlade.Validation;
using Intersoft.Client.Data.ComponentModel;
using Intersoft.Client.Data.Provider.DevForce;
using UXGridView.Samples.Data.DevForce;

namespace UXGridView.Samples.ModelServices
{
    public class ProductsRepository : IEditableDataRepository
    {
        public ProductsRepository(NorthwindEntities entityManager)
        {
            this.Manager = entityManager;
        }

        private static IDataRepository _repository;

        public static IDataRepository Instance
        {
            get
            {
                return _repository ?? (_repository = CreateRepository());
            }
            set
            {
                _repository = value;
            }
        }

        private NorthwindEntities Manager { get; set; }

        public static IDataRepository CreateRepository()
        {
            return new ProductsRepository(EntityManager.Create());
        }

        public void GetData(Action<IEnumerable> onSuccess, 
Action<Exception> onFail) { this.Manager.Products .ExecuteAsync( op => { if (op.CompletedSuccessfully) { if (onSuccess != null) onSuccess(op.Results); } else { if (onFail != null) { op.MarkErrorAsHandled(); onFail(op.Error); } } } ); } public void GetData(QueryDescriptor queryDescriptor, Action<IEnumerable> onSuccess, Action<int> onItemCountRetrieved, Action<Exception> onFail) { this.Manager.Products.OrderBy(p => p.ProductID).Parse(queryDescriptor) .ExecuteAsync( op => { if (op.CompletedSuccessfully) { if (onSuccess != null) onSuccess(op.Results); if (onItemCountRetrieved != null) onItemCountRetrieved(-1); // not applicable; } else { if (onFail != null) { op.MarkErrorAsHandled(); onFail(op.Error); } } } ); } public void GetTotalItemCount (QueryDescriptor queryDescriptor, Action<int> onSuccess) { var op = this.Manager.Products .Parse(queryDescriptor, false).AsScalarAsync().Count(); op.Completed += (o, e) => { if (onSuccess != null) onSuccess(e.Result); }; } public void Insert(object entity) { this.Manager.AddEntity(entity); } public void Delete(IList entities) { foreach (object o in entities) { this.Delete(o); } } public void Delete(object entity) { Product product = entity as Product; product.EntityAspect.Delete(); } public void RejectChanges() { this.Manager.RejectChanges(); } public void SaveChanges( Action onSuccess, Action<Exception> onError) { this.Manager.SaveChangesAsync ( op => { if (op.IsCompleted) { if (op.HasError) { // handle error op.MarkErrorAsHandled(); onError(op.Error); } else { onSuccess(); } } }, null ); } public object Create() { return this.Manager.CreateEntity<Product>(); } public void Validate(object entity) { Product product = (Product)entity; product.EntityAspect.ValidationErrors.Clear(); product.EntityAspect.VerifierEngine.Execute(product); if (product.CategoryID < 1 || product.CategoryID > 8) product.EntityAspect.ValidationErrors .Add(new VerifierResult( VerifierResultCode.Error, "Specified CategoryID does not exist", new string[] { "CategoryID" })); if (product.UnitPrice < 0) product.EntityAspect.ValidationErrors .Add(new VerifierResult (VerifierResultCode.Error, "Unit Price can not be less than 0", new string[] { "UnitPrice" })); if (product.UnitsInStock < 0) product.EntityAspect.ValidationErrors .Add(new VerifierResult( VerifierResultCode.Error, "Units in Stock can not be less than 0", new string[] { "UnitsInStock" })); if (product.UnitsOnOrder < 0) product.EntityAspect.ValidationErrors .Add(new VerifierResult( VerifierResultCode.Error, "Units on Order can not be less than 0", new string[] { "UnitsOnOrder" })); } public void RejectChanges(object entity) { IRevertibleChangeTracking revertible =
(IRevertibleChangeTracking)entity;
revertible.RejectChanges(); } } }

Finally, let’s handle the CUD commands in our ViewModel.

For Create Operation

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);
}
    

For Update Operation

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;
}

For Delete Operation

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

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

For Validation, Reject Row and Refresh Operation

public void SaveChanges()
{
    this.IsBusy = true;

    this.EditableProductsSource.SaveChanges
    (
        () =>
        {
            this.IsRefreshed = true;
            this.LoadProducts(); // refresh
            this.HasChanges = false;
            this.IsBusy = false;
        },
        exception =>
        {
            this.IsBusy = false;

            MessagePresenter presenter = new MessagePresenter();
            presenter.ShowErrorMessage("An unexpected error has occurred: " 
+ exception.Message); } ); } public void ExecuteValidateRow(object parameter) { this.EditableProductsSource.Validate(parameter); } public void ExecuteRejectRow(object parameter) { if (parameter != null) this.EditableProductsSource.RejectChanges(parameter); this.NewProduct = null; }

For Batch Operation

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();
}

I hope this post gives you a comprehensive understanding on handling the CUD (Create, Update, Delete) operation in UXGridView using MVVM design pattern. In the next post, I will blog about customizing editing controls in UXGridView, for instance, customizing the editing control of certain columns to more advanced input controls such as UXDateTimePicker, UXNumericUpDown, UXSliderBar, and so forth.

If you have any questions regarding the CUD operation, please post some questions in our community website.

Regards,

Andry

UXGridView CTP2: MVVM Data Editing

Two weeks ago, we released the first CTP of our new data controls for Silverlight and WPF. The first CTP was focused on MVVM Grid architecture and server-based data access, as well as data operation such as paging and filtering. More information can be found here.

Today, I’m excited to announce the availability of the second CTP of our data controls. Our milestone in this release is strongly focused on data validation and data editing that supports MVVM design pattern. The release also includes a number of great features like batch update support for DevForce data service, native ValueList support for display and edit mode, customizable editing controls, and a host of UI/X tweaking.

Click here to download the CTP2, and read the details below to discover what’s new.

Data Validation & Editing with MVVM Pattern

For decades, developers have to write a bunch of code to implement an editable grid, from wiring editing events to writing validation code in a number of places. That’s even true for the latest built-in Silverlight DataGrid, for instances, you should write code to perform data validation in the RowEditEnded event. This pattern of development, unfortunately, makes your code difficult to test and maintain. And it definitely violates the MVVM pattern as the event wiring requires a strong reference to the view elements.

Unlike other Silverlight grids, UXGridView supports MVVM-style data editing out-of-the-box. It allows you to write data validation and editing logic entirely in the ViewModel without trading-off the rich editing features in the grid. The benefits of using MVVM in data editing applications are obvious – you can easily reuse, test and extend the interactions logics in the ViewModel without affecting the view or data access layers.

With UXGridView, you don’t wire events in order to perform data validation. While we still provided routed events as a mean to work with the Grid using the classic MVP pattern, it’s unnecessary to wire any editing events to perform editing operations such as begin, commit or cancel edit. Implementing data validation and editing using MVVM pattern is simple and straightforward. First, you create the delegate commands for each editing command in the ViewModel, then bind the commands to the UXGridView through data binding declaration.

The following illustration shows a simple MVVM implementation of data validation and editing in UXGridView.

ViewModel:

 public DelegateCommand DeleteRowCommand { get; set; }
 public DelegateCommand InsertRowCommand { get; set; }
 public DelegateCommand UpdateRowCommand { get; set; }
 public DelegateCommand ValidateRowCommand { get; set; }

 public void ExecuteValidateRow(object parameter)
 {
      this.EditableProductsSource.Validate(parameter);
 }

public void ExecutePrepareNewRow(object parameter)
{
    this.NewProduct = this.EditableProductsSource.Create();
    this.EditableProductsSource.Insert(this.NewProduct);
}

public void ExecuteDeleteRow(object parameter)
{
    this.EditableProductsSource.Delete(parameter as IList);
}

 ...

XAML Page:

 <Intersoft:UXGridView ItemsSource="{Binding Products}" 
              CanUserAdd="True" 
              CanUserDelete="True"
              CanUserEdit="True"
              SelectedItem="{Binding SelectedProduct, Mode=TwoWay}"
              ValidateRowCommand="{Binding ValidateRowCommand}"
              InsertRowCommand="{Binding InsertRowCommand}"
              DeleteRowCommand="{Binding DeleteRowCommand}"
              UpdateRowCommand="{Binding UpdateRowCommand}" />

As you can see from the above code snippet, the beautiful of the MVVM pattern is that you have full control over your business and interaction logic. For example, if you used a powerful data service like DevForce, you can simply delegate the call to a repository that automatically execute the validation to the given entity.

You really don’t need to worry about the change tracking, dirty state, or valid state – the UXGridView takes care of them automatically. For an instance, when the modified row contains validation errors, UXGridView will not allow you to commit the changes until you correct the error, or cancel the changes – see the screenshot below.

Intuitive row validation

In addition to the solid MVVM-ready editing architecture, UXGridView also sports great looking interface, which includes a handy error message displayed in the status bar, as well as error notification in the row and row header.

New Row & Delete

You asked for it, we deliver! The new UXGridView brings back the features you loved to use such as the “new row” interface for quick data adding, inline editing, and delete row support. While these features sound common in most ASP.NET grids, they aren’t in Silverlight, so we built one.

The "new row” interface works pretty much similar to the ASP.NET version of our flagship WebGrid. You click on the new row bar to begin adding new rows. Keyboard arrow keys are also fully supported for navigating to the new row.

New row interface

The other nice feature is the support for multiple selection and row delete. The multiple selection is enabled by default, so you can easily select multiple rows by using the combination of Ctrl and Shift keys. Once selected, press Delete to delete the selection.

Multiple selection

Batch Update Support for DevForce

If you noticed on the above screenshots close enough, chances are that you’ll spot two interesting buttons beside the pager control. Think you’ve seen these buttons before? Well, you must have seen it in the batch update feature we implemented in the ASP.NET version of our WebGrid.

If you haven’t heard much about batch update, it is a very powerful feature in data-aware applications which allows you to make multiple edits locally and submit them in a single round trip. Unfortunately, the batch update implementation in Silverlight is fairly challenging since the UXGridView control should not be tightly coupled to any data access strategy. This means that the batch update should be supported by the data service provider as well.

Luckily, DevForce from IdeaBlade includes full support for batch update, thanks to their client-side caching feature. The WCF RIA Services, on the other hands, has very limited support for batch update, you might notice the options to be disabled in several editing samples for RIA.

When the batch update feature is enabled, all changes you made in the UXGridView will be kept locally. Consequently, your implementation for the Insert, Update and Delete in the ViewModel is not required to call the Save method. This, and many other benefits, make batch update an ideal approach to your data intensive applications as data input becomes more responsive and faster with reduced server-client round trip.

The changes you made in the grid can be submitted to server in batch through the SaveChanges command; or rejected through the RejectChanges command. The UXGridView control provides default buttons in the status bar interface which execute these commands, see the illustration below.

Batch update commands

The SaveChanges and RejectChanges commands will be automatically disabled when they cannot execute. For instances, when the grid is busy, or when the grid has no more changes. Again, this process is controlled from your ViewModel through the binding to the HasChanges property in UXGridView.

You can also explore a bunch of editing behaviors implemented in the UXGridView. In the DevForce samples, you can try to use inline or batch update mode via the Options panel, see below.

Sample options

Customizable Editing Controls

Now that we’ve got MVVM-ready editing architecture covered, as well as the editing behaviors like the enter key action and edit mouse gesture, the CTP2 release also shipped with powerful editing architecture that you’ll definitely excited to see.

Unlike other grid controls that require you to write specific interface to properly work in editing mode, we introduced editing cell template that allows you to use any existing input controls as the editing control for that specific column.

One of my favorite features is the easy plug-in to use the advanced input controls we introduced in the previous release such as UXNumericUpDown, UXDateTimePicker, UXCurrencyEdit and UXSliderBar just to name a few. For an instance, the following code shows how to use the UXNumericUpDown control as the editing control for UnitsInStock column:

 <Intersoft:UXGridViewTextColumn Header="Units In Stock"
                                 Binding="{Binding UnitsInStock}">
     <Intersoft:UXGridViewTextColumn.CellEditingTemplate>
         <DataTemplate>
             <Intersoft:UXNumericUpDown Maximum="9999" 
                        Value="{Binding UnitsInStock, Mode=TwoWay}"/>
         </DataTemplate>
     </Intersoft:UXGridViewTextColumn.CellEditingTemplate>
 </Intersoft:UXGridViewTextColumn>

Using UXNumericUpDown as editing control

You can also use a more unique, non-textbox input control such as UXSliderBar, by simply defining the control in the CellEditingTemplate similar to the above example.

Using UXSliderBar as editing control

The release also includes many other exciting features such as native ValueList support on both cell and editing mode, column template, and editing template selector. More on these in the next post.

Download the CTP2 Bits

Click here to download the CTP2 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.

Make sure you checked out the new samples shipped in this CTP2 release, see the red-highlighted sections below.

New samples

Enjoy the new CTP bits! In the next several posts, we will cover more code-level details and usages on the UXGridView related to the editing features, so stay tuned.

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

All the best,

Jimmy

Introducing Intersoft WebTextEditor – Part 2

Hi all, finally I have the opportunity to write to all of you again. In this post, I’m going to write about the editing features of Intersoft WebTextEditor. In case you haven’t heard about WebTextEditor yet, please read the part 1 here.

Allright, let’s start jump into the core editing features.

WebTextEditor Editing Features

WebTextEditor is using its own editing engine that developed to support various kinds of new scenarios and to provide richer user experience. In addition to common editing scenarios such as text editing, font formatting and paragraph editing, WebTextEditor covers more advanced scenarios such as discussed in the following points.

Streamlined Cut, Copy and Paste Operation

WebTextEditor implements sophisticated clipboard management to address the limitations and different specifications of clipboard in various browsers. Based on how users invoke the clipboard saving operation such as cut and copy, WebTextEditor automatically uses the best approach to manage the clipboard data according to the browser type.
This streamlined clipboard management is crucial to ensuring smooth clipboard operation in various browsers. As such, WebTextEditor manages the clipboard data and decides when it should get the clipboard data from browser, and when it should get from editor’s internal cache.
User can perform clipboard operations through the following ways:
1. Toolbar command
Operation invoked through toolbar commands for cut and copy operation will use internal cache clipboard management.
The limitation from this approach is that user cannot paste the clipboard data from editor to other place except to the editor itself.
2. Keyboard shortcut
Operation invoked through keyboard shortcut for cut and copy operation will use browser default clipboard management.
When using keyboard shortcut, WebTextEditor will perform both saving mechanism at the same time when cut or copy operation is invoked, this can be achieved by setting EnableKeyboardShortcut property to true.
The purpose of this clipboard management is tightly integrated with paste operation. In paste operation, internal editor’s cache of clipboard data will become the first priority to be pasted. If editor’s internal cache is empty, WebTexteditor will obtain data from browser’s default clipboard.
WebTextEditor also implements automatic sliding expiration for its internal clipboard cache to keep the clipboard data concurrent and latest.

WebTextEditor implements sophisticated clipboard management to address the limitations and different specifications of clipboard in various browsers. Based on how users invoke the clipboard saving operation such as cut and copy, WebTextEditor automatically uses the best approach to manage the clipboard data according to the browser type.

This streamlined clipboard management is crucial to ensuring smooth clipboard operation in various browsers. As such, WebTextEditor manages the clipboard data and decides when it should get the clipboard data from browser, and when it should get from editor’s internal cache.

User can perform clipboard operations through the following ways:

  1. Toolbar command
    Operation invoked through toolbar commands for cut and copy operation will use internal cache clipboard management. The limitation from this approach is that user cannot paste the clipboard data from editor to other place except to the editor itself.
  2. Keyboard shortcut
    Operation invoked through keyboard shortcut for cut and copy operation will use browser default clipboard management. When using keyboard shortcut, WebTextEditor will perform both saving mechanism at the same time when cut or copy operation is invoked, this can be achieved by setting EnableKeyboardShortcut property to true.The purpose of this clipboard management is tightly integrated with paste operation. In paste operation, internal editor’s cache of clipboard data will become the first priority to be pasted. If editor’s internal cache is empty, WebTexteditor will obtain data from browser’s default clipboard.

    WebTextEditor also implements automatic sliding expiration for its internal clipboard cache to keep the clipboard data concurrent and latest.

Multiple Undo and Redo

WebTextEditor includes multiple undo and redo feature. It is unique in the way every action is logged with user-friendly message to a maximum of 20 stacks using FIFO (First-in-first-out) implementation. Each logged action can be found in Undo and Redo list, by clicking on the dropdown arrow of respective tool command.

UndoRedo

WebTextEditor logs every action that cause changes to the editor, including typing. You can customize how fast WebTextEditor should log typing action. Simply set the provided UndoLatency property to an integer value measured in milliseconds. The default value is set to 1000(ms), which means every typing action will be logged on every 1000ms after the last stop.

Insert Link

WebTextEditor provides a new user interface to insert link, which is focused on simplicity and efficiency. Link insertion in WebTextEditor is displayed with lightweight, elegant callout. In the callout, user can easily specify the text of link, URL and other input.

InsertLink

If there is a text selection when user clicked on “Insert Link” command, the selected text will be automatically set as the text of the link.

Insert Image

WebTextEditor introduces new ways to work with images and provides more flexibility for user to insert images. User can insert images from callout interface or Media Gallery pane.

Task pane is an independent visual element on sidebar panel that displays related contents and editing actions which significantly improves overall user experience. It enables users to perform editing tasks more easily and conveniently in a single interface.

Image insertion through callout can be done by clicking on “Insert from Web” command in the toolbar.

InsertImage

Edit Image

Every time an image is added to WebTextEditor, an image detail callout will appear near the added image. User can input the details of the image in this callout.

There are several settings of image that can be customized:

  1. Media Title
    Specify title to the image.
  2. Media Caption
    Specify caption to the image. This will be reflected if the image is using frame.
  3. Link URL
    Specify link URL of the image.
  4. Caption Alignment
    Specify caption alignment of the image. This will be reflected if the image is using frame.
  5. Size
    Specify the size of image.
  6. UseFrame
    Specify whether the image should be placed in a frame.
EditImage

Insert Media

Beside images, user can also insert other media type in WebTextEditor such as audios, videos, flash video and even YouTube video. Several types of media file that supported by WebTextEditor are .wav, .wma, .wmv, .avi, .mov, .mpeg and .mpg.

These media can also be inserted using callout and Media Gallery pane.

Insert Symbol

There are 20 predefined symbols in WebTextEditor; user can insert the symbol to editor with Symbol pop up. Simply click on the “Insert Symbol” command on the toolbar, and choose the symbol to be inserted from the symbol popup.
InsertSymbol

Insert Table

WebTextEditor introduces new ways to work with table. User can insert table through intuitive Insert Table pop up similar to Microsoft Word, or through Insert Table task pane for more advanced table design task.

InsertTable

Edit Table

WebTextEditor also provides comprehensive table editing commands, such as listed below.

  1. Insert columns to the left
    Add new column to the left of selected cell.
  2. Insert columns to the right
    Add new column to the right of selected cell.
  3. Delete columns
    Delete selected column.
  4. Insert Rows Above
    Add new row to the above of selected cell.
  5. Insert Rows Below
    Add new row to the below of selected cell.
  6. Delete Rows
    Delete selected row.
  7. Merge Cells Down
    Merge selected cell with the cell positioned below it.
  8. Merge Cells Right
    Merge selected cell with the cell positioned at the right.
  9. Split Cells Vertical
    Split selected cell vertically.
  10. Split Cells Horizontal
    Split selected cell horizontally
EditTable

Edit Cell Properties

WebTextEditor provides more convenient way to edit cell properties in Table Designer Task Pane. The following are the cell properties that can be modified:

  1. Horizontal Align
  2. Vertical Align
  3. Width
  4. Height
  5. Back color
  6. Font color
  7. No wrap

Find

WebTextEditor’s unique search feature allows user to search in both design and html view. As in other commands, the Find command displays lightweight callout interface to provide a better searching experience. When a match is found, the text will be selected.

Find

Replace

Similar to Find feature, user can perform replace operation in both design view and html view. The replace function includes match case option, and also provides action to perform find, replace and replace all.

Replace

Word and Character Count

Word count is enabled by default, while character count is disabled. To enable both word and character count, just set ShowWordInformation and ShowCharacterInformation properties to true.

Character count information and other details are available in the word information tooltip, thus making efficient use of screen real estate.

WordAndCharacterCount

Html Inspector

WebTextEditor provides a capability to recognize selected text’s html tag and its complete path. This information is displayed in the footer row. User can enable this feature by setting EnableHTMLInspector property to true.
Each html tag that displayed in HTML Inspector is clickable. When clicked, WebTextEditor will create a selection based on the clicked html tag.
HtmlInspector

Editing in html view

User can perform basic text editing operation such as bold, italic, insert image, etc with just a single click on html toolbar. This simplifies text editing operation in html view as the html command automatically insert and close the tags, minimizing required typing effort.

edithtml

That’s for now. This post covered only small parts of Intersoft WebTextEditor capabilities, there are still a lot of exciting features that have not been revealed yet due to limited space here. I encourage you to download and try WebTextEditor for yourself. Experiencing WebTextEditor directly is the best way to delve into more details, such as its look and feel and how its user experiences differ from others.

WebTextEditor is still in beta version when this post is published, however you can expect the final release very soon. For beta version, please download it here.

Kind regards,

Budianto.