PlexityHide

Home 

Products 

Downloads 

Our Shop 

Support 

Contact 


Saturday, June 11, 2011

BlockedTime-Extension a.k.a “NoWorkTime-Extension”

 

Background

A plan may contain spans of time where no work can be done. When putting activities inside such spans, or near such spans so that they extend into the span, the activity should be paused and continue after the span.

Requirements

Implementation

1

- For each TimeItemLayout you can associate a list of no work time intervals, i.e. a "no work calendar".

A new property “Calendars” has been added to Gantt. Calenders contains zero to many Calender objects. A Calendar objects contains zero to many CalendarEntry objects.

clip_image002

2

- The list can handle "one-off" and repeated intervals.

Each CalendarEntry has a Start and Stop date. CalenderEntries may also have a Frequency to handle repetitive intervals.

3

- If time items use a TimeItemLayout with a "no work calendar", the effective Stop will be calculated from the given length and contents of the "no work calendar" in such a way that the no work intervals that fall within the period from Start to Start+Length are added to the effective stop. This is done repeatedly until no no-work-intervals in the span from Start to effective Stop are missed.

clip_image004

In the picture above the TimeItem uses a TimeItemLayout that has a BlockedTimeCalendar set. The TimeItemLayout also has BlockedTimeCalculatedStop set to true; this gives the effect that the CalenderEntries that intersect with the TimeItem are added to the Stop property of the TimeItem.

In code you can go like this:

TimeItemLayout til_wta=gantt1.TimeItemLayouts.GetFromName("WorkTimeAware");

til_wta.BlockedTimeCalculatedStop=true;

til_wta.BlockedTimeCalendar = gantt1.Calendars[0];

4

- If a time item that uses a no work calendar is resized by drag of the Stop time, the resulting Length will be updated with the Value of Stop minus Start minus "all non working intervals between"

Implemented

5

- If a time item that uses a no work calendar is moved or resized by dragging the start end, the effective Stop will be recalculated.

Implemented

6

- If a time item that uses a no work calendar is moved or resized by dragging the start end in such a way that the Start time ends up in a "no work calendar" interval an action is triggered so that the developer can choose to move the whole time item to the end of the interval, or leave it to allow for the “no work calendar”-logic to extend the effective end to the far side of the interval.

A new event was added to handle this:

gantt1.OnTimeItem_StartMovedIntoBlockedTime += new TimeItemEvent(gantt1_OnTimeItem_StartMovedIntoNoWorkTime);

void gantt1_OnTimeItem_StartMovedIntoNoWorkTime(Gantt aGantt, TimeItemEventArgs e)

{

e.Allow=false;

}

Setting e.Allow=false like this will make the TimeItem “jump” to the end of the CalendarEntry.

7

- The areas for one single time item that falls within the intervals in the no-work-calendar can be reached from the time item.

clip_image006

As shown in the picture above the BlockingCalendarEntries for one time item can be reached from the TimeItem.

8

- The areas for one single time item that falls within the intervals in the no-work-calendar can be visualized by special drawing in the time item.

By setting the property BlockedTimeShow

clip_image008

clip_image010

You can toggle the visualization of the blocked time inside the TimeItem.

9

-The “no work calendar” intervals can be visualized in the GanttRow background like it is done in MS-project where a hatched band from top to bottom shows the non working time.

By setting the properties DrawnInDateScaler and DrawnInTimeItemArea in the CalendarEntry you control if the CalendarEntries show or not:

clip_image012 clip_image014


Saturday, October 2, 2010

How to create alternate rows in Gantt for Silverlight and WPF

image
   1: <local:BackgroundConverter x:Key="myConverter"/>
 
   1: <l:GanttRow Grid.Row="1"  ItemTemplate="{StaticResource TimeItemPres_blue_schedule}"  
   2:    ItemsSource="{Binding Path=Items}" 
   3:    Background="{Binding  Converter={StaticResource myConverter}}" ></l:GanttRow>

The Background is bound to “DataContext” that will be whatever the GanttRow represents – I add a Converter that can take my – in this case – DemoDataRow and turn it into a Brush:

   1: public sealed class BackgroundConverter : IValueConverter
   2: {
   3:  
   4:     #region IValueConverter Members
   5:  
   6:     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   7:     {
   8:         if (value is DemoDataRow)
   9:         {
  10:             LinearGradientBrush b=new LinearGradientBrush();
  11:             GradientStop gs1 = new GradientStop();
  12:             GradientStop gs2 = new GradientStop();
  13:             gs1.Color = Colors.Orange;
  14:             gs1.Offset = 0;
  15:             gs2.Color = Colors.Magenta;
  16:             gs2.Offset = 1;
  17:             
  18:             if ((value as DemoDataRow).Index%2==0)
  19:             {
  20:                 b.GradientStops.Add(gs1);
  21:                 b.GradientStops.Add(gs2);
  22:              
  23:                 return b;
  24:             }
  25:             else
  26:             {
  27:                 gs1.Offset=1;
  28:                 gs2.Offset=0;
  29:                 b.GradientStops.Add(gs1);
  30:                 b.GradientStops.Add(gs2);
  31:                 return b;
  32:             }
  33:                 
  34:         }
  35:         return new SolidColorBrush(Colors.Red);
  36:         
  37:     
  38:     }
  39:  
  40:     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  41:     {
  42:         throw new NotImplementedException();
  43:     }
  44:  
  45:     #endregion
  46: }

Gantt for Silverlight and WPF

The GTP.NET.SL enables you to easily add time dependant stuff in the background or foreground of your TimeItemArea.

As our Gantt is flexible enough to integrate with any grid or treeview we need a generic way to help you do draw things in the area that belongs to the Grid or Tree but resides under the DateScaler:

Silverligth and WPF Gantt chart

We did this by exposing two events:

   1: /// <summary>
   2: /// Overridable for advanced use, public for technical reasons
   3: /// </summary>
   4: public virtual void UpdateBackAndForegroundContent()
   5: {
   6:     if (VerticalDayStripesUse != VerticalDayStripesUse.none)
   7:         DrawVerticalDayStripes();
   8:     if (TodayLineUse)
   9:         TodayLine();
  10:     if (OnDrawBackground != null)
  11:         OnDrawBackground(this, new GanttUserDrawArgs() { Canvas = _userBackgroundCanvas });
  12:     if (OnDrawForeground != null)
  13:         OnDrawForeground(this, new GanttUserDrawArgs() { Canvas = _userForegroundCanvas });
  14: }

You can from the internal code above see that the exact same way that we implement the TodayLine and the VerticalDayStripes we also call the OnDrawBackground and OnDrawForeground events.

This is how we implement the today line:

   1: Line l = new Line();
   2: _todayLineCanvas.Children.Clear();
   3: _todayLineCanvas.Children.Add(l);
   4: l.Fill = _todayLineStroke;
   5: l.Stroke = _todayLineStroke;
   6: l.StrokeThickness = TodayLineStrokeThickness;
   7: double pos = DateScaler.TimeToPixel(DateTime.Now);
   8: if (ScheduleMode)
   9: {
  10:     l.Y1 = pos;
  11:     l.Y2 = pos;
  12:     l.X1 = 0;
  13:     l.X2 = ActualWidth;
  14: }
  15: else
  16: {
  17:     l.X1 = pos;
  18:     l.X2 = pos;
  19:     l.Y1 = 0;
  20:     l.Y2 = ActualHeight;
  21: }

Notice that the call to DateScaler.TimeToPixel easily helps anyone to convert between time and position – this call – that is very effective and the heart of our Gantt implementation – ensures that the TodayLine is positioned on the exact spot where the user perceives NOW to be.

Also notice that we check the ScheduleMode setting to decide in what direction to draw the todayline.

This is the sample implementation of the OnDrawForeground event:

   1: void gantt1_OnDrawForeground(object sender, GanttUserDrawArgs e)
   2: {
   3:     e.Canvas.Children.Clear();
   4:     Canvas c = new Canvas();
   5:     e.Canvas.Children.Add(c);
   6:     Canvas.SetLeft(c, gantt10.DateScaler.TimeToPixel(DateTime.Now));
   7:     Canvas.SetTop(c, 440);
   8:  
   9:     Ellipse ellipse = new Ellipse();
  10:     c.Children.Add(ellipse);
  11:     ellipse.Stroke = new SolidColorBrush(Colors.Blue);
  12:     ellipse.StrokeThickness = 3;
  13:     ellipse.Width = 100;
  14:     ellipse.Height = 20;
  15:  
  16:     TextBlock text = new TextBlock();
  17:     c.Children.Add(text);
  18:     text.Text = "Easily add your own time dependant stuff\n in the foreground";
  19:  
  20: }

The difference between Foreground and Background is achieved with Canvas.ZIndex settings and enables you to get effects like this:

Gantt Time items ZIndex

Where the the blue ellipse goes above the TimeItem and the green ellipse goes below the TimeItem.


Size override and User draw of Gantt Time Items

One feature that may come in handy from time to time is the size override behavior that you can get by implement the event OnTimeItem_UserDrawBounds in the GTP.NET.

It is most commonly used with UserDraw – since most of the standard time items have a standard square form that does not get any real benefit from overriding the perceived size – since the standard square that is defined by Start and Stop in one axis and GanttRow Height (with consideration to Sub columns) in the other direction.

   1: gantt1.OnTimeItem_UserDraw += new TimeItemEvent(gantt1_OnTimeItem_UserDraw);
   2: gantt1.OnTimeItem_UserDrawBounds += new TimeItemEvent(gantt1_OnTimeItem_UserDrawBounds);

And the implementations:

   1: /// <summary>
   2: /// This event can help you override how a time item is perceived in the TimeItemFromXY (the active clickable area )
   3: /// </summary>
   4: void gantt1_OnTimeItem_UserDrawBounds(Gantt aGantt, TimeItemEventArgs e)
   5: {
   6:     GraphicsPath gp = new GraphicsPath();
   7:     gp.AddEllipse(e.Rect);
   8:     e.BoundingRegion = new Region(gp);
   9: }
   1: /// <summary>
   2: /// ...and this event helps you draw something in that area. 
   3: /// Note that the two areas can differ, its up to you, but it will a bit strange...
   4: /// </summary>
   5: void gantt1_OnTimeItem_UserDraw(Gantt aGantt, TimeItemEventArgs e)
   6: {
   7:     GraphicsPath gp = new GraphicsPath();
   8:     gp.AddEllipse(e.Rect);
   9:     e.G.FillPath(new SolidBrush(Color.White), gp);
  10:     e.G.DrawPath(new Pen(Color.Blue), gp);
  11:     string s = "Notice how\r\nclicks in the corners\r\nare ignored";
  12:     SizeF mess=e.G.MeasureString(s, this.gantt1.Font);
  13:     Point pointToPlaceText = new Point((int)(e.Rect.Left + Math.Round((double)e.Rect.Width / 2) - Math.Round(mess.Width / 2)), 
  14:                                        (int)(e.Rect.Top + Math.Round((double)e.Rect.Height / 2) - Math.Round(mess.Height / 2)));
  15:     e.G.DrawString(s, this.gantt1.Font, new SolidBrush(Color.Red), pointToPlaceText);
  16:  
  17: }

This will render this way:

image

Now the cool thing is that when you click in the white area the TimeItem will be selected/moved/resized etc, but when you click in the corners, where the Time Item Square should have been drawn if this was a standard time item, this is just like clicking on the GanttRow alone.

I also added some code in the userdraw to center a text – you need to measure the text then consider the available space – then place the text.


Thursday, July 22, 2010

Collision detect of TimeItems in a Gantt chart

The GTP.NET will order the colliding time items in separate sub columns. The way this is done is fully automatic and the  result will be something like this:

image

But what if you would rather to have the Top time item at the bottom?

One way is to re-order the SubColumn values after the collision detection is done. Go like this:

   1: Private _CollisionDetectNeedUpdate As Boolean
   2:  
   3: Private Sub OnCollionsDetect(ByVal aGantt As PlexityHide.GTP.Gantt,
   4:        ByVal e As PlexityHide.GTP.CollisionDetectEventArgs)
   5:     _CollisionDetectNeedUpdate = True
   6: End Sub
   7:  
   8: Private Sub Gantt1_OnTimeItemAreaPaintBackground(ByVal aOffscreenDraw As 
   9:     PlexityHide.GTP.OffscreenDraw, ByVal e As PlexityHide.GTP.OffscreenDrawArgs)
  10:         Handles Gantt1.OnTimeItemAreaPaintBackground
  11:     If (_CollisionDetectNeedUpdate And 
  12:      Gantt1.Grid.RootNodes.Count > 0 And Gantt1.MouseMoveKind = MouseMoveKind.none) Then
  13:         Dim gr As GanttRow = Gantt.GanttRowFromGridNode(Gantt1.Grid.RootNodes(0))
  14:  
  15:         For i = 0 To gr.Layers(0).Count - 1
  16:             gr.Layers(0).Item(i).SubCol = 1 + gr.SubColumnsFromCollisionDetect - 
  17:                gr.Layers(0).Item(i).SubCol
  18:         Next
  19:         _CollisionDetectNeedUpdate = False
  20:     End If
  21:  
  22: End Sub

And then you have a result that looks like this:

image


Saturday, July 17, 2010

Getting an Ajax Gantt chart set up properly with VisualStudio 2010

You can use GTP.NET on the web and it supports Ajax to move around time items and scroll in time.

Create a new ASP.NET project:

image

The Gantt chart does not care from where your data comes, but since it needs data to display I need to go thru these steps in order to show anything at all. If you already have data you want to show, you might be better of just using that from the get-go.

First we need to get hold of some data, so I add a new SqlServer database to the App_Data folder and create some tables and fields to hold my Gantt data:

image

Start and Stop fields in the WorkItem table are of type DateTime.

I then create a new typed DataSet (Do not like typed dataset? Any data is fine really, do it your way ) and drag on the tables and set up some relations:

image

I added the Activity table twice, naming in SubActivity the second time, to hold a 2 level hierarchy in the gantt chart.

On the Default.aspx page where I want to show my Gantt chart I drag on a ScriptManager and UpdatePanel onto the form. These are the standard components that provide the Ajax effect of partial postbacks. I also add the Gantt_ASP inside the UpdatePanel, and I add some buttons about the UpdatePanel.

What also is important is to add the GTPImg.aspx page to the project – this page helps with Image rendering that the Gantt needs. You will find it under [program files]\plexityHide AB\GTP.NET 3.3.5.31\Assemblies\CLR35\asp.net. Make sure to add GTPImg.aspx and GTPImg.aspx.cs to the project.

image image

 

If you run the project as it stands now you should be able to see the Gantt chart DateScaler and you should be able to panorate in time by grabbing and dragging it. You can also use the buttons to scale in or out to show less or more time:

image

Now we will need to add some code to set up the databind:

   1: private DataSet1 _appDataset;
   2: protected void Page_Load(object sender, EventArgs e)
   3: {
   4:  
   5:  
   6:     _appDataset = new DataSet1();
   7:  
   8:      if (!Page.IsPostBack)
   9:      {
  10:          // Application load, Get values from database
  11:          FetchValuesFromStorage();
  12:          
  13:          SaveThisSessionsDataInTheCacheForFasterRetrievelOnPostback();
  14:      }
  15:      else
  16:      {
  17:          LoadDatasetFromCache();
  18:      }
  19:  
  20:     
  21:      InitTheGantt();
  22:  
  23:  
  24: }

 

In this example I let the Gantt behave differently depending if this was a Postback or a new Page request. If it was a postback I get the data from the session cache to speed things up, and not from the database.

The methods involved are :

   1: private void FetchValuesFromStorage()
   2: {
   3:     ConnectionStringSettingsCollection connections =
   4:                         ConfigurationManager.ConnectionStrings;
   5:     using (SqlConnection connection =
   6:                new SqlConnection(connections["Database1ConnectionString"].ConnectionString))
   7:     {
   8:         connection.Open();
   9:  
  10:         SqlCommand command1 = new SqlCommand("SELECT * FROM Activity where parentActivity='{00000000-0000-0000-0000-000000000000}'", connection);
  11:         SqlDataReader reader = command1.ExecuteReader();
  12:         _appDataset.Activity.Load(reader);
  13:         reader.Close();
  14:  
  15:         SqlCommand command2 = new SqlCommand("SELECT * FROM Activity where parentActivity<>'{00000000-0000-0000-0000-000000000000}'", connection);
  16:         SqlDataReader reader2 = command2.ExecuteReader();
  17:         _appDataset.SubActivity.Load(reader2);
  18:         reader2.Close();
  19:         
  20:         SqlCommand command3 = new SqlCommand("SELECT * FROM WorkItem ", connection);
  21:         SqlDataReader reader3 = command3.ExecuteReader();
  22:         _appDataset.WorkItem.Load(reader3);
  23:         reader3.Close();
  24:         
  25:     }
  26: }

And also these two methods :

 

   1: private void LoadDatasetFromCache()
   2: {
   3:     string data=this.Session["ganttSessionData"] as string;
   4:     if (data != null)
   5:     {
   6:         StringReader stre = new StringReader(data);
   7:         _appDataset.ReadXml(stre);
   8:     }
   9:     else
  10:         FetchValuesFromStorage();
  11: }
  12:  
  13: private void SaveThisSessionsDataInTheCacheForFasterRetrievelOnPostback()
  14: {
  15:     StringWriter xwri = new StringWriter();
  16:     _appDataset.WriteXml(xwri, XmlWriteMode.IgnoreSchema);
  17:     this.Session["ganttSessionData"] = xwri.ToString();
  18: }

Minimum logic there really – just to show one way of many possible way to handle your data in an ASP.NET application.

Moving on with what this post is really about: a Live Ajax Gantt with Data.

To initialize the databind I have implemented this:

   1: private void InitTheGantt()
   2: {
   3:     Gantt_ASP1.Gantt.Grid.Columns.Clear();
   4:     Gantt_ASP1.Gantt.Grid.Columns.Add(new GridColumn() { Title = "Activity", Tree=true, DataSourceColumn="Name" });
   5:  

6: Gantt_ASP1.Gantt.OnEachListItemWhenDataBound_GridNode +=
new EachListItemWhenDataBoundHandler(Gantt_OnEachListItemWhenDataBound_GridNode);

   7:     Gantt_ASP1.Gantt.OnEachListItemWhenDataBound_TimeItem += 
                                         new EachListItemWhenDataBoundHandler(Gantt_OnEachListItemWhenDataBound_TimeItem);
   8:  
   9:     InitLayouts();
  10:  
  11:     Gantt_ASP1.Gantt.GridProperties.RootNodes_DataSource = _appDataset.Activity;
  12:  
  13:     
  14:     
  15:     
  16:     if (!Page.IsPostBack)
  17:     { 
  18:         // fold out tree nodes the first time we show
  19:         for (int i = 0; i < Gantt_ASP1.Gantt.Grid.RootNodes.Count; i++)
  20:         {
  21:             GridNode gn = Gantt_ASP1.Gantt.Grid.RootNodes[i];
  22:             gn.Expanded = true;
  23:             Gantt_ASP1.SetExpandedStatusForGridNode(gn, true);
  24:         }
  25:     }
  26: }

As you see on row 11 the root activities are handed to the Gantt_ASP1.Gantt.GridProperties.RootNodes_DataSource and that is the key row in that code. But equally important is the event handlers that are set up for “OnEachListItemWhenDataBound_”, there are two of these one for GridNodes and one for TimeItem.

Theses event handlers will allow us to set up further databind per item in the first databound collection (root activities).

   1: void Gantt_OnEachListItemWhenDataBound_GridNode(object GTPComponent, EachListItemWhenDataBoundArgs e)
   2: {
   3:     GridNode gn = (e.GTPObject as GridNode);
   4:     if (gn.SubLevel == 0)
   5:     { 
   6:         // root item
   7:         DataSet1.ActivityRow row=(e.CurrencyManagerListItem as DataRowView).Row as  DataSet1.ActivityRow;                
   8:         gn.SubNodes_DataSource=row.GetChildRows(_appDataset.Relations["FK_Activity_SubActivity"]);
   9:     }
  10:     else if (gn.SubLevel == 1)
  11:     {
  12:         // sub item
  13:         DataSet1.SubActivityRow row = e.CurrencyManagerListItem as DataSet1.SubActivityRow;
  14:         Layer l = GanttRow.FromGridNode(gn).Layers[0];
  15:         l.NameInDS_Identity = "id";
  16:         l.NameInDS_Start = "Start";
  17:         l.NameInDS_Stop = "Stop";
  18:         l.TimeItemLayout = "MyTimeItemLayout";
  19:         l.DataSource = row.GetChildRows(_appDataset.Relations["FK_SubActivity_WorkItem"]);            
  20:     }
  21:  
  22: }
  23:  
  24: void Gantt_OnEachListItemWhenDataBound_TimeItem(object GTPComponent, EachListItemWhenDataBoundArgs e)
  25: {
  26:     // This is a good place to apply data dependant styling per time item
  27: }
  28:  

In the code above you will see that I do different stuff depending on GridNode.SubLevel – this is a convenient way to know on what level I am getting this call. On Level zero – root level – I set up the databind of the Sub-activity-nodes. And on Level 1  the sub-activities – I set up databind for TimeItems in a TimeItemLayer.

I also want to mention a few words about CellLayouts and TimeItemLayouts – these are objects that control the rendering look and feel. In this sample I have chosen to define them in code like this:

   1: private void InitLayouts()
   2: {
   3:     CellLayout cl=Gantt_ASP1.Gantt.Grid.CellLayouts.GetFromName("MyCellLayout");
   4:     cl.Font=new System.Drawing.Font("Geneva",18);
   5:     cl.MinHeight = 30;
   6:  
   7:     Gantt_ASP1.Gantt.Grid.Columns[0].LayoutName = "MyCellLayout";
   8:  
   9:     TimeItemLayout til=Gantt_ASP1.Gantt.TimeItemLayouts.GetFromName("MyTimeItemLayout");
  10:     til.TimeItemStyle = TimeItemStyle.Glossy;
  11:     til.BrushKind = BrushKind.GradientHorizontal;
  12:     til.Color = Color.Pink;
  13:     til.GradientColor = Color.Red;
  14:  
  15:  
  16: }

Running the sample will give you this ; depending on your data of course:

image

The implementation behind the buttons are all about data and not Gantt specific at all:

   1: protected void ButtonAddRoot_Click(object sender, EventArgs e)
   2: {
   3:  
   4:     _appDataset.Activity.AddActivityRow(Guid.NewGuid(), "Activity", "", Guid.Empty);
   5: }
   6:  
   7: protected void ButtonAddSub_Click(object sender, EventArgs e)
   8: {
   9:     if (_appDataset.Activity.Count > 0)
  10:     {
  11:         _appDataset.SubActivity.AddSubActivityRow(Guid.NewGuid(), "SubAct", "", _appDataset.Activity[0]);
  12:     }
  13: }
  14:  
  15: protected void ButtonAddWork_Click(object sender, EventArgs e)
  16: {
  17:     if (_appDataset.Activity.Count > 0 && _appDataset.Activity[0].GetSubActivityRows().Count()>0)
  18:     {
  19:         DataSet1.SubActivityRow row = _appDataset.Activity[0].GetSubActivityRows()[0];

20: _appDataset.WorkItem.AddWorkItemRow(Guid.NewGuid(), Gantt_ASP1.Gantt.DateScaler.StartTime.AddDays(2),

Gantt_ASP1.Gantt.DateScaler.StartTime.AddDays(12), (float)0.90, row);

  21:     }
  22: }
  23:  
  24: protected void ButtonSave_Click(object sender, EventArgs e)
  25: {
  26:     DataSet1TableAdapters.ActivityTableAdapter ta1=new DataSet1TableAdapters.ActivityTableAdapter();
  27:     DataSet1TableAdapters.SubActivityTableAdapter ta2 = new DataSet1TableAdapters.SubActivityTableAdapter();
  28:     DataSet1TableAdapters.WorkItemTableAdapter ta3 = new DataSet1TableAdapters.WorkItemTableAdapter();
  29:  
  30:     ta1.Update(_appDataset.Activity);
  31:     ta2.Update(_appDataset.SubActivity);
  32:     ta3.Update(_appDataset.WorkItem);
  33:  
  34:     _appDataset.AcceptChanges();
  35: }

The One bit of important Gantt related code left is this event implementation:

   1:  
   2: protected void Gantt_ASP1_OnClientSideChangesApplied(object sender, EventArgs e)
   3: {
   4:     SaveThisSessionsDataInTheCacheForFasterRetrievelOnPostback();
   5: }
   6:  

In the event above that is fired by the Gantt_ASP when it is done with applying the users change to the data, I save the data to the cache. This is the brilliant thing about databind – Load and save code need not be bothered with the components that aided in presenting or changing the data.

Ok Done. Comments are appreciated.

Sample can be downloaded from here http://www.plexityhide.com/pub/VS2010_Gantt_ASP_WebApplication1.zip


This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]

Support

Support intro

Knowledgebase

FAQ phGantTimePackage

FAQ GTP.NET

FAQ general

History