I try to get the previous cells of a given range. So far my code looks like this:
Get the selected range and pass it to another method
Microsoft.Office.Interop.Excel._Application app = this.ExcelAppObj as Microsoft.Office.Interop.Excel._Application;
Microsoft.Office.Interop.Excel.Range range = null;
range = app.get_Range(this.DataRangeTextBox.Text);
var caption = ExcelHelper.GetRangeHeaderCaption(range);
The following method is executed
/// <summary>
/// Gets the range header caption of a given range.
/// The methode goes through every previous cell of the given range to determine the caption.
/// If the caption can not be determined the method returns an random string.
/// </summary>
/// <param name="selectedRange">The selected range.</param>
/// <returns></returns>
public static string GetRangeHeaderCaption(Range selectedRange, Microsoft.Office.Interop.Excel._Application excelApp)
{
// The caption of the range. The default value is a random string
var rangeCaption = ExcelHelper.getRandomString(5);
// Check if the provided range is valid
if (selectedRange != null && excelApp.WorksheetFunction.CountA(selectedRange) > 0)
{
var captionNotFound = true;
Range rangeToCheck = selectedRange.Previous;
// Go to each previous cell of the provided range
// to determine the caption of the range
do
{
// No further previous cells found
// We can stop further processing
if (rangeToCheck.Cells.Count == 0)
{
break;
}
//System.Array myvalues = (System.Array)rangeToCheck.Cells.Value;
System.Array myvalues = (System.Array)rangeToCheck.Cells.Value;
rangeToCheck = rangeToCheck.Previous;
} while (captionNotFound);
}
return rangeCaption;
}
At the point
var rangeToCheck = selectedRange.Previous;
the property access throws the following exception:
A first chance exception of type 'System.Runtime.InteropServices.COMException' occurred in mscorlib.dll
Additional information: Die Previous-Eigenschaft des Range-Objektes kann nicht zugeordnet werden.
What i want to achieve:
Go through all previous cells of the given data range containing double or int values and get the header caption by check if the previous cell is a numeric value or a string. If the cell contains a string return the string to the caller.
Edit #1
Maybe this is an important information. The method GetRangeHeaderCaption is implemented in another class. It is not included in the class where i get the the range by using the Excel Interop.
Edit #2
Found the problem.
The property Previous returns the previous LEFT cell of the given range. For example, if my range has the address B2:B16 the previous property returns the address A2. So if i try to access the Previous property of A2:A16 i get the exception because there is no column before the column A.
But what i need is that if i have the range B2:B16 i need to get the content of B1. Can you follow me so far?
Okay then, i used my brain and worked out a solution for this problem. Its a workaround and i don't know if there is another way or better way to solve this - see my code here:
using XLS = Microsoft.Office.Interop.Excel;
/// <summary>
/// Gets the range header caption of a given range.
/// The methode goes through every previous cell of the given range to determine the caption.
/// If the caption can not be determined the method returns an random string.
/// </summary>
/// <param name="selectedRange">The selected range.</param>
/// <returns></returns>
public static string GetRangeHeaderCaption(Range selectedRange, XLS._Application excelApp)
{
// The caption of the range. The default value is a random string
var rangeCaption = ExcelHelper.getRandomString(5);
// Check if the provided range is valid
if (selectedRange != null && excelApp.WorksheetFunction.CountA(selectedRange) > 0)
{
// Get the included cells of the range
var rangeCells = selectedRange.Address.Split(new char[] { ':' });
// Get the beginning cell of the range
var beginCell = rangeCells[0].Trim();
// Get the column and row data of the cell
var cellColumnRow = beginCell.Split(new char[] { '$' });
// Split the beginning cell into the column and the row
var cellColumn = cellColumnRow[1];
var cellRow = Convert.ToInt32(cellColumnRow[2]);
var captionNotFound = true;
int i = 0;
// Go to each previous cell of the provided range
// to determine the caption of the range
do
{
// Check if the next cell would be invalid
var nextCellRow = cellRow - i;
if (nextCellRow == 0)
break;
// Create the cell coordinates to look at
var cellToLook = string.Format("{0}{1}",
cellColumn,
nextCellRow);
// Get the value out of the cell
var cellRangeValue = ((XLS.Worksheet)((XLS.Workbook)excelApp.ActiveWorkbook).ActiveSheet).get_Range(cellToLook).Value;
// ...just to be sure it does not crash
if (cellRangeValue != null)
{
// Convert the determined value to an string
var cellValue = cellRangeValue.ToString();
double value;
// Check if the cell value is not a numeric value
// Check if the cell value is not empty or null
if (!double.TryParse(cellValue, out value) &&
!string.IsNullOrWhiteSpace(cellValue) &&
!string.IsNullOrEmpty(cellValue))
{
// In this case we found the caption
rangeCaption = cellValue;
captionNotFound = false;
}
}
i++;
} while (captionNotFound);
}
return rangeCaption;
}
Related
I am trying to make software to search for byte patterns, I have many bin files and many patterns to search. If the pattern exists on the file. it will represent it on CheckedListBox with a specific name . and if the checked box is checked for a particular one , it will replace the pattern with 0000000000 on the saved file.
For example, I have this pattern to search (note that I have more than 100 patterns to search):
{"2004940101000078", "3004940101000078", "3E04940101000028", .... ,.... }
Open the bin file by OpenFileDialog
Covert the file to byteArray
Search for the patterns
put the result on checkedlist Box
(2004940101000078 = P0420) (3004940101000078 = P0430) (3E04940101000028 =P043E),note P0420 is the name that i want to put on the checkedbox:
DTC
Description
Checkedbox P0420
Catalyst System Efficiency Below Threshold Bank1
Checkedbox P0430
Catalyst System Efficiency Below Threshold Bank2
Checkedbox P043E
EvaporativeEmissionSystemLeakDetectionReferenceOrificeLowFlow
If I want to delete the code P0420 , checkbox P0420 replace 2004940101000078 with 0000000000000000 and save it to a new bin file
I tried with this code to search for the patterns, but its give the offset position of the pattern only. please this code-only example and part of my codes.if you have a solution or other code or way. please help, I am new in C#
string hex2 = BitConverter.ToString(byteArray).Replace("-", string.Empty);
string[] patterns = {"2004940101000078", "3004940101000078", "3E04940101000028" };
foreach (string p in patterns)
{
int i = 0;
int indice = 0;
// teminate loop when no more occurrence is found;
while (indice != -1)
{
// index if the pattern is found AFTER i position, -1 if not
indice = hex2.IndexOf(p, i);
i = indice + 0; // skip the pattern occurrence itself
int indexxx = (i / 2);
//Transform the index into hexadecimal
string outputHex = int.Parse(indexxx.ToString()).ToString("X");
//Output the index as an hexadecimal offset address
MessageBox.Show("0x" + outputHex);
break;
}
}
thank you
You are checking the hex string versions of the byte arrays. This means
You can easily use IndexOf to find a match - great
You will use 4 times as much memory (one byte = two 16-bit chars)
You can have "false positives": searching for "1234" will find it in "512346" - you will have to check whether that can be a problem, or better, just guard against it (keep searching if you find an odd index)
If you want to remember where the match was and link it to certain buttons, don't use a List<string> but a List<PatternMatch> where PatternMatch is:
public class PatternMatch
{
public string HexPattern {get;set;} // the pattern to search for
public int HexIndex {get;set;} = -2; // the index where it was found, -1=not found, -2=not searched yet
public string Name {get;set;} // the button name, like P0420, P0430
public bool ToErase {get;set;} // whether to erase the matched pattern from the input
}
and then something like:
// the source to search in
string hex2 = BitConverter.ToString(byteArray).Replace("-", string.Empty);
// your patterns and button names
List<PatternMatch> patterns = new List<PatternMatch>();
patterns.Add(new PatternMatch { HexPattern = "2004940101000078", Name = "P0420" });
patterns.Add(new PatternMatch { HexPattern = "3004940101000078", Name = "P0430" });
patterns.Add(new PatternMatch { HexPattern = "3E04940101000028", Name = "P043E" });
// etc
foreach(var pattern in patterns)
{
// initialise search position
pattern.HexIndex = -1;
do
{
// try and find next match
pattern.HexIndex = hex2.IndexOf(pattern.HexPattern, pattern.HexIndex+1);
// repeat while there was something found, but at an odd index (false positive)
} while (pattern.HexIndex != -1 && index % 2 == 1);
// NB: in the original byte[] use half of pattern.HexIndex
if (pattern.HexIndex == -1)
Debug.WriteLine($"pattern {pattern.HexPattern} not found");
else
Debug.WriteLine($"pattern {pattern.HexPattern} found at byte index {pattern.HexIndex/2}");
}
Then you can use that "patterns" list to match/fill your checkboxes, using pattern.Name to find the corresponding checkbox.
When the checkbox is checked, set the corresponding pattern.ToErase to true.
You can do it by handling the "checked changed" event.
Assuming winforms with CheckBox. you can set the Tag property to the name (like "P0420"). Then you can find out what pattern this checkbox belongs to, independent of what you display.
Then you will need to handle the Click event for all checkboxes using one method:
private void PatternCheckbox_Click(object sender, System.EventArgs e)
{
var cb = (CheckBox)sender;
var name = (string)cb.Tag; // The type of Tag is 'object', so you need a cast
var pattern = patterns.Single(p => p.Name == name); // find the corresponding one
pattern.ToErase = cd.Checked; // set 'ToErase to match the checkbox state
}
Or when you use a CheckedListBox then, in your Save method, use its CheckedItems to get the checked items so you can set the corresponding ToErase flag:
// TODO fix some names to match your code
foreach(object itemChecked in checkedListBox1.CheckedItems)
{
// TODO cast 'itemChecked' to the correct type
var myitem = (MyItem)itemChecked;
// TODO get its name ("P0420" etc)
string name = myitem.Name;
var pattern = patterns.Single(p => p.Name == name); // find the corresponding pattern
pattern.ToErase = true; // the CheckedItems only returns checked items (and indeterminate)
}
Then you can start to perform the erasures.
When the user clicks "Save", you can loop through the patterns and see which have set ToErase to true. Then you can replace the corresponding HexPattern at the HexIndex that was found with zeroes.
Tip: new string('0', HexPattern.Length) has exactly enough 0's to cover that pattern.
This should do the erasures:
// process the marked patterns that correspond to a real match
foreach (var toErase in patterns.Where(p => p.ToErase && p.HexIndex >= 0))
{
// first remove the old pattern, then insert 0's
// (Replace would replace all occurrences, not just the one at the index you found)
hex2 = hex2.Remove(toErase.HexIndex, toErase.HexPattern.Length)
.Insert(toErase.HexIndex, new string('0', toErase.HexPattern.Length));
}
Note: There is quite a bit of text here but its additional information that may or may not be required. If I don't include this, then responders ask for more information or make suggestions for approaches that may not work, due to my entire implementation. If there is a better way to structure this so that all information is available, please let me know the best way to provide this supporting information. The gist is that I am trying to is here:
I am building a program that will read files and determine an organization (internal org code) to associate each file to, based on a predefined list of orgs and how often each org shows up in the file. The basic premise and current manual solution is:
Manual Process
Receive files in an unusual but overall consistent data format.
Each file has data pertaining to an organization's transactions (inbound and outbound types)
Each row in the file represents a single transaction; however, the fields are not always in the same order.
Goal is to determine which org "owns" the transactions (the entire file will be associated with this org at the end)
To be determined as the transaction "owner", the org in question must show up the same amount of times on each row (e.g. if Org1 shows up twice on Row 1 and once on Row 2, it can't be our Org due to inconsistency).
Nearly 100% of the time, the correctly associated Org has the highest frequency in the file, consistent across every row checked so programming this will give us a 99% solution. We can refine it further, if needed, once these rules are implemented
So far in my project, I have implemented a class with the following members:
My class thus far (summary of all logic at this point):
Properties/fields
_orgArray - list of orgs to look for in each file
_orgLength - length of org code (used for iterating over character in file line and creating a buffer of specified length to check if buffer value contains org)
Methods
GetDictIntersects - accepts two dictionaries and returns a dictionary containing keys and values that exist in both input dictionaries
GetLinesFromFile - iterator that reads a file and yields a single lines from the file each iteration
GetOrgCounts - accepts a line of text (returned from GetLinesFromFile) and returns a dictionary containing each Org and its count of occurrences in the line of text
ReduceOrgArray - accepts a two dictionaries containing Org Counts (returned by GetOrgCounts) as well as an array of Orgs. Returns a new array of orgs having removed any entries that did not have consistencies amongst them (using GetDictIntersects) as well as any orgs that did not have any values > 0 (indicating no occurrences in lines of text)
What the DetermineOrg method should do
To determine an Org for each file, I'm working on a method named DetermineOrg. My goal is to utilize the above methods and properties to
starts with org array containing all potential orgs
iterates the specified file (using GetLinesFromFile)
reduces org array by removing orgs that don't meet criteria on each line by comparing the current line's org counts (returned from GetOrgCounts) with the previous line's org counts (using GetDictIntersects and ReduceOrgArray)
Once file is iterated, if only a single Org remains in array, that is the return value
If multiple Orgs remain, Org with highest count is returned. If there is a tie, then Org (that tied with Max count) that shows up first in _orgArray is the return value
However, I can't seem to figure out the logic of how structure this so that I can recursively implement this. My code for the method alone in question (in case you don't want the entire class) is below. Hopefully someone can point out what I'm doing incorrectly and point me in the right direction:
Stuck on this method:
/// <summary>
/// Determine the appropriate organization for specified file
/// Recursively apply the following business rules:
/// <list type="number">
/// <item>Initial run uses all Orgs</item>
/// <item>Get Org Counts for each org on a line-by-line basis</item>
/// <item>Compare each line's Org Counts with the previous line's Org Counts, removing any orgs from potential org list that do not have same counts on both lines</item>
/// <item>After entire file has been read, determine a single Org by identifying which Org has the most occurences (highest value in dict)</item>
/// <item>In case of ties, class member org array lists order of precedence. Org with lowest index takes precedence.</item>
/// </list>
/// </summary>
/// <param name="filePath"><c>string</c> - file to be processed</param>
/// <param name="numLines"><c>int</c>:
/// Number of lines to be read from file in order to determine associated org.
/// Value less than 1 indicates to read the entire file.
/// Default value is -1 (read entire file to determine associated org).
/// </param>
/// <param name="orgArray"><c>string[]</c> representing potential orgs that file may be associated with</param>
/// <param name="streamReader"><c>StreamReader</c> stream to specified file (read-only)</param>
/// <param name="prevOrgCounts"><c>int</c> representing Org Counts for previous line of text</param>
/// <returns><c>string</c> - represents org that file is associated with</returns>
public static string DetermineOrg(string filePath, int numLines = -1, string[] orgArray = null, IEnumerable<string> streamReader = null, Dictionary<string, int> prevOrgCounts = null)
{
// base condition - no streamreader exists yet
if (streamReader == null)
{
streamReader = GetLinesFromFile(filePath, numLines);
// if no orgArray value is set, then use class member as starting value
if (orgArray == null)
{
orgArray = _orgArray;
}
}
else
{
// get org counts from iterator
foreach (string line in streamReader)
{
Dictionary<string, int> currentOrgCounts = GetOrgCounts(line, orgArray);
// if we have previous and current counts, then get reduce orgs
if (prevOrgCounts != null)
{
orgArray = ReduceOrgArray(currentOrgCounts, prevOrgCounts, orgArray);
}
else
{
}
}
}
// base condition - if no counts yet, then get counts from filePath
if (prevOrgCounts == null)
{
foreach (string line in GetLinesFromFile(filePath, numLines))
{
prevOrgCounts = GetOrgCounts(filePath, _orgArray);
}
}
}
Entire Class / All Code
The entire class can be found below.
using System;
namespace OrgProcessor
{
internal class OrgProcessor
{
/// <summary>
/// <c>string[]</c> containing list of orgs in order of precedence. In case of ties between counts, order with lowest index takes precedence in org determination.
/// </summary>
static string[] _orgArray = { "Org1", "Org2", "Org3"};
/// <summary>
/// Length of Org (is consistent amongst orgs as each org is an "org code" representing an org that can be found in a lookup table.
/// </summary>
static byte _orgLength = 4;
/// <summary>
/// Compare 2 dictionaries and return a dictionary containing only keys and values that exist in both dictionaries.
/// </summary>
/// <param name="dictionary1"><c>Dictionary<string, int></c></param>
/// <param name="dictionary2"><c>Dictionary<string, int></param>
/// <returns><c>Dictionary<string, int> - New dictionary containing key-value pairs that exist in both input dictionaries</returns>
public static Dictionary<string, int> GetDictIntersects(Dictionary<string, int> dictionary1, Dictionary<string, int> dictionary2)
{
// only return entries that exist in both dictionaries
Dictionary<string, int> returnDict = new Dictionary<string, int>();
foreach (dynamic key in dictionary1.Keys)
{
// ensure key exists in other dictionary's keys AND values match in both dictionaries
if (dictionary2.ContainsKey(key))
{
if (dictionary2[key] == dictionary1[key])
{
returnDict.Add(key, dictionary1[key]);
}
}
}
return returnDict;
}
/// <summary>
/// Iterator method returning file content, line by line
/// </summary>
/// <param name="filePath"><c>string</c> - path to file to read from</param>
/// <param name="numLines"><c>int</c> - number of lines to read from the file. Negative numbers will be interpreted as "All Lines". Default value is -1 (Read "All" lines)</param>
/// <returns><c>IEnumerable</c><<c>string</c>></returns>
public static IEnumerable<string> GetLinesFromFile(string filePath, int numLines = -1)
{
// TODO: Make more generic so iterator can take instructions to manipulate lines in file
// and optionally write to file (would need to write to temp, then delete orig and rename temp
// track lines iterated
int i = 0;
// create reader
using (StreamReader reader = new StreamReader(filePath))
{
// yield line if not reached end of file AND
// num lines is not specified (i == -1) OR
// i (num lines return) has not exceeded num lines specified
while (!reader.EndOfStream && (i < numLines || numLines == -1))
{
yield return reader.ReadLine();
i++;
}
}
}
/// <summary>
/// Get number of times each org occurs in specified text
/// </summary>
/// <param name="lineOfText"><c>string</c> of text to process</param>
/// <param name="orgArray"><c>string[]</c> containing orgs to be counted</param>
/// <returns><c>Dictionary<string, int></c> containing each Org and number of occurences in specified text</returns>
public static Dictionary<string, int> GetOrgCounts(string lineOfText, string[] orgArray)
{
// instantiate return value
Dictionary<string, int> orgCounts = new Dictionary<string, int>();
// get length of line of text as it will be referenced multiple times
int textLength = lineOfText.Length;
foreach (string org in orgArray)
{
//// set matchCount to 0
//// int matchCount = 0;
// since orgs are 4 characters long, iterate each character and compare with next 3 characters for each
for(int i = 0; i < textLength; i ++)
{
// get character at index
char c = lineOfText[i];
// calculate remaining characters
int remainingChars = textLength - i;
// char can only be part of an org if enough characters remain in lineOfText
if (remainingChars >= _orgLength)
{
// Get amount of chars that equals org length
string curCharBuffer = lineOfText.Substring(i, _orgLength);
// if characters match current org, then increment count dictionary
// or add to dict with count of 1
if (curCharBuffer == org)
{
if (orgCounts.ContainsKey(org))
{
orgCounts[org] += 1;
}
else
{
orgCounts.Add(org, 1);
}
// no need to evaluate other characters that were part of org so adjust loop incrementer to start next iteration after buffer
i += curCharBuffer.Length - 1;
}
}
}
//// orgCounts[org] = matchCount;
}
return orgCounts;
}
/// <summary>
/// Accepts 2 dictionaries (containing orgs and associated counts) and an array of strings (representing potential orgs) and returns a new string array having removed any "invalid" strings based on specified business rules:
/// <list type="number">
/// <item>If no orgs exist in array, return empty array</item>
/// </list>
/// </summary>
/// <param name="dictA"><c>Dictionary<string, int> containing orgs and counts</c></param>
/// <param name="dictB"><c>Dictionary<string, int> containing orgs and counts</c></param>
/// <param name="orgArray"><c>string[]</c> portential orgs</param>
/// <returns><c>string[]</c> - represents potential orgs</returns>
public static string[] ReduceOrgArray(Dictionary<string, int> dictA, Dictionary<string, int> dictB, string[] orgArray)
{
// base condition - if orgArray is empty, then return as is
if (orgArray.Length == 0)
{
return orgArray;
}
// Return value
List<string> remainingOrgs= new List<string>();
// business rules require that org counts be same from record to record
// we can rule out potential orgs by removing those with inconsistent counts amongst records
Dictionary<string, int> remainingDict = GetDictIntersects(dictA, dictB);
// Iterate over remaining orgs
// remove those that don't have matching keys in remaining dict (showing inconsistency amongst recs)
// remove those with 0 counts (org must exist on all rows)
foreach (string org in orgArray)
{
if (remainingDict.ContainsKey(org))
{
if (remainingDict[org] > 0)
{
remainingOrgs.Add(org);
}
}
}
// return remainingOrgs as array
return remainingOrgs.ToArray();
}
/// <summary>
/// Determine the appropriate organization for specified file
/// Recursively apply the following business rules:
/// <list type="number">
/// <item>Initial run uses all Orgs</item>
/// <item>Get Org Counts for each org on a line-by-line basis</item>
/// <item>Compare each line's Org Counts with the previous line's Org Counts, removing any orgs from potential org list that do not have same counts on both lines</item>
/// <item>After entire file has been read, determine a single Org by identifying which Org has the most occurences (highest value in dict)</item>
/// <item>In case of ties, class member org array lists order of precedence. Org with lowest index takes precedence.</item>
/// </list>
/// </summary>
/// <param name="filePath"><c>string</c> - file to be processed</param>
/// <param name="numLines"><c>int</c>:
/// Number of lines to be read from file in order to determine associated org.
/// Value less than 1 indicates to read the entire file.
/// Default value is -1 (read entire file to determine associated org).
/// </param>
/// <param name="orgArray"><c>string[]</c> representing potential orgs that file may be associated with</param>
/// <param name="streamReader"><c>StreamReader</c> stream to specified file (read-only)</param>
/// <param name="prevOrgCounts"><c>int</c> representing Org Counts for previous line of text</param>
/// <returns><c>string</c> - represents org that file is associated with</returns>
public static string DetermineOrg(string filePath, int numLines = -1, string[] orgArray = null, IEnumerable<string> streamReader = null, Dictionary<string, int> prevOrgCounts = null)
{
// base condition - no streamreader exists yet
if (streamReader == null)
{
streamReader = GetLinesFromFile(filePath, numLines);
// if no orgArray value is set, then use class member as starting value
if (orgArray == null)
{
orgArray = _orgArray;
}
}
else
{
// get org counts from iterator
foreach (string line in streamReader)
{
Dictionary<string, int> currentOrgCounts = GetOrgCounts(line, orgArray);
// if we have previous and current counts, then get reduce orgs
if (prevOrgCounts != null)
{
orgArray = ReduceOrgArray(currentOrgCounts, prevOrgCounts, orgArray);
}
else
{
}
}
}
// base condition - if no counts yet, then get counts from filePath
if (prevOrgCounts == null)
{
foreach (string line in GetLinesFromFile(filePath, numLines))
{
prevOrgCounts = GetOrgCounts(filePath, _orgArray);
}
}
}
}
}
If I understood your code correctly, you only keep track of the current list of candidates, but not the ocurrence count so you have nothing to compare to.
Here is my solution:
string[] orgs = { "ORG1", "ORG2", "ORG3"};
// This array will hold
// - either the number of times the corresponding organisation name occurs in each line
// - or 0 if the corresponding organisation name does not occur in any line
// or occurs with different count in some lines
var orgCounts = new int[orgs.Length];
var readLines = 0; // the number of lines read so far (used to recognize the first line)
foreach (var line in File.ReadLines(fileName))
{
for (var i = 0; i < orgs.Length; i++)
{
// count the occurrences of orgs[i] in the read line
var count = CountOccurrences(line, orgs[i]);
if (readLines == 0)
{
// first line: just remember the count of occurrences
orgCounts[i] = count;
}
else if (orgCounts[i] != count)
{
// mismatch, set count to 0
orgCounts[i] = 0;
}
readLines++;
}
}
// helper function to count the occurrences of toFind in str
int CountOccurrences(string str, string toFind)
{
var count = 0;
var index = str.IndexOf(toFind);
while (index >= 0)
{
count++;
index = str.IndexOf(toFind, index+1);
}
return count;
}
This is the solution that worked. This was basically the final piece to the puzzle and now everything fits together:
public static string DetermineOrg(string filePath)
{
// Initialize variables to store previous line's org counts and current list of potential orgs
Dictionary<string, int> previousLineOrgCounts = null;
string[] potentialOrgs = _orgArray;
// Iteratve over the file's lines using the GetLinesFromFile method
foreach(string line in GetLinesFromFile(filePath))
{
// Get the org counts for the current line
Dictionary<string, int> currentLineOrgCounts = GetOrgCounts(line, potentialOrgs);
// Check if this is not the first line of the file
if (previousLineOrgCounts != null)
{
// reduce the list of potential orgs using the current and previous line org counts
potentialOrgs = ReduceOrgArray(previousLineOrgCounts, currentLineOrgCounts, potentialOrgs);
}
// update the previous line or g counts for the next iteration
previousLineOrgCounts = currentLineOrgCounts;
}
// Check if only one org remains in the potential orgs list
if(potentialOrgs.Length == 1)
{
// return the remaining org
return potentialOrgs[0];
}
else
{
// Initialize variables to store the max org count and the org with the max count
int maxOrgCount = 0;
string orgWithMaxCount = null;
// Iterate over the remaining potential orgs
foreach (string org in potentialOrgs)
{
// Check if the current org has a higher count that the previous max count
if (previousLineOrgCounts[org] > maxOrgCount)
{
// update the max org count and the org with the max count
maxOrgCount = previousLineOrgCounts[org];
orgWithMaxCount = org;
}
}
// Return the org with the max count
return orgWithMaxCount;
}
}
I have created a 1x3 table as my header in word. This is how I want it to look like.
LeftText MiddleText PageNumber:
I want the PageNumber cell to look like this -
Page: X of Y
I have managed to do cell (1,1) and (1,2). I found this to help me with cell (1,3) but it is not working as I like. I know how to get the total count of the document. I'm not sure how to implement it properly.
Range rRange = restheaderTable.Cell(1, 3).Range;
rRange.End = rRange.End - 1;
oDoc.Fields.Add(rRange, Type: WdFieldType.wdFieldPage, Text: "Page Number: ");
I can't even get the Text "Page Number: " to display in the cell. All it has is a number right now.
The field enumeration you're looking for is WordWdFieldType.wdFieldNumPages.
The next hurdle is how to construct field + text + field as Word doesn't behave "logically" when things are added in this order. The target point remains before the field that's inserted. So it's either necessary to work backwards, or to move the target range after each bit of content.
Here's some code I have the demonstrates the latter approach. Inserting text and inserting fields are in two separate procedures that take the target Range and the text (whether literal or the field text) as parameters. This way the field code can be built up logically (Page x of n). The target Range is returned from both procedures, already collapsed to its end-point, ready for appending further content.
Note that I prefer to construct a field using the field's text (including any field switches) rather than specifying a field type (the WdFieldType enumeration). This provides greater flexibility. I also highly recommend setting the PreserveFormatting parameter to false as the true setting can result in very odd formatting when fields are updated. It should only be used in very specific instances (usually involving linked tables).
private void btnInsertPageNr_Click(object sender, EventArgs e)
{
getWordInstance();
Word.Document doc = null;
if (wdApp.Documents.Count > 0)
{
doc = wdApp.ActiveDocument;
Word.Range rngHeader = doc.Sections[1].Headers[Microsoft.Office.Interop.Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
if (rngHeader.Tables.Count > 0)
{
Word.Table tbl = rngHeader.Tables[1];
Word.Range rngPageNr = tbl.Range.Cells[tbl.Range.Cells.Count].Range;
//Collapse the range so that it's within the cell and
//doesn't include the end-of-cell markers
object oCollapseStart = Word.WdCollapseDirection.wdCollapseStart;
rngPageNr.Collapse(ref oCollapseStart);
rngPageNr = InsertNewText(rngPageNr, "Page ");
rngPageNr = InsertAField(rngPageNr, "Page");
rngPageNr = InsertNewText(rngPageNr, " of ");
rngPageNr = InsertAField(rngPageNr, "NumPages");
}
}
}
private Word.Range InsertNewText(Word.Range rng, string newText)
{
object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;
rng.Text = newText;
rng.Collapse(ref oCollapseEnd);
return rng;
}
private Word.Range InsertAField(Word.Range rng,
string fieldText)
{
object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;
object unitCharacter = Word.WdUnits.wdCharacter;
object oOne = 1;
Word.Field fld = rng.Document.Fields.Add(rng, missing, fieldText, false);
Word.Range rngField = fld.Result;
rngField.Collapse(ref oCollapseEnd);
rngField.MoveStart(ref unitCharacter, ref oOne);
return rngField;
}
I have VSTO application that outputs rows into excel. I am trying to apply some formatting based on values in a cell. My requirement is if the column 'V' contains a value "No" in the cell, the entire row that has values in it needs to be of a background colour "light grey". Note: Column V is a drop down list of either yes or no in excel. So its not a normal text based cell. Any ideas on how to do this?
var last = uiWorksheet.Cells.SpecialCells(XlCellType.xlCellTypeLastCell, Type.Missing);
var lastUsedRow = last.Row < 6 ? 6 : last.Row;
var lastUsedColumn = last.Column;
var xlrange = uiWorksheet.get_Range("V:V");
if (xlrange.Value2.ToString() == "No")
{
???.EntireRow.Interior.Color = Microsoft.Office.Interop.Excel.XlRgbColor.rgbLightGray;
}
In order to look for a value in the column, you need to use Find and FindNext functions.
After it, you need to iterate over the results and change the color.
Please note following from MSDN:
The FindNext method's search wraps back to the beginning of the search
range after it has reached the end of the range. Your code must ensure
that the search does not wrap around in an infinite loop.
var xlrange = uiWorksheet.get_Range("V:V");
var searchFor = "No";
Range firstResult = null;
var currentResult = xlrange.Find(What: searchFor,
LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole,
SearchOrder: XlSearchOrder.xlByRows
);
while(currentResult != null)
{
if (firstResult == null)
firstResult = currentResult;
else if (currentResult.get_Address(XlReferenceStyle.xlA1)
== firstResult.get_Address(XlReferenceStyle.xlA1))
{
break;
}
currentResult.EntireRow.Interior.Color = XlRgbColor.rgbLightGray;
currentResult = xlrange.FindNext(currentResult);
}
when outputting to the console, you can set the specific location of the cursor and write to that (or use other nifty tricks like printing backspaces that will take you back.)
Is there a similar thing that can be done with a stream of text?
Scenario: I need to build a string with n pieces of, text where each might be on a different line and start position (or top and left padding).
Two strings might appear on the same line.
I could build a simple Dictionary<int, StringBuilder> and fidget with that, but I'm wondering if there's something like the console functionality for streams of text where you can write to a specific place (row and column).
Edit:
This is for a text only. No control.
The result might be a string with several new lines, and text appearing at different locations.
Example (where . will be white spaces):
..... txt3....... txt2
......................
................ txt1.
this will be the result of having txt1 at row 3 column (whatever), and txt2 and txt3 and row 1 with different colum values (where txt3 column < txt2 colmun)
While waiting for a better answer, here's my solution. Seems to work, been lightly tested, and can be simply pasted into linqpad and run.
void Main()
{
m_dict = new SortedDictionary<int, StringBuilder>();
AddTextAt(1,40, "first");
AddTextAt(2,40, "xx");
AddTextAt(0,10, "second");
AddTextAt(4,5, "third");
AddTextAt(1,15, "four");
GetStringFromDictionary().Dump();
}
// "global" variable
SortedDictionary<int, StringBuilder> m_dict;
/// <summary>
/// This will emulate writting to the console, where you can set the row/column and put your text there.
/// It's done by having Dictionary(int,StringBuilder) that will use to store our data, and eventually,
/// when we need the string iterate over it and build our final representation.
/// </summary>
private void AddTextAt(int row, int column, string text)
{
StringBuilder sb;
// NB: The following will initialize the string builder !!
// Dictionary doesn't have an entry for this row, add it and all the ones before it
if (!m_dict.TryGetValue(row, out sb))
{
int start = m_dict.Keys.Any() ? m_dict.Keys.Last() +1 : 0;
for (int i = start ; i <= row; i++)
{
m_dict.Add(i, null);
}
}
int leftPad = column + text.Length;
// If dictionary doesn't have a value for this row, just create a StringBuilder with as many
// columns as left padding, and then the text
if (sb == null)
{
sb = new StringBuilder(text.PadLeft(leftPad));
m_dict[row] = sb;
}
// If it does have a value:
else
{
// If the new string is to be to the "right" of the current text, append with proper padding
// (column - current string builder length) and the text
int currrentSbLength = sb.ToString().Length;
if (column >= currrentSbLength)
{
leftPad = column - currrentSbLength + text.Length;
sb.Append(text.PadLeft(leftPad));
}
// otherwise, text goes on the "left", create a new string builder with padding and text, and
// append the older one at the end (with proper padding?)
else
{
m_dict[row] = new StringBuilder( text.PadLeft(leftPad)
+ sb.ToString().Substring(leftPad) );
}
}
}
/// <summary>
/// Concatenates all the strings from the private dictionary, to get a representation of the final string.
/// </summary>
private string GetStringFromDictionary()
{
var sb = new StringBuilder();
foreach (var k in m_dict.Keys)
{
if (m_dict[k]!=null)
sb.AppendLine(m_dict[k].ToString());
else
sb.AppendLine();
}
return sb.ToString();
}
Output:
second
four first
xx
third
No. Text files don't really have concept of horizontal/vertical position, so you'd need to build some sort of positioning yourself.
For basic positioning tabs ("\t") may be enough, for anything more advanced you'd need to fill empty space with spaces.
It sounds like you have some sort of table layout - it may be easier to build data in cells first (List<List<string>> - list of rows consisting of columns of strings) and than format it with either String.Format("{0}\t{1}\t...", table[row][0],table[row][1],...) or manually adding necessary amount of spaces for each "cell"