Telerik blogs

After we prepared our data service it is time to put some client UI into it. We will use 2 grids and a context menu.

To start with, add a SilverLight application in your solution:

SL1

In the process of adding you will have to setup where the SilverLight will be hosted:

SL2

This is also doable from the settings of the web project: invoke the context menu on your web site project then go to Property Pages > SilverLight Applications > New… and add/setup the SilverLight project.

SL3

After we successfully have made the setup, we can start enhancing our SilverLight application.
Now let’s start tweaking our UI!
The Grid that will hold our orders is defined like this:

<Controls:RadGridView x:Name="RadGridView1" Height="400" SelectionChanged="RadGridView_SelectionChanged" ColumnsWidthMode="Auto" AutoGenerateColumns="False" BorderBrush="#FF401000" BorderThickness="5" Margin="0,10,0,5" AddingNewDataItem="RadGridView1AddingNewDataItem" RowEditEnded="RadGridView1_RowEditEnded" CellEditEnded="RadGridView1_CellEditEnded" >
               
<Controls:RadGridView.Columns>
                   
<gridview:GridViewDataColumn HeaderText="OrderID" UniqueName="OrderID" IsReadOnly="True" />
                    <
gridview:GridViewDataColumn HeaderText="ShipName" UniqueName="ShipName" />
                    <
gridview:GridViewDataColumn HeaderText="ShipAddress" UniqueName="ShipAddress" />
                    <
gridview:GridViewDataColumn HeaderText="ShipCity" UniqueName="ShipCity" />
                    <
gridview:GridViewDataColumn HeaderText="ShipCountry" UniqueName="ShipCountry" />
                    <
gridview:GridViewDataColumn HeaderText="ShipPostalCode" UniqueName="ShipPostalCode" />
                </
Controls:RadGridView.Columns>
           
</Controls:RadGridView>
 
And the one holding the details is defined like this:
<Controls:RadGridView x:Name="RadGridView2" Height="250" ColumnsWidthMode="Auto" AutoGenerateColumns="False" CellEditEnded="RadGridView_CellEditEnded" AddingNewDataItem="RadGridView2AddingNewDataItem" RowEditEnded="RadGridView2_RowEditEnded" BorderBrush="#FF401000" BorderThickness="5" Margin="0,10,0,5" >
               
<Controls:RadGridView.Columns>
                   
<gridview:GridViewDataColumn HeaderText="OrderID" UniqueName="OrderID" IsReadOnly="True" />
                    <
gridview:GridViewDataColumn HeaderText="ProductID" UniqueName="ProductID" />
                    <
gridview:GridViewDataColumn HeaderText="Quantity" UniqueName="Quantity" />
                    <
gridview:GridViewDataColumn HeaderText="UnitPrice" UniqueName="UnitPrice" />
                    <
gridview:GridViewDataColumn HeaderText="Discount" UniqueName="Discount" />
                </
Controls:RadGridView.Columns>
           
</Controls:RadGridView>

Now let’s have a look in the code behind:
First we need initialize the data service context in the constructor. We will also subscribe the RowLoaded events of each grid to a custom handler that will provide each grid with nice context menu.

 

// Create the Service class specifying the location of the ADO.NET Data Services 
            this.context = new OADataContext(new Uri("OADataService.svc", UriKind.Relative));
           
// We use the Async pattern (BeginExecute/EndExecute) everywhere because ot the disconected nature of the application.
            this.context.BeginExecute<Order>(new Uri("Orders", UriKind.Relative), this.GetOrdersCallback, this.context);
           
RadGridView1.RowLoaded += new EventHandler<RowLoadedEventArgs>(RadGridView1_RowLoaded);
           
RadGridView2.RowLoaded += new EventHandler<RowLoadedEventArgs>(RadGridView2_RowLoaded);

What you really have to understand is that we always use asynchronous way of working with data in SilverLight, because of the nature of the data services that we use. The real job is done in the callback method that gets executed when the service logic finishes processing the communication part:

private void GetOrdersCallback(IAsyncResult asyncResult)
       
{           
           
var ctx = (DataServiceContext)asyncResult.AsyncState;
           
this.orders = new ObservableCollection<Order>();
           
foreach (Order ord in ctx.EndExecute<Order>(asyncResult))
           
{                
               
this.orders.Add(ord);               
           
}
           
this.RadGridView1.ItemsSource = this.orders;
       
} 

Now lets handle the context menu clicking. Firstly we need to know which item was clicked. Here is where the menuOpened method kicks in:

void OnMenu1Opened(object sender, RoutedEventArgs e)
       
{
           
GridViewRow row = ((RadRoutedEventArgs)e).OriginalSource as GridViewRow;
           
if (row != null)
           
{
               
row.IsSelected = row.IsCurrent =
                    
((GridViewCell)row.Cells[0]).IsCurrent = true;

           
}
       
}

This will let us know for which row the context menu was clicked. And here are the cases of the context menu and what happens when you click an item:

void OnMenuItem1Click(object sender, RoutedEventArgs e)
       
{
           
RadContextMenu menu = (RadContextMenu)sender;
           
RadMenuItem clickedItem = ((RadRoutedEventArgs)e).OriginalSource as RadMenuItem;
           
GridViewRow row = (GridViewRow)menu.UIElement;


           
if (clickedItem != null)
           
{
               
string header = Convert.ToString(clickedItem.Header);

               
switch (header)
               
{
                   
case "Add":
                       
RadGridView1.BeginInsert();
                       
break;
                   
case "Edit":
                       
RadGridView1.BeginEdit();
                       
break;
                   
case "Delete":
                       
{
                           
DeleteOrder((Order)row.DataContext);
                           
break;
                       
}
                   
default:
                       
break;
               
}
           
}
       
}

 

The same menu is implemented for the OrderDetails grid as well. Now as you can see When you click Add on your context menu the BeginInsert method of the corresponding grid is called. Here is the code that will handle the inserting:

private void RadGridView1AddingNewDataItem(object sender, GridViewAddingNewEventArgs e)
       
{
           
e.NewObject = new Order();
       
}

This will cause the grid to insert a new row with the corresponding fields and let you insert some data. When you are done with the inserting the RowEditEnded event will be fired where we will persist our changes.

private void OnRadGridView1RowEditEnded(object sender, RecordRoutedEventArgs e)
       
{
           
DataRecord record = (DataRecord)e.Record;
           
Order ord = (Order)record.Data;

           
// if the record is disconnected we add the new item to the collection that we use as data source
            if (record.IsDisconnected)
           
{
               
this.orders.Add(ord);
               
this.context.AddToOrders(ord);
               
this.context.BeginSaveChanges(SaveChangesOptions.ContinueOnError, this.OnSaveAllComplete, null);
           
}
           
else
           
{
               
this.context.UpdateObject(ord);
               
this.context.BeginSaveChanges(SaveChangesOptions.ContinueOnError, this.OnSaveAllComplete, null);
           
}
       
}

And that’s all that you need to handle inserts.
Having the insert functionality baked and ready, gives us the oportunity to implement the other parts of CUD:  the delete operations. Remember the collection that we added earlier when reverse mapping the classes? Let’s go to the Order class in the Model class library. Open the file containing the private declarations of the field and add [Telerik.OpenAccess.Depend()] attribtue above the OrderDetails field. That’s all you need for cascade deleting. Now OpenAccess will know that when ever you delete an Order all corresponding details should be deleted as well. And now the code! As you can see from the context menu, when you click on the delete button in the context menu the DeleteOrder method is called:

 

private void DeleteOrder(Order ord)
       
{
           
this.context.DeleteObject(ord);
           
this.context.BeginSaveChanges(SaveChangesOptions.ContinueOnError, this.OnSaveAllComplete, null);
           
this.orders.Remove(ord);          
           
this.RadGridView2.ItemsSource = null;
       
}

And that’s all you need for deleting. It is really nice, isn’t it? :)
Now lets enable editing of cells in the grid. As you can see for every grid there is aCellEditEnded eventhandler method implemented. Lets look at the Orders grid implementation(it’s the same for OrderDetails just the Selected Item is cast to OrderDetail instead):

 

private void RadGridView1_CellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
       
{
           
RadGridView grid = (RadGridView)e.Source;
           
Order ord = (Order)grid.SelectedItem;            
           
this.context.UpdateObject(ord);
           
this.context.BeginSaveChanges(SaveChangesOptions.ContinueOnError, this.OnSaveAllComplete, null);
       
}

Finally we need a way to populate the details grid on selecting any order. This is as simple as it sounds. We just need to handle the Orders grid selection changing event:

private void RadGridView_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangeEventArgs e)
       
{
           
if (RadGridView1.SelectedRecords.Count > 0)
           
{
               
Order selectedOrd = (Order)((DataRecord)this.RadGridView1.SelectedRecords[0]).Data;
               
this.context.BeginExecute<OrderDetail>(new Uri(@"OrderDetails?$filter=OrderID eq "+selectedOrd.OrderID, UriKind.Relative), this.GetOrderDetailsCallBack, this.context);
           
}
       
}

Every time you click on a different Item in the first grid a new asynchronous call will be made that will retrieve all order details based on the selected OrderID. Basically our dataservice gives us back the required resources based on the URI we provide. In our case we provide OrderDetails?$filter=OrderID eq "+selectedOrd.OrderID. If we would to translate this to something human-readable it will mean exactly “Give me from the OrderDetails extent every OrderDetail that has field OrderID that is equal to selectedOrd.OrderID”. Finally we need a method that will be executed when the communication with the dataservices ends:

private void GetOrderDetailsCallBack(IAsyncResult asyncResult)
       
{
           
if (RadGridView1.SelectedRecords.Count > 0)
           
{                
               
var ctx = (DataServiceContext)asyncResult.AsyncState;
               
this.orderDetails = new ObservableCollection<OrderDetail>();
               
foreach (OrderDetail ord in ctx.EndExecute<OrderDetail>(asyncResult))
               
{                   
                 
this.orderDetails.Add(ord);                    
               
}
               
this.RadGridView2.ItemsSource = this.orderDetails;
           
}
       
}
And that’s all. It might look a little scary at first but its quite easy when you get used to it! Enjoy!
 
This is part III of the series. if you want to read more about making the model for this application you should refer to part I. If you have missed the implementation on the server side than have a look at part II of this blog post series.
The complete source code of this example is available in both C# and VB.

Comments

Comments are disabled in preview mode.