PlexityHide

Home 

Products 

Downloads 

Our Shop 

Support 

Contact 

(Download this document as a PDF)

Foreword

The GTP.NET is a complete Gantt chart component package designed to help you visualize and edit time based information. Users currently use GTP.NET in applications that handle production planning, service tracking, radio commercials planning, TV planning, Satellite precision hand over, Car rental, Aircraft take off and landing slot assignment, resource planning, sales reporting, progress reporting, project planning, ERP systems, and more. Chances are that you already have used plexityHide time visualization components in one of the many software solutions created by our clients.

GTP.NET Solution overview.. 2

Introduction. 2

The database. 2

The WebService. 3

The application. 6

GUI 6

Grid data. 7

TimeItem data. 8

TimeItemLinks 9

Adding and removing grid nodes 9

Adding and removing time items 10

Adding and removing links 11

Handling row move of a time item. 12

Saving changes 13

Sum up the basics 13

Adding five minutes worth of cosmetics. 13

Grid look 13

TimeItem look 15

Time item area and time item links look 16

Schedule mode. 16

Printing. 17

Distribution. 18

Installed applications 18

ClickOnce. 18

XAML Browser Application, or XBAP. 18

ASP.NET. 18

Service oriented architecture, SOA. 18

 

GTP.NET Solution overview

Introduction

GTP.NET is runtime license free so it is ideal to include as a visualizing tool in applications that will be published in millions of copies, or for in-house limited distribution projects. You can license the binaries per developer and you can even license the source code.

This document shows a suggested baseline for a time visualization application that persists all values in a database enclosed in a web service. This document also provides an overview for the design patterns involved to use GTP.NET with other technologies like WebServices, ADO.NET, ASP.NET, XAML Browser Application, SOA and ClickOnce. In the included sample, we will build a basic project planning system and discuss different delivery scenarios for the product.

The database

The database for this sample consists of four important tables:

§         One table to hold the "project" object.

§         One to hold the grid nodes (both root nodes and child nodes) in the project.

§         One table to hold time items per grid node. And, finally,

§         One table to hold the relations, or links, between the time items.

Note that this is only a sample. When you visualize your legacy or new data you may use the same techniques but with a different/existing db-structure.

This sample uses an identity column called "id" which is marked as the primary key in all tables. Foreign keys are used to define the relations between rows in the db. It can be questioned if the use of identity columns is the best approach in an application that relies heavily on primary and foreign key relations since the keys are not determined until a row is saved. While we use identity columns, you could just as well use methods to generate system unique keys.

 

If we look at the details for the different tables they all follow the same basic pattern:

 

All tables use a primary key called "id" that is of the type "int" and specified as an "Identity" field in sqlserver.

The columns point out relations to other tables by using a foreign key of type "int" that has the value of the relating table row's primary key "id". In the image above the "project" field  is the foreign key field that points out the "id" value of a table in the project table.


The WebService

Once the database is in place we can create a WebService that will define some typed datasets that we will use later in the sample.

We now define the datasets by dragging in tables we want to use:

 

We also add select, update, insert, and delete commands in each table in the dataset:

Here we add a web method that will handout the result in form of this dataset from a query. We choose to base the query on the project id:

  [WebMethod]

  public OneProject GetOneProject(int aProjectId)

  {

    PlannedWorkTableAdapter x1=new PlannedWorkTableAdapter();

    WorkLinksTableAdapter x2=new WorkLinksTableAdapter();

    WorkNodeTableAdapter x3=new WorkNodeTableAdapter();

 

    OneProject xds=new OneProject();

 

    x1.Fill(xds.PlannedWork, aProjectId);

    x2.Fill(xds.WorkLinks, aProjectId);

    x3.Fill(xds.WorkNode, aProjectId);

 

    return xds;

  }

 

The work with the WebService proceeds and we add code for saving etc... The manifest looks like the following when we are done:


The application

GUI

Now that we have a WebService that can provide data, we can build an application that uses the data. We add some components to the form, a combobox for choosing a project, some buttons for save and print, a Gantt chart and some context menus.

In the code below, we implement the FormLoad event:

    private void Form1_Load(object sender, EventArgs e)

    {

      // Create a reference to the webservice

      data = new Service();

      data.Url = "http://plexityhide.dyndns.org:88//Project_whowhatwhen/service.asmx";

      // Get the list of available projects and set it as DataSource for the combobox

      projects=data.GetProjects();

      projectBindingSource.DataSource = projects;

     

      // Set some current dates in the Gantt date scale

      gantt1.DateScaler.StartTime=DateTime.Today.AddDays(-5);

      gantt1.DateScaler.StopTime = DateTime.Today.AddDays(5);

     

      // Set up context menus for grid and time item area in the Gantt

      gantt1.Grid.ContextMenu = contextMenuGrid;

      gantt1.TimeItemArea.ContextMenu = contextMenuTimeItemArea;

           

 

      comboBoxChooseProject_SelectedIndexChanged(null, null); // So that the first project is loaded (if any)

    }

 

    private void comboBoxChooseProject_SelectedIndexChanged(object sender, EventArgs e)

    {

      if (projects != null && comboBoxChooseProject.SelectedIndex!=-1)

      {

        // If any projects choosen...

        DataRow r=projects.Tables[0].Rows[comboBoxChooseProject.SelectedIndex];

        int projectid=(int)r["id"];

       

        if (projectid!=currentProjId) //...and it has changed since last time

          LoadProject(projectid); // Load the project

      }

    }

Grid data

We now implement the LoadProject method:

    private void LoadProject(int projid)

    {

      // CheckAskSave Current

      currentProjId=projid;

      currentProject=data.GetOneProject(projid);

      if (currentProject!=null)

      {

 

        // Set up databind for root nodes, note that currentProject.WorkNode contains

        // both root nodes and subnodes. I create a DataView to filter out the root nodes.

        // Root nodes are distinguished by their lack of foreign key value in the parentNode field.

        DataView rootview = new DataView(currentProject.WorkNode);

                                       rootview.RowFilter="ISNULL(parentNode,-1)=-1 or parentNode=-1";

        gantt1.Grid.RootNodes_DataSourceList=rootview;

 

      }

   

    }

We have also set up the single column in the grid of the Gantt in the form:

 

This takes care of the root nodes in the grid, but what about sub nodes? We need to implement an event that fires on each new node that is created and set up sub nodes databind for each of these:

    private void gantt1_OnNodeInserted(PlexityHide.GTP.Grid aGrid, PlexityHide.GTP.NodeEventArgs e)

    {

      // Node inserted, set up databind for any sub nodes

      DataRowView row = e.GridNode.ListItemWhenDataBound() as DataRowView;

      int val = (int) row["id"];

 

      DataView subNodeView = new DataView(currentProject.WorkNode);

      subNodeView.RowFilter = "parentNode=" + val.ToString();

      //Set the SubNodes to bind to the subNodeView

      e.GridNode.SubNodes_DataSourceList = subNodeView;

           

    }

The sub nodes are distinguished on their value in the parentNode field. All nodes having the current node as parent are sub nodes to the current node.

TimeItem data

So far we have the grid tree values, but we also want the time items for each node. We now add some more code to the OnNodeInserted event:

    private void gantt1_OnNodeInserted(PlexityHide.GTP.Grid aGrid, PlexityHide.GTP.NodeEventArgs e)

    {

      // Node inserted, set up databind for any sub nodes

      DataRowView row = e.GridNode.ListItemWhenDataBound() as DataRowView;

      int val = (int) row["id"];

 

      DataView subNodeView = new DataView(currentProject.WorkNode);

      subNodeView.RowFilter = "parentNode=" + val.ToString();

      //Set the SubNodes to bind to the subNodeView

      e.GridNode.SubNodes_DataSourceList = subNodeView;

            

     

      // Add databind for time items on this row

      GanttRow gr = Gantt.GanttRowFromGridNode(e.GridNode);

      gr.Layers[0].TimeItemLayout="TimeItemLook";

      gr.Layers[0].NameInDS_Identity = "id";

      gr.Layers[0].NameInDS_Start = "start";

      gr.Layers[0].NameInDS_Stop = "stop";

      DataView subNodeView_ti = new DataView(currentProject.PlannedWork);

      subNodeView_ti.RowFilter = "node=" + val.ToString();

 

      gr.Layers[0].DataSourceList = subNodeView_ti;

     

    }

The time items for a given grid node are distinguished by having the id of the work node in the node field. So we set up a dataview to catch these and bind them to first layer of the GanttRow.

TimeItemLinks

Below, we set up the databind for the TimeItemLinks in the LoadProject method:

    private void LoadProject(int projid)

    {

      // CheckAskSave Current

      currentProjId=projid;

      currentProject=data.GetOneProject(projid);

      if (currentProject!=null)

      {

 

        // Set up databind for root nodes, note that currentProject.WorkNode contains

        // both root nodes and subnodes. I create a DataView to filter out the root nodes.

        // Root nodes are distinguished by their lack of foreign key value in the parentNode field.

        DataView rootview = new DataView(currentProject.WorkNode);

                                       rootview.RowFilter="ISNULL(parentNode,-1)=-1 or parentNode=-1";

        gantt1.Grid.RootNodes_DataSourceList=rootview;

 

        // and for the time item links

        gantt1.TimeItemLinks.NameInDS_StartKey="start";

        gantt1.TimeItemLinks.NameInDS_TargetKey="target";

        gantt1.TimeItemLinks.DataSource=currentProject.WorkLinks;

      }

   

    }

A time item link points out the start and target time item by having the key of these time items in its start and target fields.

Adding and removing grid nodes

We now implement the menu items in the grid context menu:

    private void menuItem1_Click(object sender, EventArgs e)

    {

      if (currentProject != null)

      {

          OneProject.WorkNodeRow row = currentProject.WorkNode.NewWorkNodeRow();

          row.parentNode = -1;

          row.description = "<new row>";

          row.project = currentProjId;

 

          currentProject.WorkNode.AddWorkNodeRow(row);

      }

    }

 

    private void menuItemAddSubWorkItem_Click(object sender, EventArgs e)

    {

      if (currentProject != null)

      {

        if (gantt1.Grid.GridStructure.FocusedCell!=null)

        {

          DataRowView r=gantt1.Grid.GridStructure.FocusedCell.Node.ListItemWhenDataBound() as DataRowView;

          if ((int)r["id"]!=0)

          {

            OneProject.WorkNodeRow row = currentProject.WorkNode.NewWorkNodeRow();

            row.parentNode = (int)r["id"];

            row.description = "<new row>";

            row.project = currentProjId;

 

            currentProject.WorkNode.AddWorkNodeRow(row);

          }

          else

          {

            MessageBox.Show("Cannot add sub nodes until parent is saved");

          }

        }

      }

 

    }

 

    private void menuItemDeleteWorkItem_Click(object sender, EventArgs e)

    {

      if (currentProject != null)

      {

        if (gantt1.Grid.GridStructure.FocusedCell!=null)

        {

          DataRowView r=gantt1.Grid.GridStructure.FocusedCell.Node.ListItemWhenDataBound() as DataRowView;

          r.Row.Delete();

        }

      }

    }

Adding and removing time items

Adding and removing grid nodes is straight forward, but for the creation of time items we need to enter a mode where we can drag out a time item and decide the correct start and length in one swift action.

    private void menuItemAddTimeItem_Click(object sender, EventArgs e)

    {

      gantt1.EnterTimeItemCreateMode(true,null,0);

    }

 

    private void gantt1_OnTimeItem_AfterCreateByMouse(Gantt aGantt, TimeItemEventArgs e)

    {

      e.Allow=false; // We are databound so we just take the values and make a row of them

     

      int idForOwningNode=(int)(e.TimeItem.GanttRow.GridNode.ListItemWhenDataBound() as DataRowView)["id"];

      if (idForOwningNode>0)

      {

 

        OneProject.PlannedWorkRow row = currentProject.PlannedWork.NewPlannedWorkRow();

        currentProject.PlannedWork.AddPlannedWorkRow(row);

        row.followupworkamount=0;

        row.start=e.TimeItem.Start;

        row.stop=e.TimeItem.Stop;

        row.worker=-1;

        row.progress=0;

        row.node = idForOwningNode;

      }

      else

      {

        MessageBox.Show("Cannot add time items to an unsaved row");

      }

      // ok done, go back to normal mode

      gantt1.EnterTimeItemCreateMode(false, null, 0);

    }

And to remove a time item:

    private void menuItemDeleteTimeItem_Click(object sender, EventArgs e)

    {

      if (currentProject != null)

      {

        if (gantt1.FocusedTimeItem != null)

        {

          DataRowView r = gantt1.FocusedTimeItem.ListItemWhenDataBound() as DataRowView;

          r.Row.Delete();

        }

      }

    }

 

Adding and removing links

As with the creation of time items, we should be able to enter a link create mode and drag out the link from one time item to the other:

    private void menuItemAddLink_Click(object sender, EventArgs e)

    {

      gantt1.EnterLinkCreateMode(true);

    }

 

 

    private void gantt1_OnTimeItem_LinkCreate(Gantt aGantt, TimeItemEventArgs e)

    {

      e.Allow = false; // We are databound so we just take the values and make a row of them

 

      int startid=(int)(e.TimeItem.ListItemWhenDataBound() as DataRowView)["id"];

      int targetid = (int)(e.LinkTargetTimeItem.ListItemWhenDataBound() as DataRowView)["id"];

      if (startid > 0 && targetid>0)

      {

 

        OneProject.WorkLinksRow row;

        if (e.LinkUnderEdit == null)

        {

          // new link created

          row = currentProject.WorkLinks.NewWorkLinksRow();

          currentProject.WorkLinks.AddWorkLinksRow(row);         

        }

        else

        {

          // existing link changed(e.LinkUnderEdit)

          row = (e.LinkUnderEdit.ListItemWhenDataBound() as DataRowView).Row as OneProject.WorkLinksRow;           

        }

 

        row.start=startid;

        row.target=targetid;

      }

      else

      {

        MessageBox.Show("Cannot add link if not time items are saved");

      }

 

      // ok done, go back to normal mode

      gantt1.EnterLinkCreateMode(false);

    }

 

The following is the remove operation of a link:

    private void menuItemDeleteLink_Click(object sender, EventArgs e)

    {

      if (currentProject != null)

      {

        if (gantt1.TimeItemLinks.SelectedLink != null)

        {

          DataRowView r = gantt1.TimeItemLinks.SelectedLink.ListItemWhenDataBound() as DataRowView;

          r.Row.Delete();

        }

      }

    }

Handling row move of a time item

While a move of a time to a different time is merely an update of its start and stop attributes, a move to a different row is something completely different. A change of row is in our data model equivalent to a change of the owning work node for that time item. We must catch this event and react correctly:

    private void gantt1_OnTimeItem_ChangeRow(Gantt aGantt, TimeItemEventArgs e)

    {

      e.Allow=false; // Since we are databound we do not allow the Gantt to continue with the move, we change the data instead

      int idForNewNode = (int)(e.NewGanttRow.GridNode.ListItemWhenDataBound() as DataRowView)["id"];

      if (idForNewNode!=0)

      {

        // Change row is really a change of the node foreign key on the time item

        // It is actually very important to call beginEdit and endEdit here so that the row change is effectuated immediately

        (e.TimeItem.ListItemWhenDataBound() as DataRowView).BeginEdit();

        (e.TimeItem.ListItemWhenDataBound() as DataRowView)["node"] = idForNewNode;

        (e.TimeItem.ListItemWhenDataBound() as DataRowView).EndEdit();

      }

      else

      {

        MessageBox.Show("Cannot move time item to a row that is not saved");

      }          

    }

Saving changes

Let’s look at the code behind the save button:

    private void buttonSaveProject_Click(object sender, EventArgs e)

    {

      string result;

      DataSet ds=data.UpdateOneProject(currentProjId,currentProject.GetChanges(),out result);

      if (result!="")

        MessageBox.Show("Server note: "+result);

      currentProject.Merge(ds);

      currentProject.AcceptChanges();

     

    }

The only thing it does is to extract the changes from the loaded project and sends it to our WebService UpdateOneProject method. The updated rows are returned and we merge them and accept the changes.

Sum up the basics

And so we are done with the “plumbing”.

·         We have created a data bound tree with sub nodes in infinite levels.

·         We have created data bound time items for each grid node (infinite amount of time items per row).

·         We have set up data bound visual links between any two time items.

·         We can update grid node texts, time item rows, time item start and stop, and time item links.


Adding five minutes worth of cosmetics

Grid look

Each cell can have its own cell layout. You can re-use cell layouts between cells.

We choose to assign the "Parent" CellLayout name to the grid column:

We now add some code to change the layout for the child nodes:

    private void gantt1_OnNodeInserted(PlexityHide.GTP.Grid aGrid, PlexityHide.GTP.NodeEventArgs e)

    {

      // Node inserted, set up databind for any sub nodes

      DataRowView row = e.GridNode.ListItemWhenDataBound() as DataRowView;

      int val = (int) row["id"];

 

      DataView subNodeView = new DataView(currentProject.WorkNode);

      subNodeView.RowFilter = "parentNode=" + val.ToString();

      //Set the SubNodes to bind to the subNodeView

      e.GridNode.SubNodes_DataSourceList = subNodeView;

                 

     

      // Add databind for time items on this row

      GanttRow gr = Gantt.GanttRowFromGridNode(e.GridNode);

      gr.Layers[0].TimeItemLayout="TimeItemLook";

      gr.Layers[0].NameInDS_Identity = "id";

      gr.Layers[0].NameInDS_Start = "start";

      gr.Layers[0].NameInDS_Stop = "stop";

      DataView subNodeView_ti = new DataView(currentProject.PlannedWork);

      subNodeView_ti.RowFilter = "node=" + val.ToString();

 

      gr.Layers[0].DataSourceList = subNodeView_ti;

 

      // Set different layout on root nodes

      if (e.GridNode.ParentNode == null)

        gantt1.Grid.CellLayouts.ApplyNamedLayoutToGridNode("Parent", e.GridNode);

      else

        gantt1.Grid.CellLayouts.ApplyNamedLayoutToGridNode("SubNode", e.GridNode);

       

      // Let the GanttRows expand on collisions (rather than making the time items thinner)

      gr.IncreaseRowHeightOnCollision=true;

      gr.IncreaseRow_SuggestedHeightPerItem=15;

     

    }

 

TimeItem look

Time item look is controlled by TimeItemLayouts. Below, we choose to change the TimeItemLayout named "Default":

 

Time item area and time item links look

We choose to set the TimeItemArea to a white background color, and we turn on VerticalDayStripes for Saturday and Sunday.

We further choose to set the links style like this:

      gantt1.TimeItemLinks.CreationTimeItemLinkDrawStyle = TimeItemLinkDrawStyle.ZStyle;

      gantt1.TimeItemLinks.CreationTimeItemLinkStyle = TimeItemLinkStyle.StopToStart;

The result looks like this:

 

Schedule mode

The GTP.NET Gantt Chart can just as well be used a schedule component. All the information you have learned so far applies here. You just set the property ScheduleMode to true:

 

Printing

GTP.NET Gantt chart uses the .NET standard printing mechanism. We drag on a PrintPreviewDialog and a PrintDocument to the form. We connect the two, and implement the PrintDocument events:

 

    private void printDocument1_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)

    {

      gantt1.PrintInit(null);

    }

 

    private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)

    {

      bool ganttHasMorePages=false;

      gantt1.PrintPage(e.Graphics, e.MarginBounds, gantt1.GridWidth, gantt1.DateScalerHeight, ref ganttHasMorePages);

      e.HasMorePages=ganttHasMorePages;

    }

 

    private void printDocument1_EndPrint(object sender, System.Drawing.Printing.PrintEventArgs e)

    {

      gantt1.PrintEnd();

 

    }

 

Then the tool button to print does this:

    private void toolStripButton_print_Click(object sender, EventArgs e)

    {

      printPreviewDialog1.ShowDialog(this);   

    }

We get a printout that can span multiple pages and looks like this:

 

 

Distribution

Installed applications

The most straight forward way of distribution is a standard windows application (installed application). But even if the ease of it can seem attractive it raises some questions: What about updates? How can we ensure that the user runs the correct version? What about security? Will all users trust us enough to install a "black-box-bag-of-bits" on their hard drive that basically can do ANYTHING? If you want to go with the windows application distribution you should build an install script so that it easy to add, update, and remove the application for any user. GTP.NET works very well in this deployment scenario.

ClickOnce

ClickOnce addresses some of the questions raised by the installed application approach. The application is delivered by the "one" click on a web link (or a link to a CD or directory). The application is updated if needed every time the user clicks the link. It looks like this http://plexityhide.dyndns.org/ClickOnce/publish.htm. It runs "Sandboxed", which means that it is not released on its own on the machine, but rather to a process that runs the CLR and which the user can control the trust level. It will be a bit harder to create a Sandboxed application since you, as a developer, must check if you have enough trust from your user to do certain things before you do them. It could be like this: The user hits "Save", if (AmITrustedToAccessTheDiskSoThatICanSave==true) Save else Alert('I would love to save but since you dont trust me I am not allowed to'); GTP.NET is fully security aware and adapts so that it will not violate any trust level. GTP.NET works very well in this deployment scenario.

XAML Browser Application, or XBAP

Another sandboxed approach is to have your application work inside Internet Explorer. It could be a full blown application, or maybe only a really cool new component that your web designers will want to use from time to time. As of this writing, XBAP was still under development, but the older technology to embed CLR components gives the same result and it looks like this:  http://www.plexityhide.nu/IEHostedGantt/Default.htm  . You will need at least .net 2.0 and IE5 to make that link work. Now, this is starting to look very much like Java applets. The only difference is that Microsoft still needs to spread the CLR to more platforms and make it work in more browsers. GTP.NET works very well in this deployment scenario.

ASP.NET

Yet another distribution method is ASP.NET. This works by translating all communication to standard HTML. GTP.NET allows for you to produce Gantt charts that like this one: http://www.plexityhide.nu/ . One neat thing is that if you make one solution with, for example, clickOnce distribution, and you want the exact same Gantt-GUI to show up on a web page delivered by ASP.NET, you can simply hook up a Gantt_ASP component to your finished Gantt instance and you will have the exact same look. GTP.NET works very well in this deployment scenario.

 


Service oriented architecture, SOA

The following is the definition for service oriented architecture, as found on wikipedia:

" Service Oriented Architecture was first proposed by Roy W. Schulte and Yefim V. Natis who were Gartner analysts. They specified SOA as 'a style of multitier computing that helps organizations share logic and data among multiple applications and usage modes.'

 

SOA can also be regarded as a style of Information Systems architecture that enables the creation of applications that are built by combining loosely coupled and interoperable services. These services inter-operate based on a formal definition (or contract, eg. WSDL) which is independent of the underlying platform and programming language. The interface definition hides the implementation of the language-specific service. SOA-compliant systems can therefore be independent of development technologies and platforms (such as Java, .NET etc). For example, services written in C# running on .Net platforms and services written in Java running on JEE platforms can both be consumed by a common composite application. In addition, applications running on either platform can consume services running on the other as Web services, which facilitates reuse."

 

PlexityHide now offer a service for enterprise resource planning. This Service will not solve all your problems if you are running an enterprise, but it will reduce the amount of work you need to do to implement a complete ERP-system. SOA mainly focus on the server side business logic, but we can also deliver very competitive client side controls (based on GTP.NET) to visualize and enter data. What we offer will give you a SOA service that handles most of the problems associated with resource planning, such as planning for what and when your resources should be used. And follow up outcome and costs. You can then integrate other systems to the service so that all the employees handled by this service are fed to it from your existing employee register. If you find this interesting and want to know more contact us at ERPSOA@plexityHide.com.