Since I am doing the same thing over and over, I would like to write a generic function that is passed the form that is calling the function, another form name that has a field with the value. A field on my form that is passing the value and a field off another form. I would think this should be easy.
for a simple example:
call setdefaultvalues(form1 as object, form2 as object, field1 as object, field2 as object)
function setdefaultvalues(form1 as ojbect, form2 as object, field1 as object, field2 as object)
If CurrentProject.AllForms(form1).IsLoaded Then
form1.field1.defaultvalue = form2.field2
end if
end function
Can someone give me a hand with this.Thanks!
Here is one way to pass the names of objects & then reference...
Private Sub Command6_Click()
' Pass the names of the objects.
Call setdefaultvalues(Me.Name, Me.Name, "txtField1", "txtField2")
End Sub
Function setdefaultvalues(form1 As String, form2 As String, field1 As String, field2 As String)
If CurrentProject.AllForms(form1).IsLoaded Then
Forms(form1).Controls(field1).DefaultValue = Forms(form2).Controls(field2)
End If
End Function
Related
I have a problem that has been bugging me for a while now. Consider this code:
Public Class Class1
Dim VariableList as New List(of Object) From {MainForm.X, MainForm.Y,
SettingsForm.Z, SettingsForm.Textbox1.Text} '...etc.
Sub SetToZero()
For Each Element in VariableList
Element = 0
Next
End Sub
Sub SetToCustomValue(value As Double)
For Each Element in VariableList
Element = value
Next
End Sub
Sub LoadValuesFromFile()
Dim path As String = MainForm.GetPath()
For Each Element in VariableList
Element = File.Readline()
Next
End Sub
Sub SaveValuesToFile()
Dim path As String = MainForm.GetPath()
For Each Element in VariableList
Element = File.Writeline()
Next
End Sub
'and more similar functions/subs
As you can see, what this class does is that it takes lot of different variables from different places into a collection, and then various functions read or write values to every variable in that collection using loops. In this example, I have just a few variables, but most of the time there are dozens.
Reading the values is not a problem. Writing them, is, because when I declare that VariableList at the top of my class, that List just makes a copy of each variable, rather than maintaining a reference to it. Meaning that if, say, one of the functions modifies the MainForm.X in that List, the actual variable MainForm.X is not modified. To work with references, I would have to forgo loops, and assign every single variable manually, in every function. Which is obviously a lot of bad code. I want to declare that list of variables only once, and then use loops, like in this example code that I wrote above. My question is, how can I make such a container (List, Array, whatever) that would retain the references to the original variables in it, and make the code above possible?
There is no easy way to store pointers to variables in VB.NET. As a workaround, you can use a class to store your variables, as a class is always used as a pointer.
Here's an example of a way to achieve this with a ContainerClass which own a Dictionary of integers. One interest of this method would be that you can declare and name "variables" dynamically. In reality, they will be managed KeyValuePair. Once you have instantiated a copy of this class, you can use it to "manage" your variables by using this class as your pointer.
I included a loop which set all the integers to the same number just for fun, and to demonstrate the kind of manipulation which would end up having an effect similar to one of those described in your question.
Public Class Form2
'This is the container class which will be used to bypass the lack of pointers
'if you wanted to change a property, like the window width, it would be more difficult, but simples variables will be no trouble
Private variableContainer As New VariableContainer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
variableContainer.AddVar("X", 5)
variableContainer.AddVar("Y", 15)
Debug.Print(variableContainer.GetVar("X"))
Debug.Print(variableContainer.GetVar("Y"))
variableContainer.SetAllVar(42)
Debug.Print("Next line will print 42")
Debug.Print(variableContainer.GetVar("X"))
End Sub
End Class
Public Class VariableContainer
'I know a public variable wouldn't need the fancy functions down there, but it's usually better to encapsulate, especially if you're working with a team
'and "future you" count as a teammate, never forget that...
Private list As New Dictionary(Of String, Integer)
Public Sub AddVar(ByVal name As String, ByVal value As Integer)
list.Add(name, value)
End Sub
Public Function GetVar(ByVal name As String) As Integer
If list.ContainsKey(name) Then
Return list(name)
Else
'I choose -1 arbitrarily, don't put too much thinking into this detail
Return -1
End If
End Function
Public Sub SetVar(ByVal name As String, ByVal num As Integer)
If list.ContainsKey(name) Then
list(name) = num
End If
End Sub
Public Sub SetAllVar(ByVal num As Integer)
Dim dict As New Dictionary(Of String, Integer)
For Each item As KeyValuePair(Of String, Integer) In list
dict.Add(item.Key, num)
Next
list = dict
End Sub
End Class
Have fun!
all! I'm developing a BlackJack game but I've run into a little bit of a problem. When calculating score, I have to type YourCard1.Text, YourCard2.Text, YourCard3.Text, etc.
Can I make a function that gets the right label each time it's called? I want to do this so I don't have to type so much...
For example, instead of typing out "YourCard1.Text", I want to be able to type "card(1)" Is this possible? I've tried multiple ways of doing this, but to no avail. I'm having trouble figuring out how to make it work.
Assuming you have those labels on your form, YourCard1.Text, YourCard2.Text, YourCard3.Text, etc., This function should work for you. It returns the Label itself, not the Text property.
Private Function card(index As Integer) As Label
Try
Return Me.Controls.
OfType(Of Label).
Where(Function(l) l.Name = "YourCard" & index.ToString()).
Single()
Catch
Return Nothing
End Try
End Function
Note: Me.Controls returns the controls directly inside the form, but doesn't return controls inside containers in the form. If your cards are inside a panel, Panel1 for example, you would do Return Panel1.Controls.OfType(Of Label)...
Usage:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
card(1).Text = "Hello"
card(2).Text = "World"
End Sub
Edit to address comment.
You are pidgeonholed into only those semantics. So there is another way I could think of. But I wouldn't personally do this.
Public Class Form1
Private Class cardClass
Private myContainer As Control
Sub New(container As Control)
myContainer = container
End Sub
Default Public WriteOnly Property Item(ByVal index As Long) As String
Set(value As String)
card(index).Text = value
End Set
End Property
Private Function card(index As Integer) As Label
Try
Return myContainer.Controls.
OfType(Of Label).
Where(Function(l) l.Name = "YourCard" & index.ToString()).
Single()
Catch
Return Nothing
End Try
End Function
End Class
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim card As New cardClass(Me)
card(1) = "Hello"
card(2) = "World"
End Sub
End Class
The reason it's so complex is that though String is a reference type, it uses value type semantics. So when returning a string from a function, it can't refer back to the original memory location: it actually creates a copy of the string. So using function semantics won't work. Same would go for an array. It would be difficult (impossible?) to modify a string from either a function or array and have it modify the Label's Text property.
I am developing a window application in VB.net and i as using backgroundworker.
Maybe it is a very simple question, but is it possible report the progress as a double number and not as the integer part of the progress percentage?
I need the full number in order to display some more info, and i can only do this when i know the exact iteration the algorithm is in.
Is there any easy way doing so?
Thanks in advance!
The ReportProgress method has two overloads. The first one takes only a percentProgress As Integer parameter, but the second one takes an additional userState As Object parameter. With that second overload, you can pass any type of data that you want. In your case, you could pass a Double value as your user-state, like this
BackgroundWorker1.ReportProgress(0, myDouble)
Then, in the ProgressChanged event handler, you can convert the value back to a Double, like this:
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Dim myDouble As Double = CDbl(e.UserState)
' ...
End Sub
As in the above example, if you don't need the percentProgress parameter, you can just pass a value of 0 for that parameter. You are not limited to passing just one or two values either. If you need to pass additional information, such as a status string, you could do so by creating your own class to encapsulate all of the status-related data and then pass one of those objects as your userState parameter. For instance:
Public Class MyUserState
Public Property MyDouble As Double
Public Property StatusDescription As String
End Class
Then you could call the ReportProgress method like this:
Dim myState As New MyUserState()
myState.MyDouble = 1.1
myState.StatusDescription = "Test"
BackgroundWorker1.ReportProgress(0, myState)
Then, you can read the values like this:
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Dim myState As MyUserState = DirectCast(e.UserState, MyUserState)
' ...
End Sub
In my application I have a TabControl with some tabs. Each tab contains many components. If the application is closing I want to check If a value of any component has changed. If so, I will ask a user if he wants to save it or not.
I want to know how you solve this situation (because it is standard behaving when application is closing). I thought that I have some flag (bool) and I set an event ValueChanged to each component. If the method handlings this event is fired, this flag is set to true. In case of closing application I will only check if flag is true.
But the problem is that there is more than 30 components and create method handling the event to each component it seems not efectve to me.
You're on the right track wit the boolean flag and the ValueChanged event. As far as I'm aware, that's really the only way to deal with this kind of thing. To accomplish this you could simply writing the handling for the event once and then copy and paste the component over and over as needed.
However, to your question of effectiveness when spread across 30 components, you should consider rolling your own component(s) that inherit from a basal class which exposes an IsDirty property or similar. When closing you could then loop through all controls on your tabs to see any have IsDirty set to true.
Unfortunately, given that you've already created the interface, neither approach will solve your current dilemma.
When you fill in the text/value for each control, also fill in the TAG with the same value. Then you can compare the TAG against the text/value for each control to see if anything changed.
To avoid having to write code for each control (when checking) you cah do a for loop on each control in [Tab Page Name].Controls().
Another way to do this is to extend each control by adding a IsDirty property and overriding the validation event. Then you can set this if it changed. You might also want to have a method to RESET the IsDirty property.
Yet another way, I always bind to a class, it just makes my code less error-prone and gives me intellisense. Also gives you tons of features that you can easily throw in like this. Then just BIND to your custom class.
Here is an example of how I would do this using a custom class. This is just an example of reading a csv and writting it but the key, to answer your question, is in the "Dirty" code.
Imports System.ComponentModel
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
'On form load, open the file and read the data
Using SR As New System.IO.StreamReader("Test.csv")
Do While SR.Peek >= 0
'Put each line into it's own instance of the class
BindingSource1.Add(New MyData(SR.ReadLine))
Loop
End Using
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
'When closing, see if any data has been changed and ask to save
Dim bDirty As Boolean = False
For Each dat As MyData In BindingSource1
If dat.IsDirty Then
bDirty = True
Exit For
End If
Next
If bDirty Then
'Example code for saving
Select Case MessageBox.Show("Do you want to save your changes?", "Save Changes", MessageBoxButtons.YesNoCancel)
Case Windows.Forms.DialogResult.Cancel
e.Cancel = True
Case Windows.Forms.DialogResult.Yes
'Here you should remove the old file, I like to rename it to BAK,
' save the new file, then you can get rid of it.
' Just in case there is a problem saving.
If System.IO.File.Exists("Test.csv") Then System.IO.File.Delete("Test.csv")
Using SW As New System.IO.StreamWriter("Test.csv", False)
For Each dat As MyData In BindingSource1
SW.WriteLine(dat)
Next
End Using
End Select
End If
End Sub
End Class
Public Class MyData
Implements INotifyPropertyChanged
'Event that implements INotifyPropertyChanged. This tells the binding to refresh a property in the UI.
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Public Sub New(ByVal SomeDataToLoad As String)
'Take you data and parse it or whatever into the various properties
'This example uses a comma-seperated string
Dim sWords As String() = SomeDataToLoad.Split(",")
_FirstName = sWords(0)
_LastName = sWords(1)
End Sub
''' <param name="PropertyName">Case-Sensative property name</param>
Public Sub ForcePropertyChanged(ByVal PropertyName As String)
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(PropertyName))
End Sub
Private _IsDirty As Boolean
Public ReadOnly Property IsDirty As Boolean
Get
Return _IsDirty
End Get
End Property
''' <summary>Override the ToString method for getting the data back out, in this case as comma seperated again. You can then write this to file or whatever.</summary>
Public Overrides Function ToString() As String
Return FirstName & "," & LastName
End Function
'--Properties you can bind to------------------------------------------------
Private _FirstName As String
Public Property FirstName As String
Get
Return _FirstName
End Get
Set(value As String)
_FirstName = value
_IsDirty = True
ForcePropertyChanged("FirstName")
End Set
End Property
Private _LastName As String
Public Property LastName As String
Get
Return _LastName
End Get
Set(value As String)
_LastName = value
_IsDirty = True
ForcePropertyChanged("LastName")
End Set
End Property
End Class
I did not go into how to bind here, you can find that all over the web, but I did put the data in a BindingSource so the rest will be easy. Notice that when the form is closing, I can loop through EVERY record to see if there were changes, easily. If you only had a single record, you wouldn't even have to loop, just ask if it is dirty.
Hi
I am using following code to run a private function.
I have two values in my combo box, One and Two and two private functions with the same names, Private Sub One() and Private Sub Two()
I want my application to call the function whatever value user choses in the combo box.
If One is chosen in the combo box, Private function one should be called.
Thanks
Code is below, that does not work
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim vrValue = ComboBox1.Items(1)
Call vrValue()' In this case vrValue is Two, so Two() should be called.
End Sub
Private Sub two()
MsgBox("Function called")
End Sub
Make your subs functions (the only difference is the returning of a value) and put them in their own class:
Public Class RunFunctions
Dim oMessageBox As MessageBox
Public Function One() As String
'oMessageBox = MessageBox
Return "Message One"
End Function
Public Function Two() As String
Return "Message Two"
End Function
End Class
Add Each function from the class as an item in your combo box:
Public Class Combo_Functions
Dim oRunFunction As RunFunctions
Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As Object _
, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
MessageBox.Show(ComboBox1.Items(ComboBox1.SelectedIndex()))
End Sub
Private Sub Combo_Functions_Load(ByVal sender As Object _
, ByVal e As System.EventArgs) Handles Me.Load
oRunFunction = New RunFunctions
ComboBox1.Items.Add(oRunFunction.One())
ComboBox1.Items.Add(oRunFunction.Two())
End Sub
End Class
When the combo box is changed (or use the code for the button click) the messagebox for the correct function is executed.
Dim vrValue = ComboBox1.SelectedItem.ToString()
Select vrValue
Case "One"
One()
Else
Two()
End Select
It looks like what you're trying to do is to dynamically call a particular method using a string variable that contains its name. For example, the combo box would contain items "One" and "Two", and you would call the sub named "One" if the first item in the combo box is selected, or the sub named "Two" if the second item is selected. To that end, you may find this article interesting:
http://www.codeproject.com/KB/cs/CallMethodNameInString.aspx
The code in the article is in C#, which shouldn't be too difficult to convert to VB. But here's the translated version of the code for simply invoking a method without passing or returning any parameters (note: I have not tested this code). It simply uses reflection to find the appropriate method:
Public Shared Sub InvokeStringMethod(ByVal typeName As String, ByVal methodName As String)
'Get the type of the class
Dim calledType As Type = Type.[GetType](typeName)
'Invoke the method itself
calledType.InvokeMember(methodName, BindingFlags.InvokeMethod Or BindingFlags.[Public] Or BindingFlags.[Static], Nothing, Nothing, Nothing)
End Sub
You simply pass the name of the class that contains the method(s) you want to call as the typeName and the name of the method itself that you want to call as the methodName:
InvokeStringMethod("MyClass", "Two")