Formatting programatically added controls to fit in cells - excel

I'm creating a program in Visual Basic through Microsoft Excel, where I can input all the information from an application into excel. Each row is unique and features a dropdown control that is created and placed programatically (see code below).
The problem I have come across is correctly placing the control in each cell within one of the columns.
I have read through documents on the web and seen examples on other questions but none of them seem to work for my situation. I keep having to guess-n-check the x and y values for the AddFormControl function to place the controls in a somewhat correct way.
Code:
For y = 1 To UBound(array) + 1
Set lb = Sheet1.Shapes.AddFormControl(xlDropDown, 274, 32 + (19 * y), 77, 19)
With lb
.ControlFormat.DropDownLines = 3
.ControlFormat.AddItem "Select a value", 1
.ControlFormat.AddItem "Auto", 2
.ControlFormat.AddItem "Default", 3
.ControlFormat.Value = 1
End With
Next y
I have counted the width's and height's of all the rows and columns so that I can place the controls in the correct spot but they are offset, some more than others.
The 274 x-value above is what lines up to the spot I would like it to for the x-axis on the excel sheet but is not how wide the columns prior to the spot add up to. Same goes for the y-value 77.
What would be the most efficient solution to this issue?

You can actually use Range.Top and Range.Left to place your From Controls exactly where you want them and they will be perfectly lined up with the cell in question. This is a great method for adding them in large quantities dynamically.

Related

How to programmatically print a long text string in 3 columns over multiple lines in excel vba

I have a long string of text. I want to print this in 3 columns over 2 pages in excel, using vba. e.g.:
A|B|C
-----
D|E|F
This is easy to do in ms word, I just split the page into 3 columns and print the text and it automatically goes onto the next column/page once the previous on is full. But I need to do similar in excel.
Ideas I've explored:
Having a textbox that stretches over 2 pages - splits into 3 columns, but the arrangement is:
a|c|e
b|d|f
I can't seem to find a way to put a page break within the text box.
Have 6 separate text boxes and split the text up - can't find a way of determining when one box is full so that the other can be started. Can't even determine how many lines the text takes up as characters have varying widths.
Have 6 separate large cells and split the text up - same issues as above.
Does anyone know of a way this can be overcome? I just want to replicate the behaviour of ms word.
Edit: here's the code for a textbox with columns that I can't get to page break:
Dim tb_1
Set tb_1 = jumbledwords_sheet.Shapes.AddTextbox(msoTextOrientationHorizontal, 10, 7470, 488, 1300)
tb_1.TextFrame2.Column.Spacing = 10
tb_1.TextFrame2.Column.Number = 3
tb_1.TextFrame2.TextRange.Font.Size = 9
tb_1.TextFrame2.TextRange.Characters.Text = long_string
The stucture of long_string isn't important, and I can break it etc. to fit the solution.

Identify overlapping configurations in Excel

I'm setting up a configuration excel sheet to be imported into a database.
It has four columns
Equipment Fleet Start Date End Date Highlight Me
=============================================
A X 1-Jan-20 5-Jan-20 X
A Y 6-Jan-20
B C 1-Jan-20 3-Jan-20
B D 4-Jan-20 10-Jan-20
A Z 3-Jan-20 X
A Z 5-Jan-20 X
I need to identify and highlight overlapping configs
I'd like lines 1, 5 and 6 to be highlighted.
They are all a configuration for the same Equipment, but their configuration dates overlap
Fleet is that attribute we are configuring for the date range but has no bearing on the validation
Constraints:
I'd like to use tables (not named ranges) for this. My table is called tblFleetConfig
Yes I could do this in VBA but I don't want to deal with trusted workbooks etc. etc.
So far I have pasted this into a column on the right
=
(tblFleetConfig[#[Start Date]] >= tblFleetConfig[Start Date])
*
(tblFleetConfig[#[Start Date]] <= tblFleetConfig[End Date])
*
(tblFleetConfig[#Equipment]=tblFleetConfig[Equipment])
The result I'm getting is a 1 for the first line and 0 for every other line.
Clearly I don't understand this syntax and I'm interested in learning.
You asked a complicated one, those blanks throw a wrench into it.
Your formula is the right syntax, you need to wrap it in a SUMPRODUCT() though.
=SUMPRODUCT((tblFleetConfig[End Date]<=tblFleetConfig[#[End Date]])*(tblFleetConfig[Start Date]>=tblFleetConfig[#[Start Date]])*(tblFleetConfig[Equipment]=tblFleetConfig[#Equipment]))
This is your formula wrapped it in a SUMPRODUCT().
This will return a 1 if there is a single occurrence and a number greater than 1 if multiple.
Let me know if it works.

How to merge two (or more) rows using VBA in Excel?

I am trying to merge two rows in Excel using VBA in order to create a new row with the combination values of selected rows using a factor x.
alpha 5 6 8 3 ...
beta 10 1 5 7 ...
With alpha and beta I want to create row ab70 (x=.7)
ab70 6.5 4.5 7.1 4.2 ...
(5*.7+10(1-.7)) ...
I would like to create this from a GUI in VBA selecting from a list the materials and chosing the factor x.
Thanks :D
L
The first version of this answer was more concerned with clarifying the requirement than answering the question. The second version is closer to an proper answer. Questions in the first version which were answered in comments have been deleted.
First version after removal of questions
This is not a site which can teach you to create a userform although you could get help with the code for a control. Try searching the web for “excel vba userform tutorial”. There are a number to choose from. I have not tried any so cannot make a recommendation.
A List box allows the program to provide a list from which the user can select one or more items. A Combo box allows the program to provide a list from which the user can select one item or enter a new value that is not within the list. You do not want the user to be able to specify their own material so you need a List Box. By default the user can only select one item which is what you want.
Second version
This will not be a complete answer. I will give you design ideas which you can then develop to meet your exact requirement or you can clarify your requirement and I will develop them a little more. I will give you some useful code but not all you will need for the complete solution.
You say that combining two materials would meet your immediate needs but in the longer term you wish to combine more. There are different approaches to addressing such a situation:
Design and implement a solution for the immediate need now. Redesign for the longer term later.
Design and implement a solution for the long term need now.
Design a solution for the long term then implement as much of the long-term design as seems appropriate.
None of these approaches will be correct in every case. If you are working to a deadline, approach 1 many be the only choice. Approach 1 may also be appropriate if you lack experience with the technology and wish for a simple implementation as a training exercise. When I was young, distributing a new version of an application to multiple users could be very expensive and approach 2 would often be the preferred approach. These days, approach 3 is normally my preference.
From your comments I deduce you are thinking of something like:
The two list boxes are filled with the names of the materials so the user can click one row in the first list box and one in the second to specify the two materials. Text boxes allow the user to enter the Proportion and the Name. I have used the blue “Rem” to represent the remainder (1 – x) which you may wish to display as a comment. You may not have thought of buttons. There should always be an Exit button in case the user has started the macro unintentionally. Clicking a button to save the mixture allows the user to check the four values first.
I think this could be an excellent design for the two material version. If we ignore the actual merging of the rows, there would be little code behind this form.
I do not know how long your material names are but I assume this design could be expanded for three or four materials by adding extra list boxes to the right with a Proportion text box under all but the last list. However, this arrangement would have a low maximum number of materials in a mixture. This will be acceptable if you do have a low maximum. You might also allow the user to mix mixtures thereby allowing an unlimited number of base materials in a mixture.
The code behind a form that allowed three or four materials in a mixture would be only a little more complicated than that behind the two material version.
I have two alternative designs that would perhaps be better with a higher maximum number of materials but it will not outline then unless you indicates that this first design is unacceptable.
I would expect any good tutorial to explain the various methods of loading a list box with values to I will not repeat them.
However you decide to handle the selection of materials and their proportions, you will need a routine to generate the new row.
I have created a worksheet “Material” and have set the first few rows and columns so:
I appreciate you have many more rows and columns but my data is adequate for a test and demonstration. Note in the heading line "Prop" is short for "Property".
You need to tell the routine which merges rows, which rows to mix. The user will select material B2 say. You could pass “B2” to the routine and let it discover the row from which it had come but this would make the routine more difficult to code than necessary. When loading the list boxes from this worksheet, values will be taken from column A of rows 2 to 12. I would expect your user form tutorial to explain that your code can identify the value selected by the user either by value (B2) or by index (4th row). You know the 1st row of the list box was loaded from row 2 of the worksheet so you can calculate that the 4th row of the list box was loaded from row 5 of the worksheet.
You need to tells the routine the proportions entered by the user and the name of the mixture.
Above I listed three possible approaches to deciding how much to implement. An addition to any of these approaches is the inclusion of flexibility that is not required but is as easy or is easier to include than exclude.
The declaration for my routine is:
Sub RecordNewMixture(ByVal WshtName, ByRef RowSrc() As Long, ByRef Prop() As Single, _
ByVal MaterialNameNew As String)
You will only have one worksheet holding materials and its name is unlikely to change so I could hardcode that worksheet’s name into the routine. However, it almost as easy to make the worksheet name a parameter and I think it makes the code tidier so I have make it a parameter.
The routine requires the array Prop() hold all the proportions including the last. So, for example, (0.7, 0.3) or (0.3, 0.3, 0.4). The user form will have to calculate the last proportion so it might as well pass the last proportion. I have made Prop() an array of Singles which I assume will give you adequate precision. If you do not understand the last sentence I can explain. Note that here "Prop" is short for proportion. Sorry for using "Prop" as an abbreviation for both "Property" and "Proportion". I did not notice until I the final checking of this text.
I needed a routine to test Sub RecordNewMixture so I have provided it as a demonstration. Note that I have coded and tested this routine without any involvement of the user form. It is always a good idea to develop and test your routines in isolation before combining them into the finished product.
After running the macro, worksheet “Material” has two new rows:
If you duplicate the new rows with formulae, you will find that the values are as you require.
Option Explicit
Sub Test()
Dim RowSrc() As Long
Dim Prop() As Single
ReDim RowSrc(0 To 1)
ReDim Prop(0 To 1)
RowSrc(0) = 2: Prop(0) = 0.7!
RowSrc(1) = 4: Prop(1) = 0.3!
Call RecordNewMixture("Material", RowSrc, Prop, "Join24")
ReDim RowSrc(1 To 3)
ReDim Prop(1 To 3)
RowSrc(1) = 3: Prop(1) = 0.3!
RowSrc(2) = 6: Prop(2) = 0.3!
RowSrc(3) = 9: Prop(3) = 0.4!
Call RecordNewMixture("Material", RowSrc, Prop, "Join369")
End Sub
Sub RecordNewMixture(ByVal WshtName, ByRef RowSrc() As Long, ByRef Prop() As Single, _
ByVal MaterialNameNew As String)
' * RowSrc is an array containing the numbers of the rows in worksheet WshtName
' that are to be mixed to create a new material.
' * Prop is an array containing the proportions of each source material in the new
' mixture.
' * Arrays RowSrc and Prop must have the same lower and upper bounds.
' * MaterialNameNew is the name of the mixture.
' * Each data row in Worksheet WshtName defines a material. Column A contains the
' name of the material. The remaining columns contain numeric properties of the
' material.
' Each data row in Worksheet WshtName must have the same maximum number of
' columns. Call this value ColLast.
' * This routine creates a new row below any existing rows within worksheet
' WshtName. Call this row RowNew. The values in this new row are:
' * Column A = MaterialNameNew
' * For ColCrnt = 2 to ColMax
' * Cell(RowNew, ColCrnt) = Sum of Cell(RowSrc(N), ColCrnt) * Prop(N)
' for N = LBound(RowSrc) to UBound(RowSrc)
Dim ColCrnt As Long
Dim ColLast As Long
Dim InxRowSrc As Long
Dim RowNew As Long
Dim ValueNewCrnt As Single
Application.ScreenUpdating = False
With Worksheets(WshtName)
' Locate the row before the last row with a value in column A
RowNew = .Cells(Rows.Count, "A").End(xlUp).Row + 1
' Store name of new material
.Cells(RowNew, "A") = MaterialNameNew
' Locate the last column in the first source row. Assume same
' last column for all other source rows
ColLast = .Cells(RowSrc(LBound(RowSrc)), Columns.Count).End(xlToLeft).Column
For ColCrnt = 2 To ColLast
' If Single does not give adequate precision, change the declaration of
' Prop() and ValueNewCrnt to Double. If you do this, replace "0!" by "0#"
ValueNewCrnt = 0!
For InxRowSrc = LBound(RowSrc) To UBound(RowSrc)
ValueNewCrnt = ValueNewCrnt + .Cells(RowSrc(InxRowSrc), ColCrnt).Value * Prop(InxRowSrc)
Next
.Cells(RowNew, ColCrnt) = ValueNewCrnt
Next
End With
Application.ScreenUpdating = True
End Sub

Excel dynamic data series. Unusual data look and chart

Since I solved previous problem with collecting data from database, I need to put that data on a chart now. I am working on a report generating software called ReportWorx.
Problem is, data comes in series and looks like this:
ID DATE SAMPLE
1 XX-XX-XX VALUE
1 XX-XX-XX VALUE
1 XX-XX-XX VALUE
2 XX-XX-XX VALUE
2 XX-XX-XX VALUE
3 XX-XX-XX VALUE
3 XX-XX-XX VALUE
I can not change how it looks because it is generated automatically. What I want is linear chart in which 1, 2, 3 are series name and of course next to it DATE and VALUE are put on a linear chart (or bargraph, w/e) (Date at X axis, Value at Y axis).
I can`t specify how many records will be there (how many rows) but I found few solutions about creating dynamically increasing charts, so probably it will not be a poblem. I just do not know how to separate thos ID series from each other.
EDIT:
I have found a solution in VBA according to the first answer. Here you have VBA code below:
Sub Rewrite()
Dim row, id
For row = 38 To 1000
For id = 1 To 37
If Sheet1.Cells(row, 1).Value = id Then
Sheet2.Cells(row, 1).Value = Sheet1.Cells(row, 2)
Sheet2.Cells(row, id + 1).Value = Sheet1.Cells(row, 3)
End If
Next id
Next row
End Sub
Thank You #sancho.s
I will post a solution that I use a lot for cases like yours.
With reference to the figure (where I used sample numbers), you set up 3 new columns (D:F here), the header of which contain the corresponding labels. Then you use a formula for "splitting" the list of X data (column B here) associated with each label, and assigning a "NULL" value for data not corresponding (#N/A here, but you can choose whatever you want):
=IF($A3=D$2,$B3,$B$1)
You enter this in D3. The absolute/relative indexing used allows for copy-and-paste throughout D3:F9.
Cell B1 here contains the "NULL" value.
Then you plot 3 series: column C against columns D, E, F.
PS: I guess you could split the Y data column instead, with similar results. For some reason that I do not recall, I decided a long time ago that this was the best option, at least in my case then. You may want to try out the other option.
PS2: This also works for data that is not sorted by label.
PS3: Using NA() as the "NULL" value avoids cell values being taken as zero and then showing up in the chart, as it is the case with other errors (e.g., try using =1/0 in B1). It is the best option I found so far. Alternatively (just in case you find it useful), you can use an explicit value which is outside the actual X data range, but then you would have to manually set the X axis range. All this is for a Scatter plot, just check what works for your case.

Excel 2003: Better workaround for 10 if statements

I have a very rough workaround for 10 statements using a combination of 2 cells, as follows
Cell 1 (O2)
=IF(C2="TW2-OUT",VLOOKUP($D2,Players,8,FALSE)+VLOOKUP($D2,Players,9,FALSE),IF(C2="TW2-IN",IF($D2="","",VLOOKUP($D2,Players,10,FALSE)),IF(C2="Playing",IF($D2="","",VLOOKUP($D2,Players,8,FALSE)+VLOOKUP($D2,Players,9,FALSE)+VLOOKUP($D2,Players,10,FALSE)),IF(C2="IN1OUT2",VLOOKUP($D2,Players,9,FALSE)+VLOOKUP($D2,Players,10,FALSE),IF(C2="TW1-OUT",IF($D2="","",VLOOKUP($D2,Players,8,FALSE)),IF(C2="TW1-IN",IF($D2="","",VLOOKUP($D2,Players,9,FALSE)+VLOOKUP($D2,Players,10,FALSE)),IF(C2="TW3-OUT",VLOOKUP($D2,Players,8,FALSE)+VLOOKUP($D2,Players,9,FALSE)+VLOOKUP($D2,Players,10,FALSE),0)))))))+P2
Cell 2 (P2)
=IF(C2="TW3-IN",IF($D2="","",VLOOKUP($D2,Players,11,FALSE)),IF(C2="IN2OUT3",VLOOKUP($D2,Players,10,FALSE),IF(C2="IN1OUT3",VLOOKUP($D2,Players,9,FALSE)+VLOOKUP($D2,Players,10,FALSE),0)))
Is there a better way of doing this. I have read via a Google search about using a table approach with an array to achieve the same effect. However, in my case the status of a player determines the score of a player and this complicates things for me. Here are the 10 possible statuses (if statements) broken down as follows with how scored are calculated:
TransferStatuses Cols
Playing 8+9+10+11
TW1-IN 9+10
TW1-OUT 8
TW2-IN 10+11
TW2-OUT 8+9
TW3-IN 11
TW3-OUT 8+9+10
IN1OUT2 9
IN1OUT3 9+10
IN2OUT3 10
8 = ColK (Transfer Window 0)
9 = ColL(Transfer Window 1)
10 = ColM (Transfer Window 2)
11 = ColN(Transfer Window 3)
The 'score' array will be along the lines as follows:
=VLOOKUP(C2,$S$2:$T$11,2,FALSE)
The problem is that I don't know how to put it all together to make it work, i.e. I have to extend my formula to 300 cells but I don't know how to implement it so that the array calculates the scores correctly for each player?
Can someone help?
If I understand you correctly I would approach it like this:
Set up a matrix of binary values that specify, for each status, which columns should be added up. Use OFFSET and MATCH to look up the status for each data row and return the array/range of binary values, and SUMPRODUCT to sum it all up. See screenshot:

Resources