How to load a form on the basis of previous execution? - vb.net

I have made a form in Vb.net in which the user can do several changes. I want the changes which the user makes should remain each time it is executed.
For Example, I have applied some effects on font while execution -
Private Sub Checkbox1_CheckedChanged ()
If CheckBox1.Checked = True then
Label1.Font = New Drawing Style ("Comic Sans Ms", 16, FontStyle.Bold)
End If
End Sub
The changes which the user makes should be visible next time the program is executed.
Is it possible?
Thanks in Advance!

You have a couple of options, The one that I would use if you are just going to have a few settings is the built in User Settings, you can create a setting in your Project Propertys. This is an example of your Font.
You would then use it like this.
Private Sub Checkbox1_CheckedChanged() Handles CheckBox1.CheckedChanged
If CheckBox1.Checked = True Then
Label1.Font = New Font("Comic Sans Ms", 16, FontStyle.Bold)
My.Settings.MyNewFont = Label1.Font
My.Settings.Save()
End If
End Sub
Public Sub New()
' This call is required by the designer.
InitializeComponent()
If Not IsNothing(My.Settings.MyNewFont) Then Label1.Font = My.Settings.MyNewFont
End Sub

Yes you can use external Css file for this purpose
1st you create a external Css file of your project and specify some default style
Like your Css file
.Fstyle {
font-family: Comic Sans Ms;
}
And your aspx
<asp:label id="lblname" runat="server" class="Fstyle">
2nd you can re-write to this file by the changes which the user makes
save the changes to the variables and override this file by using IO function of vb.net it remains the changes permanent.

Marks answer is how i would do it Except if you wish to store changes for a number of different controls you might be best to loop through them and check the settings on form closing and store them in a System.Collections.Specialized.StringCollection in My.Settings then you can loop through this collection on Form Load and set the settings as they should be.
Let me know if you want an example and i can provide it.
Example:
Note: I have always found when adding a System.Collections.Specialized.StringCollection you need to add an empty string manually (you can then delete it) to force it to create the .xml (probably a more technical solution to this but it works for me)
I created one called FormLoadData
To identify which controls to store data for I prefixed their names with "UC_", if it is all the controls on the form or all in a groupbox etc then you can skip this check.
then add the following form closing sub:
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
'clear collection
My.Settings.FormLoadData.Clear()
'loop through each control on the form
For Each Con As Control In Me.Controls
'run check to see if it is a control to store changes for
If Con.Name.Substring(0, 3) = "UC_" Then
'load any settings you wish to store in the collection starting with the control ID
My.Settings.FormLoadData.Add(Con.Name & "," & Con.Font.ToString)
End If
Next
End Sub
and the following under New:
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
'loop through controls
For Each Con As Control In Me.Controls
'loop through settings to compare
For Each item In My.Settings.FormLoadData
'check stored name against control name
If Con.Name = item.Split(",").ElementAt(0) Then
'update settings
Con.Font = New Font(item.Split(",").ElementAt(1), Con.Font.Size)
MsgBox(item)
Exit For
End If
Next item
Next Con
End Sub
There is certainly a better way of doing this rather then the loop through the collection created but unless you are storing the data for thousands of controls this will work OK.
My browser is playing up so i cannot check what this looks like but hopefully it is clear!

Related

Most efficient way to programmatically update and configure forms and controls

I am looking for a way to prevent user form controls from appearing one by one when I'm programmatically adding them and for ways to enhance application performance and visual appeal.
Say, I have a Panel_Top in which I programmatically add comboboxes. What is happening is that they appear one by one as they are created and I am looking for a way to suspend the refreshing of the panel and or user form to make all of those programmatically added comboboxes to appear at the same time and faster than it happens right now.
I've tried suspendlayout which doesn't do anything for me or maybe I'm doing it wrong.
MyForm.PanelTop.SuspendLayout = true
And also I've tried to set the Panel_Top to invisible like:
MyForm.Top_Panel.visible = false
Which kind of sorta looks and performs better, or it might be a placebo.
What is the correct approach to this problem?
PS: I do have form set to doublebuffer = true, if that matters
What I tend to do is create a loading modal to appear on top of the form rendering the controls that need to be created/made visible, this can optionally have a progress bar that gets incremented as the control is created/shown. With the loading modal running, the container that needs to add the controls starts with SuspendLayout, adds the controls, and then finished with ResumeLayout.
This makes it so that controls are added/shown while giving the user a visual indicator that something is going on behind the scenes.
Here is a phenomenal example of a loading modal: https://www.vbforums.com/showthread.php?869567-Modal-Wait-Dialogue-with-BackgroundWorker and here is an example of using it:
Private ReadOnly _controlsToAdd As New List(Of Control)()
Private Sub MyForm_Show(sender As Object, e As EventArgs) Handles MyBase.Shown
Using waitModal = New BackgroundWorkerForm(AddressOf backgroundWorker_DoWork,
AddressOf backgroundWorker_ProgressChanged,
AddressOf backgroundWorker_RunWorkerCompleted)
waitModal.ShowDialog()
End Using
End Sub
Private Sub backgroundWorker_DoWork(sender As Object, e As DoWorkEventArgs)
Dim worker = DirectCast(sender, BackgroundWorker)
For index = 1 To 100
_controlsToAdd.Add(New ComboBox() With {.Name = $"ComboBox{index}"})
worker.ReportProgress(index)
Threading.Thread.Sleep(100) ' Zzz to simulate a long running process
Next
End Sub
Private Sub backgroundWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
Dim percentageCompleted = e.ProgressPercentage / 100
' do something with the percentageCompleted value
End Sub
Private Sub backgroundWorker_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
PanelTop.SuspendLayout()
PanelTop.Controls.AddRange(_controlsToAdd.ToArray())
PanelTop.ResumeLayout()
End Sub
SuspendLayout() is the correct way to handle this with WinForms.
But first of all, this is a function you call, and not a flag you set.
Secondly, don't forget to call ResumeLayout() at the end of the changes.
Finally, you need to ensure you only call them once when you start to change around the controls in the panel and then again at the very end. If you use them with every control you won't get any benefit.
So the pattern might look something like this:
Public Sub SomeMethod()
PanelTop.SuspendLayout() ' Prevent the panel from updating until you've finished
' Make a bunch of changes
PanelTop.Controls.Clear()
For Each ...
PanelTop.Controls.Add( ... )
Next
PanelTop.ResumeLayout() ' Allow the panel to show all the changes in the same WM_PAINT event
End Sub
You also need to ensure you don't have anything in there like DoEvents()/Invalidate() that might invoke the windows message loop and cause the form to redraw itself.

location of recent files list vb.net

I am looking to add a recent files list to an application I am writing.
I was thinking of adding the recent files to an xml file.
Where should this file be stored?
And how should it be called from the code?
I would imagine the xml would be stored in the same folder that the application is installed in, but not everybody will install the application in the same directory.
Is there a way to code it in such a manner that it will always be stored in the same folder as the application will be installed in?
much thanks in advance!
Here is an example using My.Settings. It requires you to open the Settings page of the project properties and add a setting of type StringCollection named RecentFiles as well as a ToolStripMenuItem with the text "Recent".
Imports System.Collections.Specialized
Public Class Form1
Private Const MAX_RECENT_FILES As Integer = 10
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
LoadRecentFiles()
End Sub
Private Sub LoadRecentFiles()
Dim recentFiles = My.Settings.RecentFiles
'A StringCollection setting will be Nothing by default, unless you edit it in the Settings designer.
If recentFiles Is Nothing Then
My.Settings.RecentFiles = New StringCollection()
recentFiles = My.Settings.RecentFiles
End If
'Get rid of any existing menu items.
RecentToolStripMenuItem.DropDownItems.Clear()
'Add a menu item for each recent file.
If recentFiles.Count > 0 Then
RecentToolStripMenuItem.DropDownItems.AddRange(recentFiles.Cast(Of String)().
Select(Function(filePath) New ToolStripMenuItem(filePath,
Nothing,
AddressOf RecentFileMenuItems_Click)).
ToArray())
End If
End Sub
Private Sub UpdateRecentFiles(filePath As String)
Dim recentFiles = My.Settings.RecentFiles
'If the specified file is already in the list, remove it from its old position.
If recentFiles.Contains(filePath) Then
recentFiles.Remove(filePath)
End If
'Add the new file at the top of the list.
recentFiles.Insert(0, filePath)
'Trim the list if it is too long.
While recentFiles.Count > MAX_RECENT_FILES
recentFiles.RemoveAt(MAX_RECENT_FILES)
End While
LoadRecentFiles()
End Sub
Private Sub RecentFileMenuItems_Click(sender As Object, e As EventArgs)
Dim menuItem = DirectCast(sender, ToolStripMenuItem)
Dim filePath = menuItem.Text
'Open the file using filePath here.
End Sub
End Class
Note that the Load event handler includes a bit of code to allow for the fact that a setting of type StringCollection will be Nothing until you assign something to it. If you want to avoid having to do that in code, do the following.
After adding the setting, click the Value field and click the button with the ellipsis (...) to edit.
Add any text to the editor and click OK. Notice that some XML has been added that includes the item(s) you added.
Click the edit button (...) again and delete the added item(s). Notice that the XML remains but your item(s) is gone.
That XML code will cause a StringCollection object to be created when the settings are first loaded, so there's no need for you to create one in code.
EDIT:
I tested that by adding the following code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using dialogue As New OpenFileDialog
If dialogue.ShowDialog() = DialogResult.OK Then
UpdateRecentFiles(dialogue.FileName)
End If
End Using
End Sub
I was able to add ten files to the list via that Button and then they started dropping off the end of the list as I added more. If I re-added one that was already in the list, it moved to the top. If I closed the app and ran it again, the list persisted.

Controls not populated when form loaded for second time

I've got a program which calls a second form. This second form has a combo box populated from the contents of an external file, and the user needs to select an option in the combo box from the presented options. This selection is then passed back to the main form where a lot of work is done.
This all works nicely the first time it is done. However, the second time this second form is called, the drop-down is blank. I've confirmed via some debugging that the correct code is being run and that entries are being added via "SecondForm.ComboBox1.Items.Add" (I can clear the combobox, check it's zero, read the data and then check the items in the list again, it increases correctly) but they're just not being displayed on the form. I can't figure out why or how to fix it.
And so the pertinent parts of the code....
At the form level I have this line to set up the second form, I believe I need the WithEvents to pass the selected data back as far as I can tell:
Public Class Form1 Friend WithEvents SecondForm As New Form2
Public Sub OpenStripformatstxtToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles OpenStripformatstxtToolStripMenuItem.Click
Dim fd As OpenFileDialog = New OpenFileDialog()
Dim pos1 As Integer
Dim pos2 As Integer
' Select the file to open
fd.Title = "Open File Dialog"
fd.InitialDirectory = "C:\BEST\Data"
fd.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
fd.FilterIndex = 1
fd.RestoreDirectory = True
' Put the filename selected in strfilename2
If fd.ShowDialog() = DialogResult.OK Then
strFileName2 = fd.FileName
Else : Return
End If
If SecondForm.IsDisposed Then
Dim secondform As New Form2
I suspect this line above is the problem, I'm creating the form a second time but WITHOUT the WithEvents paramater. However I can't use that from within this part of the code, I get an error "'WithEvents' is not a valid local variable declaration". I've read that closing and reopening forms is not good coding and that I should be hiding / showing them
secondform.Show()
InitializeComponent()
Else
SecondForm.Show()
End If
' Copy the file contents to a string called sfcontents (Strip Format Contents)
sfcontents = My.Computer.FileSystem.ReadAllText(fd.FileName)
' Define some points in the string, starting at the beginning
pos1 = 1
pos2 = 1
' Loop from the start to the end of the string
For pos1 = 1 To Len(sfcontents)
' Look for FO, the strip name header, do the following if you find it
If Mid(sfcontents, pos1, 3) = "FO " Then
pos1 = pos1 + 3
pos2 = pos1 + 1
'Find the space after "FO " so we've captured the whole next word, that's the strip name
Do Until Mid(sfcontents, pos2, 1) = " "
pos2 = pos2 + 1
Loop
' Add that strip name to the combobox for selecting by user
SecondForm.ComboBox1.Items.Add(Mid(sfcontents, pos1, pos2 - pos1))
It is this line above which is populating the ComboBox, but that data is NOT being displayed on the form which is shown to the user after the first instance of the form is shown
End If
' Next step in the string
Next pos1
End Sub
Private Sub secondform_formclosing(sender As Object, e As FormClosingEventArgs) Handles SecondForm.FormClosing
There's a few hundred lines of code in here which then work with the data passed from the form closing, ie the selected value of the ComboBox. This all works fine for the first running of the code, but as the ComboBox is empty on subsequent runs, it doesn't work after that. Happy to post that code if anyone thinks it'll help, but I think it'll just muddy the issue at this stage as that code seems fine. However, see the bit below about event handlers...
End Sub
The code on Form2.vb is as follows:
Public Class Form2
Public selectedstrip As String '= ComboBox1.SelectedItem
Public stripfunction As Integer
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If RadioButton1.Checked Then stripfunction = 1
If RadioButton2.Checked Then stripfunction = 2
If RadioButton3.Checked Then stripfunction = 3
selectedstrip = ComboBox1.SelectedItem
Me.Close()
End Sub
End Class
I've read a bit online that says closing and reopening forms is not, excuse the pun, good form. However I'd then need event handlers for form.hide and I can't seem to work out how to use them or even what they are. If hiding the form is a better alternative solution, if someone could point me in the right direction for how to do that and what handlers to use instead, then I'd be grateful.
I'm probably doing something incredibly stupid, because everything I'm doing is self-taught from googling and I probably lack a greater understanding of WHY I need to do certain things, so apologies for any ignorance on my part. With that in mind, if I'm doing anything a completely silly way I'm open to rewriting it in a way that helps, but I may need some hand-holding to do that!
Thanks in advance for any help anyone can give.
The main problem appears to he here:
If SecondForm.IsDisposed Then
Dim secondform As New Form2
You are declaring a new local variable there and assigning the new Form2 object to that rather than the member variable, so when you later refer to the member variable to populate the ComboBox, you're not referring to the Form2 instance you just created.
Your code is rather weird anyway. Here's my advice.
Firstly, get rid of the code form Form1 that populates the ComboBox in Form2. Forms should populate their own controls. Put the code to populate the ComboBox in the Load event handler of Form2. You're then guaranteed that any time you call Show on a new instance of Form2, the code to populate the ComboBox will be executed. That's how a form should work.
As an alternative, given that you're reading from a file and that data probably won't change over the course of a session, read the data and put it into an array in Load event handler of Form1 and then pass that array to the constructor of Form2. You would have to write that constructor yourself and, in it, you would populate the ComboBox with the array data. That way, you're not reading and processing the same data file over and over but you're still populating Form2's controls in Form2.
Secondly, change this code:
If SecondForm.IsDisposed Then
Dim secondform As New Form2
secondform.Show()
InitializeComponent()
Else
SecondForm.Show()
End If
to this:
If SecondForm.IsDisposed Then
'Create and display a new instance.
Secondform = New Form2
Secondform.Show()
Else
'Focus the existing instance.
SecondForm.Activate()
End If
Note that there is no local variable, so the new instance is assigned to the member variable.
There is also no call to InitializeComponent. That method is what creates and configures the controls on a form based on the actions in the designer. The ONLY place that gets called is in a constructor.
Finally, if an instance of Form2 is already displayed, its Activate method is called to make sure that it has focus.

Can I use variables to control which PictureBox I am using?

Is there a way that I can use a variable to control which PictureBox I am using in Visual Basic?
I.e.:
CurrentNumber = 1
PictureBox(CurrentNumber).backcolour = backcolour
You can use the Me.Controls(String) indexer. It lets you specify the name (as a string) of the control you want to access, thus you can dynamically access a picture box by concatenating the string "PictureBox" with a number.
Dim TargetPictureBox As PictureBox = TryCast(Me.Controls("PictureBox" & CurrentNumber), PictureBox)
'Verifying that the control exists and that it was indeed a PictureBox.
If TargetPictureBox IsNot Nothing Then
TargetPictureBox.BackColor = Color.Red
End If
Alternatively, to save processing power by avoiding looping through the entire control collection every time you can call the OfType() extension on Me.Controls, storing the result in an array sorted by the controls' names. That way it'd only have to iterate the control collection once.
'Class level - outside any methods (subs or functions).
Dim PictureBoxes As PictureBox() = Nothing
'Doesn't necessarily have to be done in a button, it's just an example.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If PictureBoxes Is Nothing Then
PictureBoxes = Me.Controls.OfType(Of PictureBox).OrderBy(Function(p As PictureBox) p.Name).ToArray()
End If
'NOTE: CurrentNumber - 1 is necessary when using an array!
PictureBoxes(CurrentNumber - 1).BackColor = Color.Red
End Sub
NOTE: This solution will only work properly if all your picture boxes are named "PictureBox1", "PictureBox2", etc. If you suddenly skip a number ("PictureBox3", "PictureBox5", "PictureBox6") then PictureBoxes(CurrentNumber - 1) for CurrentNumber = 5 would return PictureBox6 rather than PictureBox5.
What you really should do is create a PictureBox() and use that to reference your picture boxes via an index.
The best way to build your array is to create a method that builds the array from the references created by the designer. This lets you continue to use the designer to create your controls and it makes your code check for deleted controls at design-time. Using Me.Controls(...) suffers from run-time errors if controls you are looking for have been deleted.
Here's the code you need:
Private _PictureBoxes As PictureBox() = Nothing
Sub AssignPictureBoxesArray
_PictureBoxes = {PictureBox1, PictureBox2, PictureBox3}
End Sub
Then you access them like this:
Sub SomeMethod
Dim CurrentNumber = 1
Dim PictureBox = _PictureBoxes(CurrentNumber - 1)
PictureBox.BackColor = System.Drawing.Color.Red
End Sub

Dynamically adding an ActiveX control to VB.Net form does nothing

In VB.Net, I'm trying to add a QuickTime ActiveX control to a Form when the user clicks a button.
My code is below. For testing I've got a design-time ActiveX control, "designed_control", which works fine, but I'm trying to place "dynamically_created_control" onto the form.
Public Class Form1
Private moviePath As String = "\\localhost\D$\Temp\Test.mov"
Friend WithEvents dynamically_created_control As AxQTOControlLib.AxQTControl = Nothing
Private Sub buttonLoadMovieIntoExisting_Click(sender As Object, e As EventArgs) Handles buttonLoadMovieIntoExisting.Click
' load movie into control created in designer, works fine:
MessageBox.Show(moviePath)
With designed_control
.URL = moviePath
MessageBox.Show("URL:" + .URL)
End With
End Sub
Private Sub buttonCreateNewControl_Click(sender As Object, e As EventArgs) Handles buttonCreateNewControl.Click
' create a new ActiveX control when button is clicked:
dynamically_created_control = New AxQTOControlLib.AxQTControl
CType(dynamically_created_control, System.ComponentModel.ISupportInitialize).BeginInit()
Me.SuspendLayout()
Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(Form1))
With dynamically_created_control
.CreateControl()
.Enabled = True
.Location = New System.Drawing.Point(160, 160)
.Name = "new_control"
.OcxState = CType(resources.GetObject("designed_control.OcxState"), System.Windows.Forms.AxHost.State)
.Size = New System.Drawing.Size(480, 270)
.TabIndex = 0
Me.Controls.Add(Me.dynamically_created_control)
.Visible = True
.URL = moviePath
End With
CType(dynamically_created_control, System.ComponentModel.ISupportInitialize).EndInit()
Me.ResumeLayout(True)
With dynamically_created_control
MessageBox.Show("URL:" + vbCrLf + .URL)
.Movie.Play()
End With
End Sub
End Class
This doesn't work; when I click 'buttonCreateNewControl', it will pop up the 'URL:' messagebox with the correct URL, showing that the properties of dynamically_created_control are being set and the object is not nothing. However the rectangular shape of the control I'd expect doesn't appear on the form. As soon as I call the .Play() method of the control, it raises an exception because .Movie is nothing, when it shouldn't be.
Can anyone spot why the dynamically-generated ActiveX control simply doesn't appear (but without throwing errors), when the designer-based version is absolutely fine?
Incidentally, I'm aware of the security issues around QuickTime, which is why I'm now trying to code something which can optionally use QuickTime if the user decides to.
Thanks
I believe you may need to update buttonCreateNewControl_Click to use
.OcxState = CType(resources.GetObject("dynamically_created_control.OcxState"), System.Windows.Forms.AxHost.State)
Hope this helps.
I have spotted the error in my code.
The .createControl() method was attempting to create an external QuickTime window. That control works for something like VLC Player, but QuickTime doesn't support it, so nothing was happening.
As soon as I commented out createControl(), the behaviour became what I was expecting.
The .createControl() was a leftover from code I found online, I assumed it was essential for initiating the ActiveX control but it isn't.