Brockmann: "Creating Rich Web
Applications Gets a Ton Easier"

e-grou chooses Visual WebGui
over standard ASP.NET

Quick migration of VB 6.0
Applications to the Web

Fully functional software versions
for 30 days evaluation period

Download the free edition of the
Visual WebGui Studio
 

Code Snippets

jr365 posted on May 27, 2009 :: 1364 views

Datagrids are great for a rigid display of rows and columns, but what about flexibility? 

Here is your average datagrid:


Here is a simple example of what's possible using a flowpanel:

 

Features:

  • Databinding
  • Detail Control
  • Reflection for building detail controls
  • Base detail control

Requirements:


Notes:

For starters, this is a preliminary foray into what's possible using this technique.  This is not exhaustive, and may not be the most efficient either.  For example, the base classes and reflection usage could be refactored further, and There are not many comments in the code.

On Features:

Databinding: Both a curse and a blessing to developers.  I have one bug that is not solved yet.  Both the qty and price cells are databound to an LLBLGEN entity, and the underlying datasource (the entity) do not recieve the value on the first try.  So on the second try, it will work.

But databinding is great when it works.  You'll notice that the totals automatically update.  This calculation occurs within the LLBLGEN entity.

FlowPanel: I was trying very hard to make the TablePanel work, but the FlowPanel got it perfect on the first try.

Reflection:  What fun, we get to use reflection!  The benefit of this is that we can specify the control to be repeated inside of the flow panel by its type with a string value.

 

On requirements:

Northwind: I modified the order details table to include a comment column.  This shows of the two line order detail approach that I've implemented.

LLBLGEN: I already have some experience with this framework, so I'm going to be using it for all my data calls. True I could use datatables, table adapters, and sql connections, but then again, you could be writing html & ajax using just visual studio or notepad.  I think LLBLGEN gives cleaner code, along with the benefits of being strongly typed.  All the controls are databound to llblgen entities.

The dlls for this are already included in the demo, for your enjoyment :)

If you have ever spent time refining sql queries, perameters, filters, table adapters, not to mention a schema that is changing, you owe it to yourself to discover the amazing wonder that is LLBLGEN!

Caching: In my quick sample, each Order Detail line has a combobox with a datasource.  Caching makes sense here, as we don't want to be making multiple data calls.  I'm not going to use any dependancies, to keep this simple.

SQL Server: The LLBLGEN entities and the sql update script were built against a sql 2008 database.  I'm not sure what will happen when using this code against a 2000 or 2005 database, but hopefully no complications will arise.

 

 

On Installation:

  1. Unpack ZIP file
  2. Install Northwind Database on SQL Server 00/05/08
  3. Execute included sql script (sqlUpdateScript.sql)
  4. Modify key Main.ConnectionString in Web.Config to match your sql database
    1. if your catalog name differs from "Northwind" please see this post.
  5. Build

On Usage:

The initial form should be the OrderChooserForm.  If you click on a row, and then click button1, an order window will pop up.

The Code:

Without going into too much detail, here are the inner working.  As I noted earlier, there may be a more efficient design, this is only a starting point.  Additionally, this is a read-only design so far.

There are 3 main elements to this:

  1. The Detail Control 
  2. The layout panel hosting a collection of detail controls
  3. The main form hosting the layout panel

 

1. The Detail Control

I created a base class - GridDetailBaseControl that contains some events and an Index property.

The OrderDetailControl contains UI elements

and a minimum amount of code. 

The detail control is instantiated like this:

   27 public OrderDetailControl(OrderDetailsEntity orderDetailEntity, int index) : base(index)

   28 {

   29    InitializeComponent();

   30    productsEntityCollection = HttpContext.Current.Cache["AllProductsEntityCollection"] as EntityCollection;

   31    productComboBox.DataSource = productsEntityCollection;

   32    orderDetailBindingSource.DataSource = orderDetailEntity;

   33    fixComboboxBinding();

   34 

   35    formLoaded = true;

   36 }

For simplicity sake, I am taking in my LLBLGEN entity, as well as the index, and initializing all of my databinding at the beginning.

When the price text box or the qty text box changes, the refresh method is called which gets the row total calculation from the entity:

   44 private void refresh()

   45         {

   46                orderDetailBindingSource.EndEdit();

   47                orderDetailBindingSource.ResetCurrentItem();

   48         }

When the row total text value changes, the rowChanged event is called, which is encapsulated in the base class:

   80 private void totalTextBox_TextChanged(object sender, EventArgs e)

   81 {

   82    if (formLoaded)

   83    {

   84         OnRowChanged(sender, e);

   85    }

   86 }

The layout panel handles this event and throws it up to the main form which is able to then recalculate the order total.

The OrderDetailControl also contains a static member of its type (useful for reflection):

   24 public static string TypeName = "DetailItemsDemo.OrderDetailControl";

 

2. The Layout Panel

The layout panel is named a GridFlowPanel, which isn't the best name, but anyway, it inherits from a FlowLayoutPanel.

The controls are drawn in this method:

   33 public void DrawControls(IListSource data, Type controlType)

   34 {

   35    IList datasource = (IList)data;

   36    for (int i = 0; i < ((IList)data).Count ; i++)

   37    {

   38         // create control

   39         GridDetailBaseControl controlToAdd = (GridDetailBaseControl)Activator.CreateInstance(controlType, new object[] { datasource[i],i });

   40         // wire up rowchanged event

   41         controlToAdd.RowChanged += delegate(object sender, EventArgs e)

   42         {

   43             if (RowChanged != null)

   44                RowChanged.Invoke(sender, e);

   45         };

   46 

   47         Controls.Add(controlToAdd);

   48    }          

   49 }

This method takes in the list of the data and the type of control to create, and then loops through the list, creating a control for each line.  Each data object in the list in passed, along with the index value, to the constructor of the new control.  Note that the constructor perameters have to match your object array, or a runtime error will occur.

It's also worth noting that the rowchanged event for that detail control is wired up at this point on line 41.  So when the row throws a RowChanged event, the flowControl will Raise its own RowChanged event to the form.

There is an override that takes in a string for the control name:

   22 public void DrawControls(IListSource data, string assemblyFile, string typeName)

   23 {

   24    Type controlType;

   25 

   26    if (assemblyFile != string.Empty)

   27         controlType = Assembly.LoadFrom(assemblyFile).GetType(typeName);

   28    else

   29         controlType = Assembly.GetExecutingAssembly().GetType(typeName);

   30 

   31    DrawControls(data, controlType);

   32 }

3. Main Form

The main form does a few important things:

  1. fetch data
  2. init rows
  3. perform calculations on the fly

Fetch Data:

   68 private void FetchData(int orderNumber)

   69 {

   70    using (DataAccessAdapter adapter= new DataAccessAdapter())

   71    {

   72       CurrentEntity = (DataAccess.GetOrderEntity(orderNumber, adapter));

   73         DataAccess.GetProducts(adapter);

   74    }

   75    orderEntityCollection.Add(CurrentEntity);

   76    Text = String.Format("Sales Order: {0}", orderNumber.ToString());

   77 }

We're using LLBLGEN here to get the order entity (which also includes the customer and the order detail items), and the Products.  I should also mention that the method executed on line 73 will also cache the list of products, which is used by the OrderDetail row as the datasource for the combobox.

Here is the row initialization:

   78 public void FillTable()

   79    {        

   80          gridFlowPanel1.DrawControls(CurrentEntity.OrderDetails, OrderDetailControl.TypeName);

   81         statusBar1.Text = String.Format("{0} rows", gridFlowPanel1.Controls.Count);

   82    }

There is not much to it, just supply the list of data, and the string name of the order detail control.  We are using the executing assembly here, if you had controls in another assembly or project, you would probably have to use another overload that I haven't tested yet.

Finally, when the order total changes on a row, the RowChanged event on the flowControl is handled, and the CalculateOrder method is executed:

   58 private void CalculateOrderTotal()

   59 {

   60    decimal orderTotal = 0;

   61    for (int i = 0; i < CurrentEntity.OrderDetails.Count; i++)

   62    {

   63         orderTotal += CurrentEntity.OrderDetails[i].ExtPrice;

   64    }

   65    this.orderTotalTextBox.Text = String.Format("{0:C}",orderTotal);

   66 }

This is a benefit of LLBLGEN, you have strongly typed objects, so you can loop through the collection of bound orderdetail objects, and get your new order total.

 

 

And that's it.  Hopefully you enjoyed the article and found some illumination.  Good luck!

Download Code

Note: Codes are submitted as a .zip file to shorten your download time. After downloading it, you will need a program like Winzip to decompress it.

Terms of Agreement:
By using this code, you agree to the following terms...

  1. You may use this code in your own programs (and may compile it into a program and distribute it in compiled format for languages that allow it) freely and with no charge.
  2. You MAY NOT redistribute this code (for example to a web site) without written permission from the original author. Failure to do so is a violation of copyright laws.
  3. You may link to this code from another website, but ONLY if it is not wrapped in a frame.
  4. You will abide by any additional copyright restrictions which the author may have placed in the code or code's description.

Post Rating

Comments

# rdhatch
Wow - GREAT Work, Jr!!

-Ryan
Posted by rdhatch on Thursday, May 28, 2009 5:25 PM
# Ewans
I was thinking about do this whilst lying in bed this morning - Good work!
Posted by Ewans on Saturday, June 06, 2009 5:55 AM
# Ewans
Unfortunately I get this error

Server Error in '/' Application.
--------------------------------------------------------------------------------

Could not load file or assembly 'SD.LLBLGen.Pro.DQE.SqlServer.NET20, Version=2.6.0.0, Culture=neutral, PublicKeyToken=ca73b74ba4e3ff27' or one of its dependencies. The system cannot find the file specified
Posted by Ewans on Saturday, June 06, 2009 7:54 AM
# jr365
Hi Ewans,

I am out of the office for now, and will try to repackage the code on Monday.


Posted by jr365 on Saturday, June 06, 2009 4:54 PM
# jr365
Ok Ewans,

I've updated the zip package to include the dll that I previously forgot. However, it seems I can't update the article directly.

I've put a link to a zip file here: http://data.files.s3.amazonaws.com/development/dotnet/WebGUI/OrderDetailsDemo.zip?AWSAccessKeyId=021T7PE0P5HK29W41Y82&Expires=1245036679&Signature=3aUFnbj83mzSX2CE3%2Bgul4F46WE%3D

The link will only last 1 week, so hopefully I will have the article updated before then.
Posted by jr365 on Sunday, June 07, 2009 11:33 PM

Post Comment

Only registered users may post comments.
Most promising startups
Top 3 most promising startups in 2009
   AJAX Framework | Web Development | Cloud applications | RIA Development | Silverlight Applications | Legacy Migration
The most popular open source Ajax applications framework for enterprises | Sitemap | Terms Of Use | Privacy Statement
Copyright © 2005-2009 Visual WebGui®    Design By: Template World
   
Visual Studio Partners