ASP.NET TreeView sort - asp.net

I am accustomed to winform TreeView having a Sorted property which automatically manages nodes sorting.
I now have to alphabetically sort an ASP.NET TreeView and I'm surprised I cannot find any similar property or callback method.
Is there any way to automatically achieve this operation in ASP.NET or do I have to manually sort and insert my nodes in correct order?

You'll need to write your own sorting function but its reasonably trivial to add this functionality.
http://blog.mdk-photo.com/post/C-Extentionmethod-Tree-Node-View-Sort().aspx
.NET 3.5 supports extension methods so you can add functionality to pre-existing System Classes. Notice the this syntax on the method parameter. More Info Here
public static void Sort(this TreeView tv)
{
TreeNodeCollection T = tv.Nodes.Sort();
tv.Nodes.Clear();
tv.Nodes.AddRange(T);
}
public static void Sort(this TreeNode tn)
{
TreeNodeCollection T = tn.ChildNodes.Sort();
tn.ChildNodes.Clear();
tn.ChildNodes.AddRange(T);
}
The first link contains the rest of the code you'll need to complete the sorting functionality

Three years later and the TreeView still doesn't support sorting natively. Here's a simple method to do sort all nodes alphabetically.
private void SortTreeNodes(TreeNodeCollection treeNodes)
{
var sorted = true;
foreach (TreeNode treeNode in treeNodes)
{
SortTreeNodes(treeNode.ChildNodes);
}
do
{
sorted = true;
for (var i = 0; i < treeNodes.Count - 1; i++)
{
var treeNode1 = treeNodes[i];
var treeNode2 = treeNodes[i + 1];
if (treeNode1.Text.CompareTo(treeNode2.Text) > 0)
{
treeNodes.RemoveAt(i + 1);
treeNodes.RemoveAt(i);
treeNodes.AddAt(i, treeNode2);
treeNodes.AddAt(i + 1, treeNode1);
sorted = false;
}
}
} while (!sorted);
}
Call it like this
SortTreeNodes(myTreeView.Nodes);

Related

how to update Visual Studio UI when using DynamicItemStart inside a vsix package

I'm implementing a DynamicItemStart button inside a Menu Controller. I'm loading the dynamic items for this button when Visual Studio starts. Everything is loaded correctly so the initialize method is called an I see all the new items in this Dynamic button. After the package is completely loaded I want to add more items to this Dynamic button, but since the package is already loaded the initialize method is not called again and I cannot see the new items in this Dynamic button. I only see the ones that were loaded when VS started.
Is there any way that I can force the update of this Dynamic button so it shows the new items?. I want to be able to update the VS UI after I added more items but outside the Initialize method.
The implementation I did is very similar to the one showed on this msdn example:
http://msdn.microsoft.com/en-us/library/bb166492.aspx
Does anyone know if an Update of the UI can be done by demand?
Any hints are greatly appreciated.
I finally got this working. The main thing is the implementation of a derived class of OleMenuCommand that implements a new constructor with a Predicate. This predicate is used to check if a new command is a match within the DynamicItemStart button.
public class DynamicItemMenuCommand : OleMenuCommand
{
private Predicate<int> matches;
public DynamicItemMenuCommand(CommandID rootId, Predicate<int> matches, EventHandler invokeHandler, EventHandler beforeQueryStatusHandler)
: base(invokeHandler, null, beforeQueryStatusHandler, rootId)
{
if (matches == null)
{
throw new ArgumentNullException("Matches predicate cannot be null.");
}
this.matches = matches;
}
public override bool DynamicItemMatch(int cmdId)
{
if (this.matches(cmdId))
{
this.MatchedCommandId = cmdId;
return true;
}
this.MatchedCommandId = 0;
return false;
}
}
The above class should be used when adding the commands on execution time. Here's the code that creates the commands
public class ListMenu
{
private int _baselistID = (int)PkgCmdIDList.cmdidMRUList;
private List<IVsDataExplorerConnection> _connectionsList;
public ListMenu(ref OleMenuCommandService mcs)
{
InitMRUMenu(ref mcs);
}
internal void InitMRUMenu(ref OleMenuCommandService mcs)
{
if (mcs != null)
{
//_baselistID has the guid value of the DynamicStartItem
CommandID dynamicItemRootId = new CommandID(GuidList.guidIDEToolbarCmdSet, _baselistID);
DynamicItemMenuCommand dynamicMenuCommand = new DynamicItemMenuCommand(dynamicItemRootId, isValidDynamicItem, OnInvokedDynamicItem, OnBeforeQueryStatusDynamicItem);
mcs.AddCommand(dynamicMenuCommand);
}
}
private bool IsValidDynamicItem(int commandId)
{
return ((commandId - _baselistID) < connectionsCount); // here is the place to put the criteria to add a new command to the dynamic button
}
private void OnInvokedDynamicItem(object sender, EventArgs args)
{
DynamicItemMenuCommand invokedCommand = (DynamicItemMenuCommand)sender;
if (null != invokedCommand)
{
.....
}
}
private void OnBeforeQueryStatusDynamicItem(object sender, EventArgs args)
{
DynamicItemMenuCommand matchedCommand = (DynamicItemMenuCommand)sender;
bool isRootItem = (matchedCommand.MatchedCommandId == 0);
matchedCommand.Enabled = true;
matchedCommand.Visible = true;
int indexForDisplay = (isRootItem ? 0 : (matchedCommand.MatchedCommandId - _baselistID));
matchedCommand.Text = "Text for the command";
matchedCommand.MatchedCommandId = 0;
}
}
I had to review a lot of documentation since it was not very clear how the commands can be added on execution time. So I hope this save some time whoever has to implement anything similar.
The missing piece for me was figuring out how to control the addition of new items.
It took me some time to figure out that the matches predicate (the IsValidDynamicItem method in the sample) controls how many items get added - as long as it returns true, the OnBeforeQueryStatusDynamicItem gets invoked and can set the details (Enabled/Visible/Checked/Text etc.) of the match to be added to the menu.

Create tree using multithreading and recursion

This question is specifically related creating a tree using multithreading and recursion.
I have got the code running that will create the tree using recursion but the time required to create that tree is more than I want to spend.
The reason for the slowness of that is because I am calling TaxonomyManager in Ektron CMS which takes a little bit to return and all the calls add up quickly. I was wondering if there is a way to create a tree using multithreading.
(I don't have the code at present with me but I will add that code as soon as I get access to that code).
If I go this route what the chances of me corrupting the tree as the tree is one root node and multithreading is going to add those nodes to that node at some point.
Thanks for any input anyone may have.
Edit: Added code. TaxonomyNodes is my class doesn't have a lot of properties. Has Id,Name,Description, Path (Stores the path in similar way as Ektron), HasChildren flag, ParentId, and public List Children.
public List<TaxonomyNodes> CreateTree()
{
try
{
TaxonomyManager tManager = new TaxonomyManager();
TaxonomyCriteria criteria = new TaxonomyCriteria();
criteria.AddFilter(TaxonomyProperty.ParentId, CriteriaFilterOperator.EqualTo, 0);
criteria.OrderByDirection = EkEnumeration.OrderByDirection.Ascending;
criteria.OrderByField = TaxonomyProperty.Id;
List<TaxonomyData> tDataList = tManager.GetList(criteria);
int index = 0;
if (tDataList != null)
{
foreach (TaxonomyData item in tDataList)
{
if (item.Name != "Companies" && item.Name != "Content Information Centers")
root.Insert(index++, new TaxonomyNodes() { ParentId = 0, TaxonomyId = item.Id, TaxonomyDescription = item.Description, TaxonomyName = item.Name, TaxonomyPath = item.Path, HasChildren = item.HasChildren, Children = new List<TaxonomyNodes>() });
}
}
index = 0;
foreach (TaxonomyNodes itemT in root)
{
itemT.Children = CreateNodes(itemT.TaxonomyId, itemT);
}
return root;
}
catch (Exception)
{
throw;
}
}
private List<TaxonomyNodes> CreateNodes(long taxonomyId, TaxonomyNodes itemToAddTo)
{
try
{
TaxonomyManager tManager = new TaxonomyManager();
TaxonomyCriteria criteria = new TaxonomyCriteria();
criteria.AddFilter(TaxonomyProperty.ParentId, CriteriaFilterOperator.EqualTo, taxonomyId);
criteria.OrderByDirection = EkEnumeration.OrderByDirection.Ascending;
criteria.OrderByField = TaxonomyProperty.Id;
List<TaxonomyData> tDataList = tManager.GetList(criteria);
List<TaxonomyNodes> node = new List<TaxonomyNodes>();
if (tDataList != null)
{
foreach (TaxonomyData item in tDataList)
{
node.Add(new TaxonomyNodes() { ParentId = taxonomyId, Children = null, TaxonomyId = item.Id, TaxonomyDescription = item.Description, TaxonomyName = item.Name, TaxonomyPath = item.Path, HasChildren = item.HasChildren });
itemToAddTo.Children = node;
if (item.HasChildren)
{
CreateNodes(item.Id, node[node.Count - 1]);
}
else
{
return node;
}
}
}
return node;
}
catch (Exception)
{
throw;
}
}
Rather than get into multithreading, which, though it may work, is not officially supported by Ektron's APIs and may present other challenges, I would recommend some form of caching or other storage for the data that does not require recursive DB calls.
Options:
1 - Ektron's Taxonomy APIs do include a GetTree method, which can pull the entire tree, up to a specified number of levels, and child items in a single API call rather than recursively. This may perform better and would be easily cached.
2 - Ektron provides API-level caching that can be readily enabled in the web.config by changing
<framework defaultContainer="Default" childContainer="BusinessObjects" />
To:
<framework defaultContainer="Cache" childContainer="BusinessObjects" />
3 - Use an eSync Strategy which will output the data you need (better to use your own streamlined objects than to store Ektron's with all the extra data) to something like an XML file. See this sample, http://developer.ektron.com/Templates/CodeLibraryDetail.aspx?id=1989&blogid=116, which I wrote to do this very thing. It hooks into the DB sync complete event and triggers a console application which writes the entire Taxonomy structure out to an XML file.

Presentation of data from Mondrian OLAP engine + Olap4j

I'm doing a little bit of planning of an application that uses Mondrian OLAP engine with Olap4j and should present/display data to user. I understand all the back-end stuff, but I'm not sure how should I display the data in the view layer.
For example olap4j has a formatter that prints the SELECT nicely into the console.
How is the data that I get from olap4j displayed in view layer ? I just went through the olap4j API, and there doesn't seem to be anything for getting the result in a form that can be somehow further processed and displayed. Is this process part of the Pentaho solution ? So that otherwise it is really not easy to present data just from Mondrian OLAP engine and olap4j ?
EDIT: I'm used to traditionally get some data from a database into my DTO and display it in view layer. But how do I create DTOs for such a complicated result set ?
You can create your own view layer it's just a little bit tricky.
OlapStatement.executeOlapQuery() returns a CellSet, you will have to work with that. Also read the specifications, it's a good source of information.
Here is an example, that creates List<List<MyCell>> (not the best representation but it's easy to undarstand how it works). This creates a table similar to http://www.olap4j.org/api/index.html?org/olap4j/Position.html (without the "Gender" and "Product" labels).
private final static int COLUMNS = 0; //see Cellset javadoc
private final static int ROWS= 1; //see Cellset javadoc
/**
* Outer list: rows, inner list: elements in a row
*/
private List<List<MyCell>> getListFromCellSet(CellSet cellSet) {
List<List<MyCell>> toReturn= new ArrayList<List<MyCell>>();
//Column header
//See http://www.olap4j.org/api/index.html?org/olap4j/Position.html on how Position works, it helps a lot
//Every position will be a column in the header
for (Position pos : cellSet.getAxes().get(COLUMNS).getPositions()) {
for (int i = 0; i < pos.getMembers().size(); i++) {
if (toReturn.size() <= i) {
toReturn.add(i, new ArrayList<MyCell>());
}
Member m = pos.getMembers().get(i);
MyCell myCell = new MyCell(m); //use m.getCaption() for display
toReturn.get(i).add(myCell );
}
}
//Put empty elements to the beginning of the list, so there will be place for the rows header
if (cellSet.getAxes().get(ROWS).getPositions().size() > 0) {
for (int count=0; count < cellSet.getAxes().get(1).getPositions().get(0).getMembers().size(); count++) {
for (int i = 0; i < toReturn.size(); i++) {
toReturn.get(i).add(0, new MyCell());
}
}
}
//Content + row header
for(int i = 0; i < cellSet.getAxes().get(ROWS).getPositionCount(); i++) {
List<MyCell> row = new ArrayList<MyCell>();
//Header
for (org.olap4j.metadata.Member m : cellSet.getAxes().get(ROWS).getPositions().get(i).getMembers()) {
row.add(new MyCell(m));
}
//Content
for (int j = 0; j < cellSet.getAxes().get(COLUMNS).getPositionCount(); j++) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(j); //coordinte
list.add(i); //coordinte
row.add(new MyCell(cellSet.getCell(list))); //use cell.getFormattedValue() for display
}
toReturn.add(row);
}
return toReturn;
}
Create the MyCell class with these constructors:
public class MyCell {
...
public MyCell(){...}
public MyCell(Member m){...}
public MyCell(Cell c){...}
}
Don't forget to display the filters, use Cellset.getFilterAxis() for that.
You can also check the Rectangular formatter on SourceForge, but it's a bit longer.

ASP.Net Custom Control

I am developing a custom control that needs to display a dropdownlist as a composite control.
The drop down list gets populated from a Rest web service. The problem I am facing is that the dropdownlist only has DataTextField and DataValueField but I need a way of storing more values in the control i.e. I have a couple of other properties I need to access for the selected item.
What is the best way of going about this?
Here is the code I have so far:
[ValidationProperty("SelectedValue")]
public class SelectSurveyControl : Panel
{
private DropDownList ddlSurveys;
public string SelectedSurveyId
{
get
{
return SelectedValue;
}
}
public string SelectedSurveyJavascriptEmbedCode
{
get
{
return this.ddlSurveys.SelectedItem.Attributes[""];
}
}
public string SelectedValue
{
get
{
return ddlSurveys.SelectedValue;
}
set
{
if (ddlSurveys == null)
{
ddlSurveys = new DropDownList();
}
ddlSurveys.SelectedValue = value;
}
}
protected override void OnLoad(EventArgs e)
{
base.OnInit(e);
if (ddlSurveys == null)
{
ddlSurveys = new DropDownList();
}
IList<Survey> surveys = GetSurveys();
this.ddlSurveys.DataSource = surveys;
this.ddlSurveys.DataTextField = "title";
this.ddlSurveys.DataValueField = "id";
this.ddlSurveys.DataBind();
ddlSurveys.SelectedValue = this.SelectedValue;
ddlSurveys.CssClass = "umbEditorTextFieldMultiple charlimit";
ddlSurveys.Attributes.Add("SurveyId", SelectedSurveyId);
ddlSurveys.Attributes.Add("JavascriptEmbedingCode", SelectedSurveyId);
this.Controls.Add(ddlSurveys);
}
public IList<Survey> GetSurveys()
{
...
}
}
Try using a string join/split to store and retrieve the various values, then you don't have to customize your dropdown list very much.
For Example:
Text: Some Title
Value: 1|testing test|2/12/2010
This will let you store as many values as you want, so long as you choose an appropriate character to join and split on. I usually use the bar, as in my example above.
Side Note: I was looking at your selected value set handler and it needs some tweaking. You shouldn't check for a null drop down list, instead you should call EnsureChildControls() before each get and set instead. Make sure you override the CreateChildControls() method and create your controls there.
You could use a hidden field and iterate thru a copy of the returned Surveys like this:
foreach(Survey s in Surveys){
string val = s.id + ":" + s.<property1> + ":" + s.<property2>;
hiddenField.Value += val +",";
}
When you need to read from the hidden field, you use String.Split to separate the values into arrays using ',' as the separator and in each array, you split again using ':'.
In the first split Array1[0] who be the survey id and Array1[n!=0] would be the properties of the Survey with the id = Array1[0]. Array[n!=0] would then be split into Array2.
I would suggest handling empty property values with an empty string or something or else you might end up with unequal lengths especially if you specify StringSplitOptions.RemoveEmptyEntries.
Agricfowl

Shuffle DevExpress GridControl DataSource

I need to shuffle the GridControl's DataSource. I use this property in a UserControl:
private List<Song> _songsDataSource;
public List<Song> SongsDataSource
{
get { return _songsDataSource; }
set
{
_songsDataSource = value;
if (!value.IsNull())
{
SongsBindingList = new BindingList<Song>(value);
songsBinding.DataSource = SongsBindingList;
}
}
}
Then i use a method that i clone, shuffle and append to the SongsDataSource property:
List<Song> newList = HelpClasses.Shuffle((List<Song>) SongsDataSource.Clone());
SongsDataSource = newList;
public static List<Song> Shuffle(List<Song> source)
{
for (int i = source.Count - 1; i > 0; i--)
{
int n = rng.Next(i + 1);
Song tmp = source[n];
source[n] = source[i - 1];
source[i - 1] = tmp;
}
return source;
}
Strange thing is that it doesn't seem to reflect the changes to the GridControl even i use the GridControl.RefreshDataSource() method after set the SongsDataSource method. If i check the DataSource order, shuffle was happened successfully.
Thank you.
Since you've changed the object originally set as a DataSource, calling RefreshDataSource() won't do any good cause you can't refresh something that's no longer there. Your problem is here:
List<Song> newList = HelpClasses.Shuffle((List<Song>) SongsDataSource.Clone());
SongsDataSource = newList; // the reference has changed, the grid doesn't know what to do when RefreshDataSource() is called.
You can pass the list as it is, without the need of cloning it. Also surround the Shuffle() method call with gridControl.BeginUpdate() end gridControl.EndUpdate() to prevent any updates to the grid while the elements of the DataSource are changing.
I had such problems with DevExpress GridControl. I think, that this situation caused by GridView(http://documentation.devexpress.com/#WindowsForms/clsDevExpressXtraGridViewsGridGridViewtopic), which creates automaticly for each GridControl.
This is part of GridControl responsible for visualization of DataSource.
If you need to change DataSource try:
GridView.Columns.Clear();
GridControl.DataSource = You_New_DataSource;
GridView.RefreshData();
GridControl.RefreshDataSource();

Resources