I need to insert an image into a MS Access form (not link it, as it is a licence, and it needs to be encrypted and protected). I have a MS Access front end and a SQL Server backend. This is the code for the insert
Private Sub AddLicence1Picture_Click()
Dim f As Object
Set f = Application.FileDialog(1)
f.allowmultiselect = False
If (f.Show = True) Then
Me![LicencePicture1].Picture = f.selecteditems(1)
End If
End Sub
I have a table that holds all the other data, and a column called LicencePicture1, and the data type is set to IMAGE and I have also tried setting it to VARBINARY(MAX). Can anyone please point me in the right direction as to what in doing wrong?
Well, one step at a time (encryption can be part 2).
So, for some reason a varbinary(max) column does not work with the standard odbc driver.
However, if you create sql server image column, then it can/will work.
The first step? Use the NEW image control - NOT the oleDB one from the ribbon.
So, choose this one:
Next up, you find that if you BIND that image control to the database column (linked table to sql server), it will not work.
However, you can still shove the image file (as raw binary) into that column, and save the current record.
And you can also display.
So, you can navigate to the given record, then say have a button to browse to the given file - similar to what you have.
So, we have this:
Private Sub cmdFile_Click()
Dim f As FileDialog
Set f = Application.FileDialog(msoFileDialogFilePicker)
f.Show
If f.SelectedItems.Count > 0 Then
Me.txtFile = f.SelectedItems(1)
End If
End Sub
Ok, so that puts the picture path into a un-bound text box.
We have this so far:
Now, note the save to db button. The code for that button looks like this:
Private Sub cmdSaveToDB_Click()
' save current record
If Me.Dirty Then Me.Dirty = False
Dim MyImage() As Byte
MyImage = GetFileBytes(Me.txtFile)
Me!ImageB = MyImage
Me.Dirty = False
End Sub
And we also need this binary file read routine - it uses the path name in the text box
Public Function GetFileBytes(ByVal path As String) As Byte()
Dim lngFileNum As Long
Dim bytRtnVal() As Byte
lngFileNum = FreeFile
If LenB(Dir(path)) Then ''// Does file exist?
Open path For Binary Access Read As lngFileNum
ReDim bytRtnVal(LOF(lngFileNum) - 1&) As Byte
Get lngFileNum, , bytRtnVal
Close lngFileNum
Else
Err.Raise 53
End If
GetFileBytes = bytRtnVal
Erase bytRtnVal
End Function
That's it.
As noted, the only issue is that we can't bind the picture box directly to the forms data source.
but, you can do this:
So, in above, dropped in a button to display the picture.
It looks like this:
Private Sub cmdShowFromDB_Click()
Me.Image1.PictureData = Me.Recordset!ImageB
End Sub
So, the results now look like this:
If you only load the form to one reocrd then put the code to "set" the image in the forms on-load event.
However, if you allow navagation, then you have to use the on-current to automatic display the image.
But, at least now with the new image control (2010 I think??), then you don't need a lot of special code.
so above saves the binary picture (raw) to sql server:
Related
In my Product Registration form, I have a Photo button to show the product photo in a pictubox1.
When the user clicks the photo button, the code will make a comparison between the prdCod column of the Products table and the filename of the image in the Photo folder on the server, and when found, it will show the corresponding image in picturebox1.
Photo button, execute the commands below:
Private Sub btnPhoto_Click(sender As Object, e As EventArgs) Handles btnPhoto.Click
Dim IdProduto As String = prdCod ** column in Products table that will be used for the search image in the Photos folder
If File.Exists("\\server\cmg\projects\Photos" & IdProduto) Then ** Search the image from the Photos folder on the server
PictureBox1.Image = Image.FromFile("\\server\cmg\projects\Photos" & IdProduto)
End If
End Sub
On the line
If File.Exists("\\server\cmg\projects\Photos" & IdProduto)
I have 2 problems:
The image's filetype is being read in the comparison with the prdCod and I need to compare only the filename of the image for it to work;
When the image's filename has leading zeros, the comparison doesn't work either.
Note #1: these images will not be saved in the Products table, they will already be in the Photos folder on the server and are only for being shown in picturebox1.
Note #2: the filenames of the images in the Photos folder will always be numbers and will never be repeated, ex.:\
1.png, 2.png, 3.bmp, 4.jpg, 5.bmp, 6.jpeg...
How do I compare only the image filename from the Photos folder, without the filetype, and also ignoring the leading zeros?
You can use the GetFiles method and use a wildcard for the extension.
Private Sub AddImageIfExists(idProduto As String)
Dim folder As New DirectoryInfo("\\server\cmg\projects\Photos")
If folder.Exists Then
Dim file = folder.GetFiles($"{idProduto}.*").FirstOrDefault
If file IsNot Nothing Then
PictureBox1.Image = Image.FromFile(file.FullName)
End If
End If
End Sub
That will add the photo if present.
To call the method:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim IdProduto As String = prdCod
AddImageIfExists(IdProduto)
End Sub
There is no easy solution to that. You basically have to iterate through all files in the folder, use e.g. Path.GetFileNameWithoutExtension and remove leading zeros from that, before you do your comparison.
A better way ofc. would be to redesign the workflow and store the corresponding real filename (or even the fully qualified filename) along with the product code.
I am doing a small system using Ms. Access (the database has more than 10 tables ) connecting to visual studio. I made a public class for opening the connection to the database so I can use it in every form. Everything is working and I can get the data from the database But any inserting or deleting data in forms, the database in ms access not getting the update. I can see the new records in forms but nothing in the database.
Imports System.Data.OleDb
Public Class dbconnectClass1
'create db connection
Private DBcon As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0; Data Source=dental_clinic.accdb;")
'prepare db command
Private dbcmd As OleDbCommand
' db data
Public DBDA As OleDbDataAdapter
Public DBDT As DataTable
'query parameters
Public params As New List(Of OleDbParameter)
'query statics
Public recordcount As Integer
Public Exception As String
Public Sub ExecQuery(query As String)
'reset query status
recordcount = 0
Exception = ""
Try
'open connection
DBcon.Open()
'create db command
dbcmd = New OleDbCommand(query, DBcon)
'load params into dbcommand
params.ForEach(Sub(p) dbcmd.Parameters.Add(p))
'clear params list
params.Clear()
'excute command and fill dataset
DBDT = New DataTable
DBDA = New OleDbDataAdapter(dbcmd)
recordcount = DBDA.Fill(DBDT)
Catch ex As Exception
Exception = ex.Message
End Try
'close the database connection
If DBcon.State = ConnectionState.Open Then DBcon.Close()
End Sub
'include query and command parameters
Public Sub addparam(name As String, value As Object)
Dim newparam As New OleDbParameter(name, value)
params.Add(newparam)
End Sub
End Class
This my code inside the forms:
Public Class NewExpense
Private access As New dbconnectClass1
' a varuble having the appointment Id to connect between 2 forms
Private appointmentNo As Integer
Private Function NoError(Optional report As Boolean = False) As Boolean
If Not String.IsNullOrEmpty(access.Exception) Then
If report = True Then MsgBox(access.Exception)
Return False
Else
Return True
End If
End Function
Private Sub Savebuttum_Click(sender As Object, e As EventArgs) Handles
Savebuttum.Click
Dim oDate As DateTime = Convert.ToDateTime(DateTimePicker1.Value)
access.addparam("#expensenme", expensenmtXT.Text)
access.addparam("#expensedetail", ExpenseDetailTXT.Text)
access.addparam("#expenseamount", ExpenseAmountTXT.Text)
access.addparam("#expensedate", oDate)
access.addparam("#expensepaidTo", paidtoTXT.Text)
access.ExecQuery("INSERT INTO Expense (Expenses_name, expense_details,
expenses_amount, ExpenseDate_Paid, ExpensePaid_To) Values (#expensenme,
#expensedetail, #expenseamount, #expensedate, #expensepaidTo);")
'report on errors
If Not String.IsNullOrEmpty(access.Exception) Then
MsgBox(access.Exception) : Exit Sub
'success
access.DBDA.Update(access.DBDT)
MsgBox("Expense Has been Added Successfully")
End Sub
End Class
Hum, you have this:
params.ForEach(Sub(p) dbcmd.Parameters.Add(p))
Great, we add the parmaters - looks good to go!!!
then, next line CLEARS all the work above!!! (the parameters are removed!!!!)
'clear params list
params.Clear()
Next up? Many will build a connection object, then a reader, and then a adaptor. But you ONLY need a data adaptor if you going to update a data table. if you just going to execute a command, then you don't need the data table, and you don't need a adaptor FOR that table. Adaptor = ability to modify a existing datatable (or dataset).
You are MUCH better to use the command object.
Why?
Because the command object has a connection object (don't need a separate one)
Because the command object has a data reader for you (no need for a whole data adaptor to JUST fill a table. And remember, you don't need a whole data adaptor UNLESS you are going to send/update a data table back to the database.
And because the command object has the command text, then you don't even need a variable for that!!!
And because all objects are in "one object", then really all you need is something to handy get you the connection.
So, for your insert example, we really don't gain by having that object, do we?
Ok, so here is your insert code without using those extra objects:
so in the following, I declare ONE variable, - the sql command object.
And do the insert
And as FYI? Your save button is not a save - but a insert button - every time you hit it, you will insert a new row. Lets deal with that issue in a bit.
So, here is our code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using cmdSQL As New OleDbCommand("INSERT INTO Expense
(Expenses_name, expense_details,expenses_amount, ExpenseDate_Paid, ExpensePaid_To)
Values (#expensenme,#expensedetail, #expenseamount, #expensedate, #expensepaidTo)",
New OleDbConnection(My.Settings.TESTOLEDB))
With cmdSQL.Parameters
.Add("#expensenme", OleDbType.WChar).Value = expensenmtXT.Text
.Add("#expensedetail", OleDbType.WChar).Value = ExpenseDetailTXT.Text
.Add("#expenseamount", OleDbType.Currency).Value = ExpenseAmountTXT.Text
.Add("#expensedate", OleDbType.DBDate).Value = DateTimePicker1.Value
.Add("#expensepaidTo", OleDbType.WChar).Value = paidtoTXT.Text
End With
cmdSQL.Connection.Open()
cmdSQL.ExecuteNonQuery()
End Using
So things in above:
We have strong data typing and converstion.
Because of this, note how I did NOT have to create a separate date/time variable here.
Note the SAME for "money" or so called currency conversion - again strong data type by using parameters this way.
And is this a date only, or a date+ time value? If it is date, then
.Add("#expensedate", OleDbType.DBDate).Value = DateTimePicker1.Value
If it was/is a date + time, then this:
.Add("#expensedate", OleDbType.DBTimeStamp).Value = DateTimePicker1.Value
So, notice how all your object stuff REALLY did not help one bit, and in fact you did not really save code, and above actually had LESS variables defined to do the whole job.
Now, back to the insert issue/problem. (you save is doing a insert). But what about editing existing records?
So, I would suggest you work out the problem this way:
You create/get/have/assume a data row for the form.
The form takes the data row, fills the controls. You edit, and when you hit save, the data row is sent back to the datbase. So, once this works, then to add? Well, the add code will CREATE a new data row, save to database and THEN you send that new data row to the above eixsting form that can edit a data row, and can save a data row. So the form now is able to deal with both issues (adding vs editing existing). If the user dont' want the row, then you offer a delete button.
And REALLY nice is a data row means you don't deal with SQL, and don't deal with parmaters!!
So the code (desing pattern) I use is thus this:
dim da as oledbDataAdaptor
dim myTable as DataTable = MyRstEdit("SELECT * from tblHotels WHERE ID = " & lngID,da)
dim MyDataRow as DataRow = myTable.Rows(0)
' code to fill controls
txtHotelName.Text = MyDataRow("HotelName")
txtCity.Text = MyDataRow("City")
' etc. etc. etc.
Now to save? Well I put the values back into that DataRow like this:
MyDataRow("HotelName") = txtHotelName.Text
MyDataRow("City") = txtCity.Text
MyDateRow("BookingDate") = txtTimePick1.Value
da.Update(MyTable)
Notice how I don't have parameters, and even strong data type checking occurs for say the above Date/Time booking date column.
And the above is nice, since I don't have to deal with ANY parmaters to udpate a row of data.
The MyRstEdit routine looks like this and returns byREf a "da" (data adaptor).
Public Function MyrstEdit(strSQL As String, Optional strCon As String = "", Optional ByRef oReader As SqlDataAdapter = Nothing) As DataTable
' Myrstc.Rows(0)
' this also allows one to pass custom connection string - if not passed, then default
' same as MyRst, but allows one to "edit" the reocrdset, and add to reocrdset and then commit the update.
If strCon = "" Then
strCon = GetConstr()
End If
Dim mycon As New SqlConnection(strCon)
oReader = New SqlDataAdapter(strSQL, mycon)
Dim rstData As New DataTable
Dim cmdBuilder = New SqlCommandBuilder(oReader)
Try
oReader.Fill(rstData)
oReader.AcceptChangesDuringUpdate = True
Catch
End Try
Return rstData
End Function
So, now in vb.net, I actually find it is LESS code then even writing + using recordsets in MS-Access VBA code.
However, BEFORE you go down ANY of the above road?
Have you considered using the vb.net data binding features. Data-binding in vb.net means that you do NOT write ANY of the above code. it means that vb.net will do all of the dirty work, and write and setup ALL OF the code for you to edit data on a form. The end result is you don't write any code to update a table.
You do have to use + create a "data set". Once done, then you just drag controls onto the form, and you even get this. So you just drop in a dataset, table adaptor, Binding navagator, and you get this:
Note now the tool bar at the top (and you can place it on teh bottom if you wish). So you get this:
So that WHOLE form was created without having to write ONE line of code. And you can see we have navigation, edits and saves and even the ability to add. So, you can build up a editing form - and it thus becomes similar to say working in MS-Access and ZERO lines of code is required to build the above form.
However, if you ARE going to roll your own code? Then use a data row. That way you can shuffle data to/from the table, and NOT have to use parmaters and SQL update and insert statements - but ONLY have nice clean code in which you shove, or get values from that data row. .net will "write" all the update stuff for you.
I'm using PowerPoint 2016 for a Wallboard display and I'd like it to pull a number from an MSSQL-Server table. I can get the SQL data into Powerpoint easily enough but I would like the data to refresh every day automatically and leave the wallboard running continuously.
I have a textbox on a slide that pulls data from an SQL VBA script. Is there a way to automatically run the script each time the slide is shown while the presentation is running or have the script run once every 24 hours to refresh the textbox?
This is a tricky and interesting problem and I'll admit that this is most of the solution, but there is a missing part. I haven't done much programming in Powerpoint, so it was a challenge. When you open the VBE, there are no modules, classes, or objects of any kind available. This is quite different from Word and Excel. This means we're all on our own...
I created a Presentation with five slides. On the third slide, I inserted some objects so it looked like this:
For the VBA part, the first thing I had to learn was to create a class module that will catch all of the events for the presentation. My very simple class is called EventClassModule and looks like this:
Option Explicit
Public WithEvents App As Application
Private Sub App_SlideShowNextSlide(ByVal Wn As SlideShowWindow)
If Wn.View.CurrentShowPosition = 3 Then
UpdateTheCount Wn.View.Slide
End If
End Sub
So now in a regular module, you have to run something to create and initialize this class as a global object.
Option Explicit
Dim eventClass As EventClassModule
Sub InitializeApp()
Set eventClass = New EventClassModule
Set eventClass.App = Application
End Sub
In my testing, I just manually ran the InitializeApp procedure to create the global object. This is the missing part of my solution... - I don't know how to automatically run this initialization to create the object. Maybe a ribbon button?
Then, also in the regular code module is the procedure to update the textbox:
Public Sub UpdateTheCount(ByRef thisSlide As Slide)
'--- loop through all the shapes to find the correct textbox,
' then update the value for display
Const LABEL_TEXT As String = "Value to update:"
Dim shp As Shape
For Each shp In thisSlide.Shapes
If shp.Type = msoTextBox Then
Dim theText As String
theText = shp.TextFrame.TextRange.Text
If InStr(1, theText, LABEL_TEXT, vbTextCompare) = 1 Then
Dim colonPos As Long
Dim theValue As Long
colonPos = InStr(1, theText, ":", vbTextCompare)
theValue = CLng(Right$(theText, Len(theText) - colonPos))
theValue = theValue + 1
theText = LABEL_TEXT & " " & theValue
shp.TextFrame.TextRange.Text = theText
End If
End If
Next shp
End Sub
Once your code is in place and you've run the InitializeApp() sub manually, just start the slide show (and probably set it to loop from the end) and step through it. The textbox value will update and increment automatically.
I'm interested in learning how to kick this off automatically from someone who's got that experience.
I'm working on a VB project that has a lot of comboboxes and numericupdown items.
Lets say we have ComboBox1, 2, 3, 4, and 5; and we have NumericUpDown1, 2, 3, 4, 5.
When the user clicks the "Save" button, I want to save all of their selected combobox items and numericupdown numbers to a CSV file. Is there an elegant/automatic way to tie all of the .SelectedIndex and .Value for these items to an array so I can easily write the array out to a CSV?
The only way I know to do this so far is to manually associate each one with an array position:
Arr(0) = ComboBox1.SelectedIndex
Arr(1) = ComboBox2.SelectedIndex
...
Arr(5) = NumericUpDown1.Value
Arr(6) = NumericUpDown2.Value
...
etc.
This wouldnt be too bad, except I have a LOT of these items, and writing a line for each one seems silly. I'm new to VB, so this might be an obvious solution to some. Any ideas?
Having them bound to an array would be really handy because I also allow the user to Load a CSV file, which I would like to automatically populate the ComboBoxes and NumericUpDowns from the CSV values. The only way I know to do this is to manually move each array item to the respective combobox/numeric item when they click the Load file button:
ComboBox1.SelectedIndex = Arr(0)
ComboBox2.SelectedIndex = Arr(1)
...
NumericUpDown1.Value = Arr(5)
NumericUpDown2.Value = Arr(6)
...
etc.
Edit: Here is some application info as requested...
The CSV file that can be saved/loaded looks like this:
#"Device Info","123456","asdfgh","0000","1.0x","1"
000F,0000,0032,0000,00C8,0001,0078,0101,0000,0001,0000,0001
010F,0078,0000,0103,0001,0000,000A,0005,0007,0006,0000
0001,000A,000A,000A,000A,0005,0005,0005,0002
...etc
The header line just has serial number, version, and other misc info; it is automatically generated by the target device. All of the other lines are configuration setpoints that the target device reads in and automatically configures itself. I'm writing this PC program to be able to edit (and create from scratch) these configuration CSV files with a nice GUI interface. Each item is tied to a specific setpoint, such as 000F = Language, 0032 = System Frequency, 00C8 = System Voltage, etc. The easiest way I saw to make this configuration program was to use numeric entry and drop-down comboboxes that the user can select what they want. Each NUD and CBOX equates to one of the CSV file data fields.
You can use Controls.Find() to get a reference to the desired control based on an index value. Here's a quick example to demonstrate what I mean:
For i As Integer = 1 To 30
Dim matches() As Control = Me.Controls.Find("NumericUpDown" & i, True)
If matches.Length > 0 AndAlso TypeOf matches(0) Is NumericUpDown Then
DirectCast(matches(0), NumericUpDown).Value = i
End If
Next
You can incorporate code like that into the load/save routines.
I would use binary serialization. This eliminates the need to format strings or xml when saving control properties. Similar to Plutonix's solution, it only works on certain types of control. However, it can be modified to work on any type of control - but only supports a single property to be loaded for each control. It will work on all controls of type X instead of controls named xName. You can add further restrictions by grouping the controls to be serialized in a panel or other means.
Make a new Form called Form1. Add some NumericUpDowns, TextBoxes, and ComboBoxes. Put some values in the ComboBoxes at design time, otherwise calling loadState() in Form_Load will be meaningless. However, loadState() can be called whenever (i.e. after the comboboxes have been populated).
You will need to import these two namespaces:
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
In your class:
Private Shared stateFileName As String = "SavedState.bin"
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
saveState(Me)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
loadState(Me)
End Sub
And these are the methods you will use to save and load states. The save state method:
Private Shared Sub saveState(instance As Form)
Dim controlProperties As Dictionary(Of String, Object) =
instance.Controls.OfType(Of Control).ToDictionary(Of String, Object)(
Function(c) c.Name,
Function(c)
' You can support different types of controls here too
If TypeOf c Is NumericUpDown Then
Return CType(c, NumericUpDown).Value
ElseIf TypeOf c Is ComboBox Then
Return CType(c, ComboBox).SelectedIndex
Else
' All other controls get their text property saved
' .Text is a property of Control
Return c.Text
End If
End Function)
Using myFileStream As Stream = File.Create(stateFileName)
Dim serializer As New BinaryFormatter
serializer.Serialize(myFileStream, controlProperties)
End Using
End Sub
The load state method:
Private Shared Sub loadState(instance As Form)
If File.Exists(stateFileName) Then
Using myFileStream As Stream = File.OpenRead(stateFileName)
Dim deserializer As New BinaryFormatter()
Dim controlProperties = CType(deserializer.Deserialize(myFileStream), Dictionary(Of String, Object))
For Each c As Control In instance.Controls
If controlProperties.ContainsKey(c.Name) Then
' You can support different types of controls here too
If TypeOf c Is NumericUpDown Then
CType(c, NumericUpDown).Value = CDec(controlProperties(c.Name))
ElseIf TypeOf c Is ComboBox Then
CType(c, ComboBox).SelectedIndex = CInt(controlProperties(c.Name))
Else
c.Text = controlProperties(c.Name).ToString()
End If
End If
Next
End Using
End If
End Sub
You should add exception handling, and note that a binary file is not meant to be edited by a human without the assistance of a machine.
The title should give a fair overview of the problem but I'm running a dynamic named range for use in a combo box in a userform. When I run the form, the values appear as intended. When I call a module sub-routine via a command button, the values don't appear and I've no idea why.
I'll paste all code and highlight the offending snippet(s) below:
Private Sub btnGetGAToken_Click()
'--------------------------------
'Obtain API Token from Google Analytics (GA), indicate to user that token has been obtained and populate Account combobox
'with a unique list of accounts, which will in turn populate the Profile combobox with the profiles associated with the chosen
'account
'--------------------------------
Dim txtEmailField As String
Dim txtPasswordField As String
'Values written to sheet for use in UDFToken and UDFGetGAAcctData array formulas
Range("FieldEmail").Value = Me.txtEmailField.Text
Range("FieldPassword").Value = Me.txtPasswordField.Text
Range("GAToken").Calculate
With Me.lblGATokenResponseField
.Caption = Range("GAToken").Value
.ForeColor = RGB(2, 80, 0)
End With
Call FindUniqueAccountNames
cboAccountNamesComboBox.RowSource = Sheet1.Range("ListUniqueAccountNames").Address
End Sub
Private Sub cboAccountNamesComboBox_Change()
'Value written to sheet for use in the 'ListProfileNames' dynamic, named range
Range("ChosenAccount").Value = Me.cboAccountNamesComboBox.Value
With Me.cboProfileNamesComboBox
.Value = ""
.RowSource = Sheets("CodeMetaData").Range("ListProfileNames").Address
End With
End Sub
The dynamic range was created using the name manager and is below:
Named Range: "ListUniqueAccountNames" =OFFSET(CodeMetaData!$J$5,0,0,COUNTA(CodeMetaData!$J$5:$J$5000))
and for ease of reference, the code I'm using to run it is below:
cboAccountNamesComboBox.RowSource = Sheets("CodeMetaData").Range("ListUniqueAccountNames").Address
The sub-routine calling the userform is here:
Public Sub ShowReportSpecsForm()
Load frmReportSpecs
frmReportSpecs.Show
End Sub
Forgive me for posting so much of the code, but I'm not sure exactly what it is that's causing the problem - I'm still very much a rookie with forms.
Any help will be greatly appreciated. Thanks.
If you are using the rowsource property and named ranges then I would suggest setting the rowsource property of the combobox's at design time. Then to debug where required use:
Debug.Print Range("ListUniqueAccountNames").Address
This will return the named range address to the immediate window where you can check it is correct.
Remember that the property Address from a named dynamic range returns a normal static address.
For example, Range("ListUniqueAccountNames").Address can returns $J$5:$J$20.
You do not need use a Excel address in RowSource property. You can use a Excel name.
Besides, when you show a Userform it is necessary refresh the RowSource property from a ComboBox or ListBox control in order to update its values. (Excel control does not watch if the range or data change)
That refresh can be made inside Activate event (it runs immediately before form Show and it is shown below) and any situation where data or range changes.
Private Sub UserForm_Activate()
Me.cboAccountNamesComboBox.RowSource = ""
Me.cboAccountNamesComboBox.RowSource = "ListUniqueAccountNames"
End Sub