Office.Interop.Word and Lists with SubLevels - c#

I've been scouring the interwebs for documentation leading me to be able to create lists. In doing so, I've not really been able to find any documentation that will allow me to create `lists within lists.
I've tried using the built-in macro recorder, but for whatever reason, it behaves differently when recording vs. when not recording (e.g. when I create a list item, and hit enter + tab, it doesn't create a sub list).
I've found "The Wordmeister's" MSDN post which helped me get to making a list, but lists within lists doesn't work so well for me.
Word.Paragraph p2 = doc.Paragraphs.Add();
Word.Range p2rng = p2.Range;
object oTrue = true;
object oFalse = false;
object oListName = "TreeList";
Word.ListTemplate lstTemp = doc.ListTemplates.Add(ref oTrue, ref oListName);
int l;
p2rng.ParagraphFormat.TabIndent(1);
p2rng.Text = "Rates:\r\nLevel 1\rLevel 1.1\rLevel 1.2\rLevel 2\rLevel 2.1\rLevel 2.1.1";
l = 1;
lstTemp.ListLevels[l].NumberFormat = "%" + l.ToString() + ".";
lstTemp.ListLevels[l].NumberStyle = Word.WdListNumberStyle.wdListNumberStyleArabic;
lstTemp.ListLevels[l].NumberPosition = wordApp.CentimetersToPoints(0.5f * (l - 1));
lstTemp.ListLevels[l].TextPosition = wordApp.CentimetersToPoints(0.5f * l);
l = 2;
lstTemp.ListLevels[l].NumberFormat = "%" + (l - 1).ToString() + ".%" + l.ToString() + ".";
lstTemp.ListLevels[l].NumberStyle = Word.WdListNumberStyle.wdListNumberStyleArabic;
lstTemp.ListLevels[l].NumberPosition = wordApp.CentimetersToPoints(0.5f * (l - 1));
lstTemp.ListLevels[l].TextPosition = wordApp.CentimetersToPoints(0.5f * l);
l = 3;
lstTemp.ListLevels[l].NumberFormat = "%" + (l - 2).ToString() + "%" + (l - 1).ToString() + ".%" + l.ToString() + ".";
lstTemp.ListLevels[l].NumberStyle = Word.WdListNumberStyle.wdListNumberStyleArabic;
lstTemp.ListLevels[l].NumberPosition = wordApp.CentimetersToPoints(0.5f * (l - 1));
lstTemp.ListLevels[l].TextPosition = wordApp.CentimetersToPoints(0.5f * l);
object oListApplyTo = Word.WdListApplyTo.wdListApplyToWholeList;
object oListBehavior = Word.WdDefaultListBehavior.wdWord10ListBehavior;
p2rng.ListFormat.ApplyListTemplate(lstTemp, ref oFalse, ref oListApplyTo, ref oListBehavior);
All credit to Cindy Meister for this code, it is only slightly modified to work for my use case.
The above results in the following:
Basically, how do you create multi level lists (like the following image) with lists within lists?

It's not really possible to create a "list within a list" in Word.
What you can do is put static text in the list level, in front of the dynamic number. This is the same idea as using "Chapter" or "Section" in front of the level's number, except in this case it should be a bullet symbol.
The place to define this, based on the code sample in the question, is:
lstTemp.ListLevels[l].NumberFormat = "%" + l.ToString() + ".";
As part of the NumberFormat string, in other words. For symobls this will require a conversion from unicode hex or decimal to the String data type. For example, for a solid, round bullet and an outline, round bullet for levels 1 and 2, respectively (hard-coding the list level for purposes of clarity):
char Symbol1 = (char)9679;
char Symbol2 = (char)9675;
lstTemp.ListLevels[1].NumberFormat = Symbol1.ToString() + "\t%" + l.ToString() + ".";
lstTemp.ListLevels[2].NumberFormat = Symbol2.ToString() + "\t%" + 2.ToString() + ".";

Lets try providing an answer that doesn't get deleted.
The updated list within lists example provided by #Jaberwocky can be achieved using the technique I explained in a previous post.
MS-Word: Cross reference to custom reference type
To apply the above to the specific instance required by #Jaberwock we need to amend the number formats of the list templates to which styles are linked. I'll use Word to set up the styles and required multilevel list and then include a short VBA macro which shows how to amend the list number format.
In line with the link above we first need to create our styles. The emulate the list within list example above we need to define two styles. I've defined 'ListWithinList 1' and 'ListWithinList 2'.
The key settings for these two styles are to set the outline level as 1 and 2 respectively, and to set appropriate tab stops. I've used tabs at 1,2,3 and 4 cm. Add some text to a word document and apply the styles. I've included the navigation pane in the picture below so that we can see the indentation due to the outline level of the styles
The next step is to define the multilevel list and link each level to the relevant style
Settings for outline level 1
settings for outline level 2
Our text now looks like this
I've used Word up to this point to avoid the tediousness of setting up styles and list templates programatically.
Let's now modify the format of the list numbering using a snippet of VBA.
Option Explicit
Public Sub AddTextToListNumber()
Dim my_prefix(1 To 2) As String
Dim my_index As Long
my_prefix(1) = ChrW(&H25AA) & vbTab ' small black square
my_prefix(2) = ChrW(&H25AB) & vbTab ' small white square
For my_index = 1 To 2
With ActiveDocument.Styles("ListWithinList " & CStr(my_index)).ListTemplate.ListLevels(my_index)
.numberformat = my_prefix(my_index) & .numberformat
End With
Next
End Sub
If we run the code above then the text in our document becomes
Which looks a bit ugly because of the 1cm tab stops.
If there is anything that isn't clear above please add a comment and if possible I'll update the answer.
NOTE: We didn't need the VBA code to complete setting up the list formats as we could have used appropriate Alt+XXXX keyboard sequences to insert the characters in the number format box of the multilevel list dialog box.

Related

How do I change Color of Label by getting the Color from another Label?

I am trying to do something similar to the one in the example but changing the colour of the text/label.
Example:
Label1.Text = Label2.Text;
Label2.Text = Label3.Text;
Label3.Text = Label4.Text;
//so and and so forth
I am looking for a very simple solution like the one shown in the example but with colors.
I am using Windows Form App with .Net Framework 4.7.2, I am using C# 9.0.
I have tried using .ForceColor as a way to change the color but as you can see, i am trying to accomplish a similar thing as the one in the example.
At first, I suggest put labels in a container, And don't put anything except labels.
Then:
Do for loop, and length = ContainerPanel.Controls.Count - 1, with condition i = ContainerPanel.Controls.Count
Cast Control[i] to labelFirst
Cast Control[i + 1] to labelSecond
labelFirst.ForeColor = labelSecond.ForeColor
In this example, Container is: ContainerPanel
for (int i = 0; i < ContainerPanel.Controls.Count - 1; i++)
{
// This is a simple way without variables
(Label(ContainerPanel.Controls[i])).ForeColor = (Label(ContainerPanel.Controls[i + 1]));
}
Note: last Label You should change its ForeColor, Or it will not be changed.
Q&A
Q: Why length = ContainerPanel.Controls.Count - 1??
A: Because we should get Cast Control[i + 1] to labelSecond, And we can't, except if the length is less than controls count by one

Interop Word - Insert Mergefield to the End of the Range [duplicate]

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;
}

How to append the suffix to be unique if there is a record with the same name in the database?

I'm in trouble with a little problem.
products are listed in the database. In the code column, the data is kept as follows:
"F-01, F-02, F-03, ..., X-99"
Customers may want to add external data to the system, but sometimes they want to add the same record.
To avoid these duplicates, I need to put a distinctive suffix like "-01" at the end of the line of code column.
I am developing this system in c # and I have developed such a logic:
int counter = 1;
var code = "F-" + productEntity.Code;
​
while (await uow.Repository<Product>().Query().AnyAsync(a => a.Code == code) == true)
{
code = code + "-" + counter;
counter++;
}
But what I noticed is that a record I added in the form of F-01-1 will turn into F-01-1-1 when it comes back to me.
I think I missed a very small point, but I can not reach my goal.
The expression code + "-" + counter; appends 3 strings together and return the result.
Say:
code is "F-01"
counter is 1
Then the expression above will be evaluated as such:
"F-01" + "-" + "1", that is "F-01-1"
If you reexecute your method, then the expression will be:
"F-01-1" + "-" + "1" that is "F-01-1-1"
And so on.
The solution is to split the code to extract the last part, which corresponds to the value of the counter. It is done with string[] parts = "F-01-1".Split('-'):
parts[0] = "F"
parts[1] = "01"
parts[2] = "1" which corresponds to the counter
So now int counter = int.Parse(parts[2]) + 1 will return the next value for counter 2.
And you can have your full code "F-01-2" returned by code = $"{parts[0]}-{parts[1]}-{counter}"

Programmatically check textboxes for completeness

I am looking for a way to figure out if textboxes are filled in in a specific way.
I have the following textboxes:
X1 Y1 Z1
X2 Y2 Z2
X3 Y3 Z3
Users are expected to input into them continuously. For example, if only X1,Y1 have texts, it's valid. While if X1,Z1 have texts while Y1 hasn't, it's invalid.
The reason for this is because:
string G1
if(!string.IsNullOrWhiteSpace(X1.Text) && string.IsNullOrWhiteSpace(Y1.Text))
{
G1 = "<" + X1.Text + ">";
}
else if(!string.IsNullOrWhiteSpace(X1.Text) && !string.IsNullOrWhiteSpace(Y1.Text))
{
G1 = "<" + X1.Text + ">, ";
}
If they skip around then what is printed in the end will not be what is expected.
If you can think of an easier way to do all of this, please share. I am sure that my way of attacking this problem is quite simplistic and inefficient.
The answer I found is to use an array of the text boxes, then to iterate over that to determine the text of the filled out boxes. Then add the text from those boxes to a list.
Finally I used joined all of the items of that list, in a way that made the final output correct.
Code I used in the end:
string[] cells = new string[] { X1.Text, Y1.Text, Z1.Text, X2.Text, Y2.Text, Z2.Text, X3.Text, Y3.Text, Z3.Text };
List<string> final_cells = new List<string>();
//Process array to determine which cells are full, adds full cells to list
for (int i = 0; i < cells.Length; i++)
{
if(cells[i].Length == 0)
{
//If cell is empty, continue to the next one
continue;
}
else
{
// If cell is full add its contents to the final_cells list
final_cells.Add(cells[i]);
}
}
//Join the list to one string
string content = string.Join(">, <", final_cells);
Also I am sorry that I did not better explain the original situation.
The purpose of the final application is to output text in a specific format for a minecraft plugin to read. The 3x3 of text boxes is meant to mimic the crafting grid from the game.
The type of recipe this part of the application is supposed to add does not need to use 9 items/blocks, and is not shaped (so it doesn't technically matter where the user inputs each item name). Because of this, the empty boxes have to be completely ignored, and the program needs to know if there is something coming next, so I could not simply say that the recipe ended at the first empty box.
An output of <item1> <item2> is not valid, the correct format is <item1>, <item2>. However the input can also be a single item... so this <only_item> is a valid format.
I hope it is clear both what the function of this is, but also why this was important for my application.

Using c# to read from a text file

Am reading from a text file using the code below.
if (!allLines.Contains(":70"))
{
var firstIndex = allLines.IndexOf(":20");
var secondIndex = allLines.IndexOf(":23B");
var thirdIndex = allLines.IndexOf(":59");
var fourthIndex = allLines.IndexOf(":71A");
var fifthIndex = allLines.IndexOf(":72");
var sixthIndex = allLines.IndexOf("-}");
var firstValue = allLines.Substring(firstIndex + 4, secondIndex - firstIndex - 5).TrimEnd();
var secondValue = allLines.Substring(thirdIndex + 4, fourthIndex - thirdIndex - 5).TrimEnd();
var thirdValue = allLines.Substring(fifthIndex + 4, sixthIndex - fifthIndex - 5).TrimEnd();
var len1 = firstValue.Length;
var len2 = secondValue.Length;
var len3 = thirdValue.Length;
inflow103.REFERENCE = firstValue.TrimEnd();
pointer = 1;
inflow103.BENEFICIARY_CUSTOMER = secondValue;
inflow103.RECEIVER_INFORMATION = thirdValue;
}
else if (allLines.Contains(":70"))
{
var firstIndex = allLines.IndexOf(":20");
var secondIndex = allLines.IndexOf(":23B");
var thirdIndex = allLines.IndexOf(":59");
var fourthIndex = allLines.IndexOf(":70");
var fifthIndex = allLines.IndexOf(":71");
var sixthIndex = allLines.IndexOf(":72");
var seventhIndex = allLines.IndexOf("-}");
var firstValue = allLines.Substring(firstIndex + 4, secondIndex - firstIndex - 5).TrimEnd();
var secondValue = allLines.Substring(thirdIndex + 5, fourthIndex - thirdIndex - 5).TrimEnd();
var thirdValue = allLines.Substring(sixthIndex + 4, seventhIndex - sixthIndex - 5).TrimEnd();
var len1 = firstValue.Length;
var len2 = secondValue.Length;
var len3 = thirdValue.Length;
inflow103.REFERENCE = firstValue.TrimEnd();
pointer = 1;
inflow103.BENEFICIARY_CUSTOMER = secondValue;
inflow103.RECEIVER_INFORMATION = thirdValue;
}
Below is the format of the text file am reading.
{1:F21DBLNNGLAAXXX4695300820}{4:{177:1405260906}{451:0}}{1:F01DBLNNGLAAXXX4695300820}{2:O1030859140526SBICNGLXAXXX74790400761405260900N}{3:{103:NGR}{108:AB8144573}{115:3323774}}{4:
:20:SBICNG958839-2
:23B:CRED
:23E:SDVA
:32A:140526NGN168000000,
:50K:IHS PLC
:53A:/3000025296
SBICNGLXXXX
:57A:/3000024426
DBLNNGLA
:59:/0040186345
SONORA CAPITAL AND INVSTMENT LTD
:71A:OUR
:72:/CODTYPTR/001
-}{5:{MAC:00000000}{PAC:00000000}{CHK:42D0D867739F}}{S:{SPD:}{SAC:}{FAC:}{COP:P}}
The above file format represent one transaction in a single text file, but while testing with live files, I came accross a situation where a file can have more than one transaction. Example is the code below.
{1:F21DBLNNGLAAXXX4694300150}{4:{177:1405231923}{451:0}}{1:F01DBLNNGLAAXXX4694300150}{2:O1031656140523FCMBNGLAAXXX17087957771405231916N}{3:{103:NGR}{115:3322817}}{4:
:20:TRONGN3RDB16
:23B:CRED
:23E:SDVA
:26T:001
:32A:140523NGN1634150,00
:50K:/2206117013
SUNLEK INVESTMENT LTD
:53A:/3000024763
FCMBNGLA
:57A:/3000024426
DBLNNGLA
:59:/0022617678
GOLDEN DC INT'L LTD
:71A:OUR
:72:/CODTYPTR/001
//BNF/TRSF
-}{5:{MAC:00000000}{PAC:00000000}{CHK:C21000C4ECBA}{DLM:}}{S:{SPD:}{SAC:}{FAC:}{COP:P}}${1:F21DBLNNGLAAXXX4694300151}{4:{177:1405231923}{451:0}}{1:F01DBLNNGLAAXXX4694300151}{2:O1031656140523FCMBNGLAAXXX17087957781405231916N}{3:{103:NGR}{115:3322818}}{4:
:20:TRONGN3RDB17
:23B:CRED
:23E:SDVA
:26T:001
:32A:140523NGN450000,00
:50K:/2206117013
SUNLEK INVESTMENT LTD
:53A:/3000024763
FCMBNGLA
:57A:/3000024426
DBLNNGLA
:59:/0032501697
SUNSTEEL INDUSTRIES LTD
:71A:OUR
:72:/CODTYPTR/001
//BNF/TRSF
-}{5:{MAC:00000000}{PAC:00000000}{CHK:01C3B7B3CA53}{DLM:}}{S:{SPD:}{SAC:}{FAC:}{COP:P}}
My challenge is that in my code, while reading allLines, each line is identified by certain index, a situation where I need to pick up the second transaction from the file, and the same index exist like as before, how can I manage this situation.
This is a simple problem obscured by excess code. All you are doing is extracting 3 values from a chunk of text where the precise layout can vary from one chunk to another.
There are 3 things I think you need to do.
Refactor the code. Instead of two hefty if blocks inline, you need functions that extract the required text.
Use regular expressions. A single regular expression can extract the values you need in one line instead of several.
Separate the code from the data. The logic of these two blocks is identical, only the data changes. So write one function and pass in the regular expression(s) needed to extract the data items you need.
Unfortunately this calls for a significant lift in the abstraction level of the code, which may be beyond what you're ready for. However, if you can do this and (say) you have function Extract() with regular expressions as arguments, you can apply that function once, twice or as often as needed to handle variations in your basic transaction.
You may perhaps use the code below to achieve multiple record manipulation using your existing code
//assuming fileText is all the text read from the text file
string[] fileData = fileText.Split('$');
foreach(string allLines in fileData)
{
//your code goes here
}
Maybe indexing works, but given the particular structure of the format, I highly doubt that is a good solution. But if it works for you, then that's great. You can simply split on $ and then pass each substring into a method. This assures that the index for each substring starts at the beginning of the entry.
However, if you run into a situation where indices are no longer static, then before you even start to write a parser for any format, you need to first understand the format. If you don't have any documentation and are basically reverse engineering it, that's what you need to do. Maybe someone else has specifications. Maybe the source of this data has it somewhere. But I will proceed under the assumption that none of this information is available and you have been given a task with absolutely no support and are expected to reverse-engineer it.
Any format that is meant to be parsed and written by a computer will 9 out of 10 times be well-formed. I'd say 9.9 out of 10 for that matter, since there are cases where people make things unnecessarily complex for the sake of "security".
When I look at your sample data, I see "chunks" of data enclosed within curly braces, as well as nested chunks.
For example, you have things like
{tag1:value1} // simple chunk
{tag2:{tag3: value3}{tag4:value4}} // nested chunk
Multiple transactions are delimited by a $ apparently. You may be able to split on $ signs in this case, but again you need to be sure that the $ is a special character and doesn't appear in tags or values themselves.
Do not be fixated on what a "chunk" is or why I use the term. All you need to know is that there are "tags" and each tag comes with a particular "value".
The value can be anything: a primitive such as a string or number, or it can be another chunk. This suggests that you need to first figure out what type of value each tag accepts. For example, the 1 tag takes a string. The 4 tag takes multiple chunk, possibly representing different companies. There are chunks like DLM that have an empty value.
From these two samples, I would assume that you need to consume each each chunk, check the tag, and then parse the value. Since there are nested chunks, you likely need to store them in a particular way to correctly handle it.

Categories