I have a UserForm that reads data from a file and dynamically fills a Frame control with other Frame controls that themselves are filled with Labels describing that data. So, there's one big Frame control — DisplayFrame — put onto the UserForm using the Toolbox, and at runtime when the file is opened other smaller Frame controls — cFrame1, cFrame2, etc — are added inside the DisplayFrame control, with labels like NameLabel, DateLabel, added to each cFrame.
I'd like the user to be able to select any of the cFrames, then click a button on the user form and for another window to open with all of the data inside the labels (plus additional data) in that cFrame.
By select, I mean that when the user clicks on ANY of the labels inside a particular cFrame — or the cFrame itself — the color of the cFrame and all its elements change and that particular cFrame is recorded as being the current selection. The tricky part, I think, is that the color of any previously selected cFrame should change back to the default color.
I've created a Class called FrameGroup to hold all the cFrames that are created. I defined the click event of the FrameGroup class to change color when it is selected and to hold the data of the cFrame just selected.
<<Class FrameGroup>>
Public WithEvents FrameGroup As Frame
Private cName As String
Private cDay As String
Private Sub FrameGroup_Click()
cName = FrameGroup.Controls(0).Caption
cDay = FrameGroup.Controls(1).Caption
' If current cFrame was selected before, then deselect it
' by returning to default color
If FrameGroup.BackColor = &H8000000D Then
FrameGroup.BackColor = &H80000005
FrameGroup.Controls(0).BackColor = &H80000005
FrameGroup.Controls(1).BackColor = &H80000005
Else
' Select cFrame by changing color
FrameGroup.BackColor = &H8000000D
FrameGroup.Controls(0).BackColor = &H8000000D
FrameGroup.Controls(1).BackColor = &H8000000D
End If
End Sub
<<Code for UserForm>>
Dim FrameList() As New FrameGroup
Private Sub UserForm_Initialize()
Dim TextLine As String
Dim Text() As String
Dim LineNo As Integer
' Open file containing saved Color Scales
Open file For Input As #1
LineNo = 0
Do Until EOF(1)
Line Input #1, TextLine
Text = Split(TextLine, ",")
' Making CFrame
Dim currCFrame As Frame
Set currCFrame = DisplayFrame.Controls.Add("Forms.Frame.1", "cFrame" & LineNo, True)
' Adding labels
Dim NameLabel As Control
Set NameLabel = currCFrame.Controls.Add("Forms.Label.1", "Name" & LineNo, True)
Dim DateLabel As Control
Set DateLabel = currCFrame.Controls.Add("Forms.Label.1", "DateCreated" & LineNo, True)
' Increment line number
LineNo = LineNo + 1
' Adding new frame to frame group
ReDim Preserve FrameList(1 To LineNo + 1)
Set FrameList(LineNo).FrameGroup = currCFrame
Loop
' Close file once we are done reading color scales from it
Close #1
End Sub
What happens is that only when the cFrame is clicked does anything happen--not when any of the labels inside are clicked. And I don't know how to make it so that when a cFrame is clicked, the color of the previously selected cFrame is also changed to the default color.
I've tried to look up how to do something like this, and solutions like this seem to bring up a different class module for cFrame and its labels and a intermediary class handling communications between two classes, but this seems complicated. If I followed this design, I'd probably need another intermediary between cFrame and the button being clicked to load data, right? I don't want to make this more complicated than it needs to be, but I also would like to create a sustainable and robust solution. Any help would be appreciated.
Label Click put in and clicking in another frame gives the other frames the default color,
Tried to minimize the adjustments to your code below:
'<<Class FrameGroup>>
Public WithEvents FrameGroup As MSForms.Frame
Public WithEvents LabelGroup As MSForms.Label
Private cName As String
Private cDay As String
Private Sub FrameGroup_Click()
Dim ctl As MSForms.Control
cName = FrameGroup.Controls(0).Caption
cDay = FrameGroup.Controls(1).Caption
For Each ctl In FrameGroup.Parent.Controls
ctl.BackColor = &H80000005
Next
FrameGroup.BackColor = &H8000000D
FrameGroup.Controls(0).BackColor = &H8000000D
FrameGroup.Controls(1).BackColor = &H8000000D
End Sub
Private Sub LabelGroup_Click()
Dim ctl As MSForms.Control
cName = LabelGroup.Parent.Controls(0).Caption
cDay = LabelGroup.Parent.Controls(1).Caption
For Each ctl In LabelGroup.Parent.Parent.Controls
ctl.BackColor = &H80000005
Next
LabelGroup.Parent.BackColor = &H8000000D
LabelGroup.Parent.Controls(0).BackColor = &H8000000D
LabelGroup.Parent.Controls(1).BackColor = &H8000000D
End Sub
'<<Code for UserForm>>
Dim FrameList() As New FrameGroup
Private Sub UserForm_Initialize()
Dim TextLine As String
Dim Text() As String
Dim LineNo As Integer
' Open file containing saved Color Scales
Open file For Input As #1
LineNo = 0
Do Until EOF(1)
Line Input #1, TextLine
Text = Split(TextLine, ",")
' Making CFrame
Dim currCFrame As Frame
Set currCFrame = DisplayFrame.Controls.Add("Forms.Frame.1", "cFrame" & LineNo, True)
' Adding labels
Dim NameLabel As Control
Set NameLabel = currCFrame.Controls.Add("Forms.Label.1", "Name" & LineNo, True)
Dim DateLabel As Control
Set DateLabel = currCFrame.Controls.Add("Forms.Label.1", "DateCreated" & LineNo, True)
' Increment line number
LineNo = LineNo + 1
' Adding new controls to frame group
ReDim Preserve FrameList(1 To 3 * (LineNo + 1))
Set FrameList(3 * (LineNo) + 1).FrameGroup = currCFrame
Set FrameList(3 * (LineNo) + 2).LabelGroup = DateLabel
Set FrameList(3 * (LineNo) + 3).LabelGroup = NameLabel
Loop
' Close file once we are done reading color scales from it
Close #1
End Sub
The Click event of a Frame control is only raised when you click the border of that said frame or its blank area. If you click a Label within a Frame, this control has it's own area. If you click this area, then that's the label's Click event that is raised, not the frame's.
The followin picture shows you the area that belongs to the Label (grey) vs the area that belong to the Frame (red).
Related
Background:
I'm trying to create a quiz using powerpoint where there are four buttons on each slide (Correct Answer, Wrong Answer, Wrong Answer, Wrong Answer). Depending which is selected, the user is redirected to a different slide. And to make things more difficult for the players, I'm wanting to randomise the location of the answer buttons e.g. randomly swap the correct answer location, with the wrong answer location etc.
Presentation and Spreadsheet files on OneDrive
Target:
I'm trying to use vba through excel to first find the top and left co-ordinates for each shape, on each slide. And then loop through the presentation a second time, to randomise the placement of my answer buttons (randomly swap them around).
Clarification:
Each of my answer buttons are made up of two parts, a transparent rectangle shape (which has an action link to a particular slide depending whether or not the user selected the correct or wrong answer) as well as a text field (with a red background) which says either wrong or correct answer.
Problem:
I'm currently having problems storing the top and left co-ordinates for each shape, on each slide. So I can then loop through each slide and randomise the placement of my potential answer buttons.
So Far
I'm able to access and store the top and left locations of each shape locally, but I'm not able to store them in my nested classes. Instead when I attempt to pass through the array of shapes found on a particular slide to one of my classes, each time I attempt to access this passed through array, it shows as empty even though I know values are being passed through.
Any suggestions would be fantastic
My Code:
Module 1
Option Explicit
Sub CreateQuiz()
Dim oPPApp As Object, oPPPrsn As Object, oPPSlide As Object
Dim oPPShape As Object
Dim FlName As String
'~~> Change this to the relevant file
FlName = ThisWorkbook.Path & "/Quiz.pptm"
'~~> Establish an PowerPoint application object
On Error Resume Next
Set oPPApp = GetObject(, "PowerPoint.Application")
If Err.Number <> 0 Then
Set oPPApp = CreateObject("PowerPoint.Application")
End If
oPPApp.Visible = False
Set oPPPrsn = oPPApp.Presentations.Open(FlName, True)
Dim currentPresentation As New Presentation
Dim numSlides As Integer
numSlides = 0
For Each oPPSlide In oPPPrsn.Slides
Dim currentSlide As New shapesOnSlide
Dim numShapes As Integer
numShapes = 0
For Each oPPShape In oPPSlide.shapes
Dim currentShape As New shapeDetails
currentShape.slideNumber = oPPSlide.slideNumber
currentShape.name = oPPShape.name
currentShape.left = oPPShape.left
currentShape.top = oPPShape.top
currentSlide.size = numShapes
currentSlide.aShape = currentShape
numShapes = numShapes + 1
Next
currentPresentation.Slide(numSlides) = currentSlide
numSlides = numSlides + 1
Next
currentPresentation.printAll
End Sub
ShapeDetails Class
Private ElementSlideNumber As Integer
Private ElementName As String
Private ElementLeft As Double
Private ElementTop As Double
Public Property Get slideNumber() As Integer
slideNumber = ElementSlideNumber
End Property
Public Property Let slideNumber(value As Integer)
ElementSlideNumber = value
End Property
Public Property Get name() As String
name = ElementName
End Property
Public Property Let name(value As String)
ElementName = value
End Property
Public Property Get left() As Double
left = ElementLeft
End Property
Public Property Let left(value As Double)
ElementLeft = value
End Property
Public Property Get top() As Double
top = ElementTop
End Property
Public Property Let top(value As Double)
ElementTop = value
End Property
Public Sub PrintVars()
Debug.Print "Slide: " & slideNumber & " Position: " & left & "," & top & ", Slide Name: " & name
End Sub
shapesonSlide Class
Private allShapes(99999) As Variant
Private collectionSize As Integer
Public Property Get size() As Integer
size = collectionSize
End Property
Public Property Let size(value As Integer)
collectionSize = value
End Property
Public Property Get aShape() As Variant
shapes = allShapes(collectionSize)
End Property
Public Property Let aShape(value As Variant)
allShapes(collectionSize) = value
End Property
Public Property Get everyShape() As Variant
everyShape = allShapes()
End Property
Public Property Let everyShape(value As Variant)
everyShape = value
End Property
Sub compareSizes(newIndex As Integer)
If (newIndex > collectionSize) Then
collectionSize = newIndex
End If
End Sub
Public Sub printSize()
Debug.Print collectionSize
End Sub
Presentation Class
Private allSlides() As shapesOnSlide
Private Sub Class_Initialize()
ReDim allSlides(0)
End Sub
Public Property Get Slides() As shapesOnSlide()
Slides = allSlides
End Property
Public Property Get Slide(index As Integer) As shapesOnSlide
Slide = allSlides(index)
End Property
Public Property Let Slide(index As Integer, currentSlide As shapesOnSlide)
If index > UBound(allSlides) Then ReDim Preserve allSlides(index)
allSlides(index) = currentSlide
End Property
Public Sub printAll()
For Each currentSlide In allSlides
For Each currentShape In currentSlide.everyShape
Debug.Print currentShape.name
Next
Next
End Sub
I have 25 text boxes named in the following manner on a UserForm
Name: id_[X]_box 1<= x <= 25
I am trying to write a program which can register a change event for all 25 boxes and populate the corresponding [DESCRIPTION] Labels.
Naming scheme for Description Labels Name: desc_[X]_label 1 <= X <= 25
When I program for a change event for just one box (i.e id_box_1), the functionality works fine.
When I try to implement for the 25 boxes with WithEvents and ClassModules, I am getting an error "Can't compile Module"
The form's Name: links
Please see relevant code snippets below
Code in the UserForm_Initialize function
Private Sub UserForm_Initialize()
'Code to make single change event subroutine register for all id_[INT]_textboxes on links form
Dim ctrl As MSForms.Control
Dim text_box_handler As text_boxes_change
Set textBox_collection = New Collection
For Each ctrl In Me.controls
If TypeOf ctrl Is MSForms.TextBox Then
If Split(ctrl.Name, "_")(0) = "id" Then
Set text_box_handler = New text_boxes_change
Set text_box_handler.control_text_box = ctrl
textBox_collection.Add text_box_handler
End If
End If
Next ctrl
End Sub
Custom Class Module Code
Class Module Name: text_boxes_change
Option Explicit
'This class assists in validating multiple text boxes on forms without having to define event
functions for each text box separately
'Global Constants
Const CASHFLOW As String = "Chart"
Const SETUP As String = "Settings"
Const INVOICE_STATUSES As String = "K13:K18"
Const TIME_UNITS As String = "L21:L24"
Const RELATION_TYPES As String = "M21:M25"
Const ACTIVITIES_COL As String = "T"
Const PROJ_START_ROW As Integer = 6
Public WithEvents MyTextBox As MSForms.TextBox
Public Property Set control_text_box(ByVal tb As MSForms.TextBox)
Set MyTextBox = tb
End Property
Public Sub BoxesGroup_Change()
'Setting default background color for the box
Me.MyTextBox.BackColor = RGB(255, 255, 255)
'Setting up Cashflow Worksheet Object
Dim cashflow_sheet As Worksheet
Set cashflow_sheet = Sheets("Chart")
'Finding lastrow with text inside the Sub-Activites column in Chart sheet
Dim lastrow As Integer
lastrow = cashflow_sheet.Cells(Rows.Count, ACTIVITIES_COL).End(xlUp).Row
'Range to represent the activities column in Chart worksheet
Dim activities_range As Range
Set activities_range = cashflow_sheet.Range(ACTIVITIES_COL & CStr(PROJ_START_ROW) & ":" & _
ACTIVITIES_COL & CStr(lastrow))
'A variable to store the user inputed value for id_box
Dim row_id As String
row_id = Me.MyTextBox.value
If IsNumeric(row_id) = True Then
If CInt(row_id) >= PROJ_START_ROW And CInt(row_id) <= lastrow Then
Dim desc_caption As String
'SheetFunctions is a Module ; links_description is a Function that returns a string
representing a cell address based on the rules of the workbook; functionality is tested and verified
for this part
desc_caption = SheetFunctions.links_description(row_id)
If desc_caption <> "" Then
Me.MyTextBox.BackColor = RGB(255, 255, 255)
Me.desc_1_label.Caption = desc_caption
Else
Me.MyTextBox.BackColor = RGB(140, 39, 30)
End If
Else
Me.MyTextBox.BackColor = RGB(140, 39, 30)
End If
Else
Me.MyTextBox.BackColor = RGB(140, 39, 30)
End If
End Sub
Screenshot of the Form
I have created a tool using Excel to gather inputs from a user and use it to do some processing of data. I have created a UI on a worksheet with a bunch of ActiveX controls (TextBox, ListBox, ComboBox).
Part of the ActiveX controls are dynamic - they are added at run time based on "metadata" that the tool admin creates on a second worksheet. Metadata contains the field name, type of ActiveX control, position of the control, ListRange to populate values, Multi-Text/Multi-Select flag, etc.
I am able to successfully add the ActiveX controls to the UI worksheet. However, now I want to add functionality for ActiveX TextBox controls to show a default text, when the control gets focus - default text gets removed, when the control loses focus - if user has entered any data it remains otherwise the default text shows up again.
Public Sub df_segment_GotFocus()
Dim wb As Workbook
Set wb = ThisWorkbook
Set form_sheet = Worksheets(Sheet1.Name)
If form_sheet.OLEObjects("df_segment") Is Nothing Then
Else
'When user begins to type, remove the help text and remove Italics
Dim seg_val As String
seg_val = form_sheet.OLEObjects("df_segment").Object.Value
If seg_val = "e.g. Desktop-Mac,Desktop-Win,Mobile-OSX" Then
form_sheet.OLEObjects("df_segment").Object.Font.Italic = False
form_sheet.OLEObjects("df_segment").Object.Value = ""
Else
form_sheet.OLEObjects("df_segment").Object.Value = seg_val
End If
End If
End Sub
Public Sub df_segment_LostFocus()
Dim wb As Workbook
Set wb = ThisWorkbook
Set form_sheet = Worksheets(Sheet1.Name)
If form_sheet.OLEObjects("df_segment") Is Nothing Then
Else
'Incase user doesn't enter any values, show the help text again
Dim seg_val As String
seg_val = form_sheet.OLEObjects("df_segment").Object.Value
If seg_val = "" Then
form_sheet.OLEObjects("df_segment").Object.Font.Italic = True
form_sheet.OLEObjects("df_segment").Object.Value = "e.g. Desktop-Mac,Desktop-Win,Mobile-OSX"
Else
form_sheet.OLEObjects("df_segment").Object.Value = seg_val
End If
End If
End Sub
In the sample code above, you can see that I am using the exact name of the control to setup the GotFocus and LostFocus event handlers. However, since my UI is metadata driven, the controls will be added/removed dynamically and I wouldn't know the name of the controls to explicitly add the event handlers.
I looked up the forums and implemented this:
a.) Implemented a Class Module
Public WithEvents df_TextBox As MSForms.TextBox
Public df_TextBox_Name As String
Private Sub df_TextBox_Change()
Dim wb As Workbook
Set wb = ThisWorkbook
Set form_sheet = Worksheets(Sheet1.Name)
Set metadata_sheet = Worksheets(Sheet2.Name)
Dim obj_name As String
obj_name = df_TextBox_Name
obj_val = form_sheet.OLEObjects(obj_name).Object.Value
MsgBox "Change in TextBox" & obj_val
End Sub
b.) Created objects for the Class where I instantiate the control objects
ElseIf d_Type = "TextBox" Then
df_obj.Object.Value = d_def_val
df_obj.Object.Font.Italic = True
If d_Multi = 1 Then
df_obj.Object.MultiLine = True
End If
'--------------------------------------------------------------
'part where we add the custom events for GotFocus and LostFocus
'--------------------------------------------------------------
ReDim Preserve TextBox_Event_Array(1 To i)
Set TextBox_Event_Array(i).df_TextBox = df_obj.Object
TextBox_Event_Array(i).df_TextBox_Name = df_obj.Name
Problem Statements
1.) When I create the class module, I don't see the GotFocus and LostFocus events available. Only Change, KeyDown/Press/Up, MouseDown/Move/Up
2.) I created a Change event handler just to test the Class Module but I do not see it getting triggered.
Any suggestions on how can I fix the problem or any alternate solutions?
I have a number of frames on a userform and I have a set of checkboxes that contain the names of frames inside a frame ("SectorsFrame"). I want to start with "SectorsFrame", use the tag of the checkbox to identify the next frame to work with, and then repeat that step inside the next frame.
I can't get past just re-setting/renaming my frame-name variable. Any help with this?
Private Sub cmdCopy_Click()
Dim chkBox As Control
Dim cmbBox As Control
Dim frmSource As MSForms.Frame
'Dim frmSource As String
Dim valSectCopy1 As String 'to validate that a sector is filled in
Dim valSectCopy2 As String 'to validate that an antenna is filled in
Dim valPortCopy As String 'to validate that a port is filled in
Set frmSource = SectorsFrame
valSectCopy1 = ""
valSectCopy2 = ""
valPortCopy = ""
For Each chkBox In frmSource.Controls 'Sector-level frame
If TypeName(chkBox) = "CheckBox" And chkBox.Value = True Then
valSectCopy1 = chkBox.Tag
valSectCopy2 = valSectCopy1
Set frmSource = valSectCopy1
Exit For
End If
Next chkBox
This is the line that's currently throwing a tantrum:
Set frmSource = valSectCopy1
I can't figure out what I'm doing wrong here. I added a button to an Excel Sheet programmatically. I am trying to assign an accelerator key, but it does not get assigned. The relevant code is:
Sub addPrint(sht, Optional fromLeft, Optional fromTop)
If IsMissing(fromLeft) Then fromLeft = 180
If IsMissing(fromTop) Then fromTop = 10
Set printbut = sht.Buttons.Add(fromLeft, fromTop, 50, 20)
printbut.Name = "PrintButton"
printbut.OnAction = "Sheet4.printButton"
printbut.Characters.Text = "Print/PDF"
printbut.Accelerator = "P"
End Sub
The 'P' does not get underlined and Alt-P does nothing.
This is the way to add an ActiveX-Button:
Sub addActiveXCommandButton(sht As Worksheet, Optional left As Single = 100, Optional top As Single = 100)
Dim btn As OLEObject
'
'create Button
'
Set btn = ActiveSheet.OLEObjects.Add(ClassType:="Forms.CommandButton.1", Link:=False, _
DisplayAsIcon:=False, left:=left, top:=top, _
Width:=105.75, Height:=36)
Debug.Print TypeName(btn) ' this returns OLEObject as a wrapper of the CommandButton
Debug.Print TypeName(btn.Object) ' this returns CommandButton - the activeX-Object
'
' access the CommandButton-Object and set the Accelerator value
'
btn.Object.Accelerator = "B"
End Sub
However, I am not certain, that the Accelerator Button may be accessed. On testing, the Accelerator Button could bot be accessed using the Alt-key.
I use a solution with a button and an application.onKey-definition that both access the same procedure.