Drag and Drop TreeView control

Monday, 29 June 2009 20:29 by mpyost

A neat control that I have created that I thought I would share is an extension of Microsoft’s ASP.NET TreeView control that allows for nodes on the tree to be rearranged by simply dragging and dropping them. This was originally built for a project I was working on awhile back but I found the control so cool I decided to keep it.

How it works: The extended tree works just like the original one provided in the ASP.Net 2.0 library but has the additional capability of rearranging the nodes on the tree by simply dragging and dropping a node onto the new parent node. Dragging and dropping works by holding down the left mouse button and dragging the node to its new location. As the node is dragged around on the tree you will see a shadow copy of the node you are moving as well as a highlighting of the other tree nodes as you move over them. When you are hovering over the node you wish to drop your selected node into simply release the right mouse and then BAM, the node has now been moved. Below is a screen shot of the drag/drop on the tree in action.

TreeView Demo
Figure 1 – Screenshot of Drag and Drop TreeView

I have supplied the source code for this control below but l will go over the new properties and event handlers you should be aware of.

EnableDragAndDrop property: as its name implies, this is simply a new property on the tree view that allows a user/developer to enable or disable the ability to drag and drop tree nodes.

NodeDropConfirmationMessage property: this is where you would set the text you would like to display to the user when they drop a node. The purpose for this is to give the user a confirmation of their action and give them the ability to cancel the drop if it was in error. If no value is set on this property, no confirmation message will be displayed and every drop action will be performed.

TreeNodeDropped event: this is the event called after a user has dropped a node onto another node. Handle this event if you wish to handle the dropped event in some special way, like if you need to persist the change to a database or fire off some other events. That being said, this event doesn’t have to be handled for the dropped node to show up in its new location on the TreeView because the TreeView’s viewstate is updated and the TreeView is redrawn to display the changes.

The above are all an implementer needs to know about in order to use this new feature. Now I will quickly go over the files that are included in the source I am providing. I will not go into too much detail on the specific changes but rather what class files are changed/added in order for the new feature to work.

TreeView class: this is the class that inherits from Microsoft’s TreeView control. While I kept the name the same as Microsoft’s control, if you find that too confusing you are more than welcome to give your class another name. Essentially this is the class a user/implementer would interact with for using this new feature and is the one that contains the new properties and event I described above.

TreeViewNode class: this is a class that inherits from Microsoft’s TreeViewNode control. This was necessary in order for me to add the new client side event handler for handling the moving of the node on the tree. Again, if you find the name too confusing since I kept it the same as the base class, you can change it, just make sure you update the reference to this class in your new TreeView class.

TreeNodeDroppedEventArgs: this is the class used to passed what TreeViewNode moved and what TreeViewNode received the drop to the TreeNodeDropedEvent handler.

TreeView.js: this is the file that contains all of the javascript needed to move, track, and drop a TreeViewNode. I coded the TreeView class to register this file as an embedded resource so if you change the namespace from what is in the source code, you will need to make sure you change the registration and retrieval of this file to match your namespace. You can fine more information about doing that here.

Well, I hope everyone finds this blog useful. I know it is nice to finally get some of my custom controls out there for other developers to use since I have been “building off the backs of giants” myself for so many years that I felt guilty I never gave back. As always if there are any questions or problems with this blog or my source code, please make sure to leave a comment and I will get back to you as soon as I can. Cheers!

Source Files - Download

Tags:   ,
Categories:   ASP.NET | C# | .NET 2.0 | Server Controls | Web
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

GridView with dynamic insert row

Thursday, 25 June 2009 02:09 by mpyost

We have all experienced the need to show an “insert row” in Microsoft’s GridView and the only way to do that “out of the box”is to use the footer template in each column. The biggest drawback with that approach is the footer row will only be displayed if there is data bound to the grid which in turn prevents the case where you want to show an insert row even when the grid is empty.

Fear not for I have found a solution! Actually I found it somewhere else on the web (sorry I don’t remember where) but I have “tweaked” it to meet most of my insert row needs.

Essentially the solution involves extending Microsoft’s GridView control with code that creates an insert row at the bottom of the GridView even if there is no data bound to the grid. The following is a copy of the source code.

public class GridView : System.Web.UI.WebControls.GridView
{
// Fields
private IOrderedDictionary _InsertValues = null;
protected static readonly object EventItemInserted = new object();
protected static readonly object EventItemInserting = new object();
protected GridViewRow gvFooterRow = null;

// Events
[Category("Action")]
public event GridViewUpdatedEventHandler RowInserted
{
add
{
base.Events.AddHandler(EventItemInserted, value);
}
remove
{
base.Events.RemoveHandler(EventItemInserted, value);
}
}

[Category("Action")]
public event GridViewUpdateEventHandler RowInserting
{
add
{
base.Events.AddHandler(EventItemInserting, value);
}
remove
{
base.Events.RemoveHandler(EventItemInserting, value);
}
}

// Methods
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
int ret = base.CreateChildControls(dataSource, dataBinding);
if (((this.Columns.Count != 0) && !base.DesignMode) && this.AllowInsert)
{
this.ShowFooter = true;
DataControlField[] flds = new DataControlField[this.Columns.Count];
this.Columns.CopyTo(flds, 0);
if (ret == 0)
{
this.Controls.Clear();
Table t = new Table();
this.Controls.Add(t);
if (this.ShowHeader)
{
GridViewRow r = this.CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal);
this.InitializeRow(r, flds);
t.Rows.Add(r);
}
this.gvFooterRow = this.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Insert);
this.InitializeRow(this.gvFooterRow, flds);
t.Rows.Add(this.gvFooterRow);
}
else
{
this.gvFooterRow = base.FooterRow;
}
this.FooterRow.RowState = DataControlRowState.Insert;
this.FooterRow.Visible = base.EditIndex < 0;
for (int i = 0; i < this.Columns.Count; i++)
{
DataControlFieldCell cell = (DataControlFieldCell)this.FooterRow.Cells[i];
DataControlField fld = this.Columns[i];
fld.InsertVisible = fld.Visible;
if (fld is CommandField)
{
CommandField cf = (CommandField)fld;
CommandField ins = new CommandField();
ins.ControlStyle.CopyFrom(cf.ControlStyle);
ins.ShowCancelButton = cf.ShowCancelButton;
ins.ButtonType = cf.ButtonType;
ins.InsertImageUrl = cf.InsertImageUrl;
ins.InsertText = cf.InsertText;
ins.CancelImageUrl = cf.CancelImageUrl;
ins.CancelText = cf.CancelText;
ins.InsertVisible = true;
ins.ShowInsertButton = true;
ins.Initialize(false, this);
ins.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Insert, -1);
}
else if (cell.Controls.Count == 0)
{
fld.Initialize(base.AllowSorting, this);
fld.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Insert | DataControlRowState.Edit, -1);
}
}
OrderedDictionary dict = new OrderedDictionary();
base.ExtractRowValues(dict, this.FooterRow, true, true);
DataTable tbl = new DataTable();
DataRow row = tbl.Rows.Add(new object[0]);
foreach (string k in dict.Keys)
{
tbl.Columns.Add(new DataColumn(k));
row[k] = dict[k];
}
this.FooterRow.DataItem = new DataView(tbl)[0];
GridViewRowEventArgs args1 = new GridViewRowEventArgs(this.FooterRow);
this.OnRowCreated(args1);
this.FooterRow.DataBind();
this.OnRowDataBound(args1);
this.FooterRow.DataItem = null;
foreach (DataControlField f in this.Columns)
{
f.Initialize(this.AllowSorting, this);
}
}
return ret;
}

protected override void OnRowCommand(GridViewCommandEventArgs e)
{
if ("-1".Equals(e.CommandArgument) && ("Cancel".CompareTo(e.CommandName) == 0))
{
this.Page.Trace.Warn("Canceling insert...");
}
else if ((!"-1".Equals(e.CommandArgument) || !"Insert".Equals(e.CommandName)) || (this.Page == null))
{
base.OnRowCommand(e);
}
else
{
GridViewUpdateEventArgs args = new GridViewUpdateEventArgs(-1);
base.ExtractRowValues(args.NewValues, this.FooterRow, true, true);
this.InsertValues = args.NewValues;
bool allEmpty = true;
foreach (string v in this.InsertValues.Values)
{
if (!(allEmpty = (v == null) || (v.Trim().Length == 0)))
{
break;
}
}
if (!allEmpty)
{
this.OnRowInserting(args);
if (!string.IsNullOrEmpty(this.DataSourceID) && !args.Cancel)
{
this.GetData().Insert(args.NewValues, delegate(int affectedRows, Exception ex)
{
GridViewUpdatedEventArgs evt = new GridViewUpdatedEventArgs(affectedRows, ex);
evt.NewValues.Clear();
foreach (string v in this.InsertValues.Keys)
{
evt.NewValues.Add(v, this.InsertValues[v]);
}
this.OnRowInserted(evt);
if (!((ex == null) || evt.ExceptionHandled))
{
return false;
}
base.RequiresDataBinding = true;
return true;
});
}
}
}
}

protected virtual void OnRowInserted(GridViewUpdatedEventArgs e)
{
GridViewUpdatedEventHandler h = (GridViewUpdatedEventHandler)base.Events[EventItemInserted];
if (h != null)
{
h(this, e);
}
}

protected virtual void OnRowInserting(GridViewUpdateEventArgs e)
{
GridViewUpdateEventHandler h = (GridViewUpdateEventHandler)base.Events[EventItemInserting];
if (h != null)
{
h(this, e);
}
}

// Properties
[DefaultValue("false"), Browsable(true), Category("Behavior")]
public bool AllowInsert
{
get
{
object o = this.ViewState["_InsOk"];
return ((o != null) && ((bool)o));
}
set
{
this.ViewState["_InsOk"] = value;
}
}

public override GridViewRow FooterRow
{
get
{
return ((this.gvFooterRow == null) ? base.FooterRow : this.gvFooterRow);
}
}

protected IOrderedDictionary InsertValues
{
get
{
return this._InsertValues;
}
set
{
this._InsertValues = value;
}
}
}
ooterRow = null;

// Events
[Category("Action")]
public event GridViewUpdatedEventHandler RowInserted
{
add
{
base.Events.AddHandler(EventItemInserted, value);
}
remove
{
base.Events.RemoveHandler(EventItemInserted, value);
}
}

[Category("Action")]
public event GridViewUpdateEventHandler RowInserting
{
add
{
base.Events.AddHandler(EventItemInserting, value);
}
remove
{
base.Events.RemoveHandler(EventItemInserting, value);
}
}

// Methods
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
int ret = base.CreateChildControls(dataSource, dataBinding);
if (((this.Columns.Count != 0) && !base.DesignMode) && this.AllowInsert)
{
this.ShowFooter = true;
DataControlField[] flds = new DataControlField[this.Columns.Count];
this.Columns.CopyTo(flds, 0);
if (ret == 0)
{
this.Controls.Clear();
Table t = new Table();
this.Controls.Add(t);
if (this.ShowHeader)
{
GridViewRow r = this.CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal);
this.InitializeRow(r, flds);
t.Rows.Add(r);
}
this.gvFooterRow = this.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Insert);
this.InitializeRow(this.gvFooterRow, flds);
t.Rows.Add(this.gvFooterRow);
}
else
{
this.gvFooterRow = base.FooterRow;
}
this.FooterRow.RowState = DataControlRowState.Insert;
this.FooterRow.Visible = base.EditIndex < 0;
for (int i = 0; i < this.Columns.Count; i++)
{
DataControlFieldCell cell = (DataControlFieldCell)this.FooterRow.Cells[i];
DataControlField fld = this.Columns[i];
fld.InsertVisible = fld.Visible;
if (fld is CommandField)
{
CommandField cf = (CommandField)fld;
CommandField ins = new CommandField();
ins.ControlStyle.CopyFrom(cf.ControlStyle);
ins.ShowCancelButton = cf.ShowCancelButton;
ins.ButtonType = cf.ButtonType;
ins.InsertImageUrl = cf.InsertImageUrl;
ins.InsertText = cf.InsertText;
ins.CancelImageUrl = cf.CancelImageUrl;
ins.CancelText = cf.CancelText;
ins.InsertVisible = true;
ins.ShowInsertButton = true;
ins.Initialize(false, this);
ins.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Insert, -1);
}
else if (cell.Controls.Count == 0)
{
fld.Initialize(base.AllowSorting, this);
fld.InitializeCell(cell, DataControlCellType.DataCell, DataControlRowState.Insert | DataControlRowState.Edit, -1);
}
}
OrderedDictionary dict = new OrderedDictionary();
base.ExtractRowValues(dict, this.FooterRow, true, true);
DataTable tbl = new DataTable();
DataRow row = tbl.Rows.Add(new object[0]);
foreach (string k in dict.Keys)
{
tbl.Columns.Add(new DataColumn(k));
row[k] = dict[k];
}
this.FooterRow.DataItem = new DataView(tbl)[0];
GridViewRowEventArgs args1 = new GridViewRowEventArgs(this.FooterRow);
this.OnRowCreated(args1);
this.FooterRow.DataBind();
this.OnRowDataBound(args1);
this.FooterRow.DataItem = null;
foreach (DataControlField f in this.Columns)
{
f.Initialize(this.AllowSorting, this);
}
}
return ret;
}

protected override void OnRowCommand(GridViewCommandEventArgs e)
{
if ("-1".Equals(e.CommandArgument) && ("Cancel".CompareTo(e.CommandName) == 0))
{
this.Page.Trace.Warn("Canceling insert...");
}
else if ((!"-1".Equals(e.CommandArgument) || !"Insert".Equals(e.CommandName)) || (this.Page == null))
{
base.OnRowCommand(e);
}
else
{
GridViewUpdateEventArgs args = new GridViewUpdateEventArgs(-1);
base.ExtractRowValues(args.NewValues, this.FooterRow, true, true);
this.InsertValues = args.NewValues;
bool allEmpty = true;
foreach (string v in this.InsertValues.Values)
{
if (!(allEmpty = (v == null) || (v.Trim().Length == 0)))
{
break;
}
}
if (!allEmpty)
{
this.OnRowInserting(args);
if (!string.IsNullOrEmpty(this.DataSourceID) && !args.Cancel)
{
this.GetData().Insert(args.NewValues, delegate(int affectedRows, Exception ex)
{
GridViewUpdatedEventArgs evt = new GridViewUpdatedEventArgs(affectedRows, ex);
evt.NewValues.Clear();
foreach (string v in this.InsertValues.Keys)
{
evt.NewValues.Add(v, this.InsertValues[v]);
}
this.OnRowInserted(evt);
if (!((ex == null) || evt.ExceptionHandled))
{
return false;
}
base.RequiresDataBinding = true;
return true;
});
}
}
}
}

protected virtual void OnRowInserted(GridViewUpdatedEventArgs e)
{
GridViewUpdatedEventHandler h = (GridViewUpdatedEventHandler)base.Events[EventItemInserted];
if (h != null)
{
h(this, e);
}
}

protected virtual void OnRowInserting(GridViewUpdateEventArgs e)
{
GridViewUpdateEventHandler h = (GridViewUpdateEventHandler)base.Events[EventItemInserting];
if (h != null)
{
h(this, e);
}
}

// Properties
[DefaultValue("false"), Browsable(true), Category("Behavior")]
public bool AllowInsert
{
get
{
object o = this.ViewState["_InsOk"];
return ((o != null) && ((bool)o));
}
set
{
this.ViewState["_InsOk"] = value;
}
}

public override GridViewRow FooterRow
{
get
{
return ((this.gvFooterRow == null) ? base.FooterRow : this.gvFooterRow);
}
}

protected IOrderedDictionary InsertValues
{
get
{
return this._InsertValues;
}
set
{
this._InsertValues = value;
}
}
}

AllowInsert Property: this is a boolean value property that determines if the grid should show an insert row at the bottom of the grid. True will show an insert row and false will not. Bear in mind you do not have to bind the grid in order for it to show this row.

RowInserting Event: this is the event handler that will be called when the insert command is invoked. It works just like all of the other row commands in that this event will be called before any datasource controls perform their insert command. I modified this event to pass the insert values extracted from each of the columns to the event handler by way of the GridViewUpdateEventArgs.NewValues. This is helpful when you are not using a datasource control but rather performing your own insert logic. (of course assuming you are using two-way databinding).

RowInserted Event: this is the counterpart to the RowInserting event in that it is called after the insert command is performed on any datasource controls. Like all the other post row events, this is only called if the grid is using a datasource control.

How To Use: Using the source code above or the file I have attached below, extend Microsoft’s GridView with this functionality yourself. Once you have done that, to use it simply drop your new grid on the page or usercontrol where you wish to use it, set AllowInsert property on your grid to true, and then (if you are not using some datasource control that has insert logic associated with it) handle the RowInserting event with your own custom insert logic. Every time the grid rebinds, the insert values in the insert row are cleared and ready for another insert.

I hope everyone finds this code useful, I know I have. If you have any questions or problems with the code, please post a comment to this blog and I will address your problems or questions as soon as I can.

  

Download File - GridViewSource
Tags:   , ,
Categories:   ASP.NET | C# | Server Controls
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed

Welcome to Blogs4Coders.com

Tuesday, 23 June 2009 17:13 by mpyost

Well, I have finally taken the plunge into the world of blogging. I have been fighting this for years but finally realized it was about time I started sharing my development experiences and code samples with my fellow developers (assuming there are any that care to read my thoughts). I am always joking that “everyone is entitled to my options” and this site is a way for me to do just that. That being said, I do plan to use this site for more than just recording my ramblings about development technologies (which most blogs seem to end up doing). I actually plan to include many code and design samples that I have discovered or will discover that hopefully other developers will find helpful.

At this site you will find no prejudice toward any one particular development platform or technology. I personally have a broad range of development experiences so it was only natural for me to make my blog site that as well. While my "day job" requires me to work primarily in ASP.NET/C#, like any good developer, I am constantly experimenting with new technologies and/or exploring growing trends in existing ones.

I will do my best to blog about something relevant as often as I can but as we all know, life inevitably gets in the way. Initially I will more than likely be posting useful or popular code snippets I have collected over the years. If there are actually people who are reading my blogs and wish to contribute to this site, feel free to contact me at mpyost@gmail.com. Feel free to reference my site from yours or any other site that is appropriate. I hope everyone enjoys this site and finds it useful.

Categories:  
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed