// ----------------------------------------------------- // KpiUtilities // // Contains re-usable KPI-related utility functions. // ----------------------------------------------------- using System; using System.Collections; using System.Text; using System.Windows.Forms; namespace KpiDemo { public static class KpiUtilities { /// <summary> /// Returns a collection of TreeNodes representing ///the display folders contained /// in the displayFolders parameter. These TreeNodes ///will be created if not found. /// An empty array will be returned if displayFolders ///does not contain any displayfolders. /// </summary> /// <param name="rootNodeCollection">nodes collection ///to use as the root of the KPI tree</param> /// <param name="displayFolders">the value of a ///DisplayFolders property</param> /// <param name="displayFolderImageIndex">the ///ImageIndex to use for a display folder tree node ///</param> <returns> /// An array of TreeNode objects representing the ///display folders contained in displayFolders. /// May return an empty array but will not return /// null. /// </returns> public static TreeNode[] GetDisplayFolderTreeNodes( TreeNodeCollection rootNodeCollection, string displayFolders, int displayFolderImageIndex ) { if ( string.IsNullOrEmpty( displayFolders ) ) { return new TreeNode[0]; } else { string[][] parsedDisplayFolders = ParseDisplayFolders( displayFolders ); int pathCount = parsedDisplayFolders.GetLength( 0 ); TreeNode[] displayFolderNodes = new TreeNode[pathCount]; for ( int pathIndex = 0; pathIndex < pathCount; pathIndex++ ) { string[] path = parsedDisplayFolders[pathIndex]; TreeNode folderNode = null; TreeNodeCollection parentNodeCollection = rootNodeCollection; foreach ( string folder in path ) { TreeNode[] nodesFound = parentNodeCollection.Find( folder, false ); if ( nodesFound.Length > 0 ) { folderNode = nodesFound[0]; } else { folderNode = parentNodeCollection.Add( folder, folder, displayFolderImageIndex, displayFolderImageIndex ); } parentNodeCollection = folderNode.Nodes; } displayFolderNodes[pathIndex] = folderNode; } return displayFolderNodes; } } /// <summary> /// Parses the value of a DisplayFolders property /// into an array of paths, each of which is /// an array of folder names. /// </summary> /// <param name="displayFolders">string containing /// the value of a DisplayFolders property</param> /// <returns> /// An array of arrays of strings. The outer array /// may be zero-length, but will not be null. /// The inner arrays will not be null or /// zero-length. Duplicate paths will be removed. /// </returns> public static string[][] ParseDisplayFolders( string displayFolders ) { const char pathDelimiter = ';'; const char folderDelimiter = '\\'; if ( displayFolders == null ) { return new string[0][]; } // Get paths int emptyPathCount = 0; string[] pathStrings = displayFolders.Split( pathDelimiter ); string[][] allPaths = new string[pathStrings.Length][]; for ( int i = 0; i < pathStrings.Length; i++ ) { // Get folders int emptyFolderCount = 0; string[] allFolders = pathStrings[i].Split( folderDelimiter ); for ( int j = 0; j < allFolders.Length; j++ ) { allFolders[j] = allFolders[j].Trim(); if ( allFolders[j].Length == 0 ) { emptyFolderCount++; } } // Get currentPath without any // empty folders string[] nonEmptyFolders; if ( emptyFolderCount == 0 ) { // This is the common case, // so optimize nonEmptyFolders = allFolders; } else if ( emptyFolderCount == allFolders.Length ) { nonEmptyFolders = null; emptyPathCount++; } else { nonEmptyFolders = new string [allFolders.Length emptyFolderCount]; for ( int j = 0, k = 0; j < allFolders.Length; j++ ) { if ( allFolders[j].Length > 0 ) { nonEmptyFolders[k] = allFolders[j]; k++; } } } // Remove duplicate paths if ( nonEmptyFolders != null && IsPathAlreadyInPathsArray( allPaths, nonEmptyFolders, i ) ) { nonEmptyFolders = null; emptyPathCount++; } allPaths[i] = nonEmptyFolders; } // Get results without any empty paths string[][] nonEmptyPaths; if ( emptyPathCount == 0 ) { // This is the common case, so optimize nonEmptyPaths = allPaths; } else if ( emptyPathCount == allPaths.Length ) { nonEmptyPaths = new string[0][]; } else { nonEmptyPaths = new string[allPaths.Length emptyPathCount][]; for ( int i = 0, j = 0; i < allPaths.Length; i++ ) { if ( allPaths[i] != null ) { nonEmptyPaths[j] = allPaths[i]; j++; } } } return nonEmptyPaths; } /// <summary> /// Takes a normalized value such as those associated /// with KPI Status and Trend and returns /// a numeric index of an image based on the /// normalized value. /// </summary> /// <param name="normalizedValue">normalized value /// between -1 and 1 (values less than -1 are treated /// as -1 and those greater than 1 are treated as /// 1)</param> /// <param name="firstImageIndex">value of the first /// image index</param> /// <param name="lastImageIndex">value of the last /// image index</param> /// <returns>An integer between firstImageIndex and /// lastImageIndex, inclusive</returns> public static int GetImageIndex( double normalizedValue, int firstImageIndex, int lastImageIndex ) { const double normalizedLowerBound = -1.0; const double normalizedUpperBound = 1.0; if ( double.IsNaN( normalizedValue ) ) { return firstImageIndex; } else if ( normalizedValue <= -1 ) { return firstImageIndex; } else if ( normalizedValue >= 1 ) { return lastImageIndex; } else { const double inputRange = normalizedUpperBound normalizedLowerBound; double outputRange = ( double )( Math.Abs( lastImageIndex firstImageIndex ) + 1 ); double outputSegmentsFromLowerBound = ( normalizedValue - normalizedLowerBound ) * ( outputRange / inputRange ); outputSegmentsFromLowerBound = Math.Round( outputSegmentsFromLowerBound, 10 ); // round off floating point errors int zeroBasedIndex = ( int )( ( normalizedValue > 0 ) ? Math.Floor( outputSegmentsFromLowerBound): // borders between segments (whole //numbers) belong to the preceeding // segment Math.Ceiling( outputSegmentsFromLowerBound ) - 1 ); // borders between segments (whole // numbers) belong to the following // segment return ( firstImageIndex < lastImageIndex )? firstImageIndex + zeroBasedIndex : firstImageIndex - zeroBasedIndex; } } #region ParseDisplayFolder helper methods /// <summary> /// Checks whether paths contains currentPath before /// currentIndex. Used by ParseDisplayFolders. /// </summary> private static bool IsPathAlreadyInPathsArray( string[][] paths, string[] currentPath, int currentIndex ) { if ( paths == null ) { return false; } int count = Math.Min( currentIndex, paths.GetLength( 0 ) ); for ( int i = 0; i < count; i++ ) { if ( DoPathsMatch( paths[i], currentPath ) ) { return true; } } return false; } /// <summary> /// Checks whether path1 and path2 match. /// </summary> private static bool DoPathsMatch( string[] path1, string[] path2 ) { if ( path1 == null && path2 == null ) { return true; } else if ( path1 == null || path2 == null ) { return false; } else if ( path1.Length != path2.Length ) { return false; } else { for ( int i = 0; i < path1.Length; i++ ) { if ( string.Compare( path1[i], path2[i], true,System.Globalization. CultureInfo.CurrentUICulture ) != 0 ) { return false; } } return true; } } #endregion ParseDisplayFolder helper methods } } |