How to exchange two class by value ,not reference? - vb.net

There are two classes in ArrayList.I want to exchange all their values but not their reference.For example , arraylist1(1) refers to the same class after exchanging , with its value changed.
If I change them in this way
Dim someclass1 as new someclass
Dim someclass2 as new someclass
arraylist1(1) = someclass1
arraylist1(2) = someclass2
temp = arraylist1(1)
arraylist1(1) = arraylist1(2)
arraylist1(2) = temp
It only exchange the reference.arraylist1(1) just refers to someclass2 but actually I want it refers to someclass1 with someclass2's value.
Now I just exchange their important properities one by one.

You need to copy properties yourself, one by one. You can make it a bit easier by adding two methods to your class:
Public Class SampleClass
Public Property Id As Integer
Public Property Name As String
Public Function Clone() As SampleClass
Return New SampleClass With
{
.Id = Me.Id,
.Name = Me.Name
}
End Function
Public Sub Init(input As SampleClass)
With Me
.Id = input.Id
.Name = input.Name
End With
End Sub
Public Shared Sub SwapValues(value1 As SampleClass, value2 As SampleClass)
Dim temp = value1.Clone()
value1.Init(value2)
value2.Init(temp)
End Sub
End Class
And then when trying to swap values:
SampleClass.SwapValues(list(0), list(1))

Related

Error 'Value cannot be null:nodeName' when creating new iteration

I'm getting an error when attempting to create a new Iteration using the Client SDK:
Value cannot be null.
Parameter name: nodeName
As a test, I tried creating it using Postman and the REST API—as suggested here—and it succeeded.
I've been using this successfully for quite a while now to stub out my sprint hierarchy for the year. This is the first such occurrence of this error—past year runs have gone off without a hitch. I haven't changed anything (that I know of) since last year's successful run.
As we can see, the iteration's Name property is being properly set. I tried Overloads instead of Shadows, but that didn't help.
How can I troubleshoot this to find out what nodeName is and how to populate it using the Client SDK?
Here's my code:
Module Main()
Private Sub AddYear(Year As Integer, Client As WorkItemTrackingHttpClient)
Dim oIterationYear As Classifications.Iteration
Dim dFinishDate As Date
Dim dStartDate As Date
Console.WriteLine($"Year:{vbTab}{vbTab}{Year}")
dFinishDate = New Date(Year, 12, 31)
dStartDate = New Date(Year, 1, 1)
oIterationYear = New Classifications.Iteration(Client, TeamProject, Year, dStartDate, dFinishDate)
oIterationYear.Save()
...
End Sub
End Module
Public Class Iteration
Inherits Base
Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Name As String, StartDate As Date, FinishDate As Date)
Me.New(Client, TeamProject, Name, StartDate, FinishDate, Nothing)
End Sub
Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Name As String, StartDate As Date, FinishDate As Date, Parent As Iteration)
MyBase.New(Client, TeamProject, Parent)
Me.StructureType = TreeNodeStructureType.Iteration
Me.FinishDate = FinishDate
Me.StartDate = StartDate
Me.Name = Name
End Sub
...
End Class
Public MustInherit Class Base
Inherits WorkItemClassificationNode
Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Parent As Base)
Me.ProjectName = TeamProject.ToDescription
Me.Parent = Parent
Me.Client = Client
End Sub
Public Sub Save()
If Me.Parent.IsNothing Then
Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me, Me.ProjectName, Me.StructureType).Result <-- Error
Else
Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me, Me.ProjectName, Me.StructureType, path:=Me.Path).Result
End If
End Sub
...
Public Shadows Property Name As String
Get
If Me.Node.IsNothing Then
Name = Me._Name
Else
Name = Me.Node.Name
End If
End Get
Set(Value As String)
Me._Name = Value
End Set
End Property
Private _Name As String
End Class
Note: this is a language-agnostic question, thus I've intentionally omitted the VB.NET tag. An answer can come in either VB.NET or C#—I'm fine with either one.
-- EDIT --
Based on the design suggestions found in the accepted answer, I've come up with this solution that works:
Public MustInherit Class Base
Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Parent As Base)
Me.Node = New WorkItemClassificationNode With {
.StructureType = StructureType,
.Name = Name
}
Me.ProjectName = TeamProject.ToDescription
Me.Parent = Parent
Me.Client = Client
Me.Name = Name
End Sub
Public Sub Save()
If Me.Parent.IsNothing Then
Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me.Node, Me.ProjectName, Me.StructureType).Result
Else
Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me.Node, Me.ProjectName, Me.StructureType, path:=Me.Path).Result
End If
End Sub
...
Public Property Name As String
Get
Return Me.Node.Name
End Get
Private Set(Value As String)
Me.Node.Name = Value
End Set
End Property
End Class
Essentially all I did was remove the base class' inheritance from WorkItemClassificationNode and store a node reference internally in all cases. I also simplified the Name property implementation.
As for why it suddenly stopped working with no change in my code, the only thing I can think of is the remote possibility that there was a change in the compiler that affected how the SDK evaluates the Shadows and Overloads keywords. That's a long shot, I know, but I'm at a complete loss otherwise.
Bottom line, it works now.
I can create new iteration using Microsoft.TeamFoundation.WorkItemTracking.WebApi in Azure DevOps Services .NET SDK.
Please check out below example:
class Program
{
static void Main(string[] args)
{
Uri accountUri = new Uri("https://dev.azure.com/org/");
string personalAccessToken = "pat";
VssConnection _connection = new VssConnection(accountUri, new VssBasicCredential(string.Empty, personalAccessToken));
WorkItemTrackingHttpClient workItemTrackingHttpClient = _connection.GetClient<WorkItemTrackingHttpClient>();
Iteration iteration = new Iteration(workItemTrackingHttpClient,2021, "projectName");
iteration.SaveNode();
}
}
public class Iteration
{
WorkItemTrackingHttpClient client;
WorkItemClassificationNode node;
string project;
string path;
public Iteration(WorkItemTrackingHttpClient client, int Year, string project, string path=null) {
this.client = client;
node = new WorkItemClassificationNode();
this.project = project;
this.path = path;
IDictionary<string, object> DateAttr = new Dictionary<string, object>();
DateAttr.Add("startDate", new DateTime(Year, 1, 1));
DateAttr.Add("finishDate", new DateTime(Year, 12, 31));
node.Attributes = DateAttr;
node.Name = Year.ToString();
node.StructureType = TreeNodeStructureType.Iteration;
}
public void SaveNode()
{
var res = client.CreateOrUpdateClassificationNodeAsync(node, project, TreeStructureGroup.Iterations, path).Result;
Console.WriteLine(res.Id);
}
}
See below result:
I can reproduce above error Value cannot be null. Parameter name: nodeName. If i intentionally didnot set the node.Name = null; You can debug your code to check why the node name was not set.

dim Pull data from lower variable to higher

I have
Dim m_LedgerList As New Ejm.Financial.Entities.LedgerList
m_LedgerList = My.StaticData.LedgerList
m_LedgerList.Filter = "LedgerfunctionID = 2"
but the filter i put on m_LedgerList.Filter pull trough to My.StaticData.LedgerList
Any idees how to stop the filter to go up?
This line
m_LedgerList = My.StaticData.LedgerList
makes your variable m_LedgerList reference the same data referenced by the variable My.StaticData.LedgerList. This is not a copy, this is merely two variables that look at the same data in memory. Thus any action that you perform on the m_LedgerList variable acts on the same data seen by the My.StaticData.LedgerList.
If you want to have a different set of data then you need to duplicate the original data in a new memory location. This could be done inside the LedgerList class with something like this
Public Class LedgerList
Public Function Duplicate() As LedgerList
' This create a new memory area for a new LedgerList
Dim result = new LedgerList()
' Code to duplicate and add the elements in this
' class to the new list
return result
End Function
End Class
Now you can go with
m_LedgerList = My.StaticData.LedgerList.Duplicate()
m_LedgerList.Filter = "LedgerfunctionID = 2"
You can implement ICloneable as follows:
Class LedgerList
Implements ICloneable
Property Property1 As String
Property Filter As String
Public Function Clone() As Object Implements ICloneable.Clone
Dim myclone As New LedgerList
myclone.Property1 = Me.Property1
myclone.Filter = Me.Filter
Return myclone
End Function
End Class
Then to make a copy of your object:
Dim m_LedgerList As Ejm.Financial.Entities.LedgerList = DirectCast(My.StaticData.LedgerList.Clone, Ejm.Financial.Entities.LedgerList)
m_LedgerList.Filter = "LedgerfunctionID = 2"

Get value of a property with propertyinfo object

Is there a way to get value of a object properties with a propertyinfo object?
psudo code:
propertyinfoObject = Text
myobject.toCommand(propertyinfoObject)
The psudo code above should do the same as
myobject.Text
My goal is to create a simpel Properties form that will work on any object (Later I will use keywords to filter out what options I want the use to see).
My real code
Public Class PropertiesForm
Dim propertyInfoVar() As PropertyInfo
Dim Properties As New Form2
Dim listItem As New ListViewItem
Dim stringarray() As String
Public Sub New(ByRef sender As Object)
propertyInfoVar = sender.GetType().GetProperties()
For Each p In propertyInfoVar
stringarray = {p.Name.ToString, #INSERT VALUE SOMEHOW HERE#}
listItem = New ListViewItem(stringarray)
Properties.ListView1.Items.Add(listItem)
Next
Properties.Visible = True
End Sub
EDIT
Just use propertyGrid as suggested below!
The standard PropertyGrid already does all that for you. Filtering properties is not so obvious, here's how:
The control includes a BrowsableAttributes property which allows you to specify that only properties with the specified attribute value should be shown. You can use existing attributes, or custom ones. This is specifically for tagging visible props:
<AttributeUsage(AttributeTargets.Property)>
Public Class PropertyGridBrowsableAttribute
Inherits Attribute
Public Property Browsable As Boolean
Public Sub New(b As Boolean)
Browsable = b
End Sub
End Class
Apply it to an Employee class to hide pay rates or anything else:
Public Class Employee
<PropertyGridBrowsable(True)>
Public Property FirstName As String
...
<PropertyGridBrowsable(False)>
Public Property PayRate As Decimal
<PropertyGridBrowsable(False)>
Public Property NationalInsuranceNumber As String
Test code:
Dim emp As New Employee With {.Dept = EmpDept.Manager,
.FirstName = "Ziggy",
.PayRate = 568.98D,
...
.NationalInsuranceNumber = "1234567"
}
propGrid.BrowsableAttributes = New AttributeCollection(New PropertyGridBrowsableAttribute(True))
propGrid.SelectedObject = emp
BrowsableAttributes is a collection, so you can add several.

How to create a compound object in VBA?

I cannot make my way through the Microsoft help, which is great provided you know what the answer is already, so I'm stuck.
Is it possible for me to create my own compound object (I assume that this is the term) such that, for example, the object could be a person and would have the following sub-classes:
Firstname - String
Surname - String
Date of birth - Datetime
Gender - String (M/F accepted)
Height - Real number
Sorry if it seems like a very basic question (no pun intended) but I haven't used Visual Basic for a long time, and Microsoft Visual Basic was never my forté.
You should consider using class modules instead of types. Types are fine, but they're limited in what they can do. I usually end up converting my types to classes as soon as I need some more function than a type can provide.
You could create a CPerson class with the properties you want. Now if you want to return a FullName property, you can write a Property Get to return it - something you can't do with a type.
Private mlPersonID As Long
Private msFirstName As String
Private msSurname As String
Private mdtDOB As Date
Private msGender As String
Private mdHeight As Double
Private mlParentPtr As Long
Public Property Let PersonID(ByVal lPersonID As Long): mlPersonID = lPersonID: End Property
Public Property Get PersonID() As Long: PersonID = mlPersonID: End Property
Public Property Let FirstName(ByVal sFirstName As String): msFirstName = sFirstName: End Property
Public Property Get FirstName() As String: FirstName = msFirstName: End Property
Public Property Let Surname(ByVal sSurname As String): msSurname = sSurname: End Property
Public Property Get Surname() As String: Surname = msSurname: End Property
Public Property Let DOB(ByVal dtDOB As Date): mdtDOB = dtDOB: End Property
Public Property Get DOB() As Date: DOB = mdtDOB: End Property
Public Property Let Gender(ByVal sGender As String): msGender = sGender: End Property
Public Property Get Gender() As String: Gender = msGender: End Property
Public Property Let Height(ByVal dHeight As Double): mdHeight = dHeight: End Property
Public Property Get Height() As Double: Height = mdHeight: End Property
Public Property Get FullName() As String
FullName = Me.FirstName & Space(1) & Me.Surname
End Property
Then you can create a CPeople class to hold all of your CPerson instances.
Private mcolPeople As Collection
Private Sub Class_Initialize()
Set mcolPeople = New Collection
End Sub
Private Sub Class_Terminate()
Set mcolPeople = Nothing
End Sub
Public Property Get NewEnum() As IUnknown
Set NewEnum = mcolPeople.[_NewEnum]
End Property
Public Sub Add(clsPerson As CPerson)
If clsPerson.PersonID = 0 Then
clsPerson.PersonID = Me.Count + 1
End If
mcolPeople.Add clsPerson, CStr(clsPerson.PersonID)
End Sub
Public Property Get Person(vItem As Variant) As CPerson
Set Person = mcolPeople.Item(vItem)
End Property
Public Property Get Count() As Long
Count = mcolPeople.Count
End Property
Public Property Get FilterByGender(ByVal sGender As String) As CPeople
Dim clsReturn As CPeople
Dim clsPerson As CPerson
Set clsReturn = New CPeople
For Each clsPerson In Me
If clsPerson.Gender = sGender Then
clsReturn.Add clsPerson
End If
Next clsPerson
Set FilterByGender = clsReturn
End Property
With this class, you can For Each through all the instances (google custom class and NewEnum to see how to do that). You can also use a Property Get to return a subset of the CPerson instances (females in this case).
Now in a standard module, you can create a couple of CPerson instances, add them to your CPeople instance, filter them, and loop through them.
Public Sub FillPeople()
Dim clsPerson As CPerson
Dim clsPeople As CPeople
Dim clsFemales As CPeople
Set clsPeople = New CPeople
Set clsPerson = New CPerson
With clsPerson
.FirstName = "Joe"
.Surname = "Blow"
.Gender = "M"
.Height = 72
.DOB = #1/1/1980#
End With
clsPeople.Add clsPerson
Set clsPerson = New CPerson
With clsPerson
.FirstName = "Jane"
.Surname = "Doe"
.Gender = "F"
.Height = 62
.DOB = #1/1/1979#
End With
clsPeople.Add clsPerson
Set clsFemales = clsPeople.FilterByGender("F")
For Each clsPerson In clsFemales
Debug.Print clsPerson.FullName
Next clsPerson
End Sub
There's defintely more learning curve to creating classes, but it's worth it in my opinion.
I think you need to use TYPE syntax, like this:
TYPE person
Firstname As String
Surname As String
Date_of_birth As Date ' instead of Datetime
Gender As String '(M/F accepted)
Height As Single 'instead of Real number
END TYPE
Sub Test()
Dim aTest As person
End Sub

How to declare a global variable with multiple properties?

I want to use a variable that's in the scope of all my project, what's a good way to accomplish this?
public User as (some type)
(
var (sometype)
var2 (sometype)
)
Example:
If User.name = "ADMIN" Then
otherForm.Caption = User.name
otherForm.Show
End If
You could create a class that encapsulates all of this data inside of it:
Example:
Public Class User
Public Property Name As String
Public Property Age As Integer
Sub New(_Name As String, _age As Integer)
Name = _Name
Age = _age
End Sub
End Class
Then, you'd just declare it, and set the properties:
Dim U as new User("Thomas", 18)
Messagebox.Show(U.Name) ' Will print "Thomas"
I suggest you define a class for such 'global' properties.
For example, you could name it 'ProjectSettings'.
Public Class ProjectSettings
Public Shared CurrentUser as String
Public Shared DateTimeFormat as String
...etc...
Public Shared Sub Initialize()
'Initialize your members here
End Sub
End Class
From outside, you could access it like this:
ProjectSettings.CurrentUser
ProjectSettings.DateTimeFormat
But remember, there are heaps of different approaches of how to do this.
In the above case, you could also define the Members as Readonly Properties, making sure nobody accidentally overwrites the values. Or you could define an object 'User' for CurrentUser, if you need to store more data.
It really depends on what you want to achieve with your global properties. It's only important to keep them central so that everybody in your team (including yourself) knows where to find them. Else it can easily lead to unstructured, bad code.
If you are trying to have a "settings" class like some have suggested, you probably want to look at the My.Settings or My.Resources namespaces for VB .Net
You would end up with something like:
If User.name = My.Settings.Admin Then
otherForm.Caption = User.name
otherForm.Show
End If
Is this what you are trying to do?
Your other option is to use a module or a "Public NotInheritable" class with a private constructor, with public properties or constants. Like this:
Public NotInheritableClass ProjectSettings
Public Const Admin as String = "ADMIN"
Public Const Whatever as Decimal = 3.14D
Private Sub New()
End Sub
End Class
Then you could have:
If User.name = ProjectSettings.Admin Then
otherForm.Caption = User.name
otherForm.Show
End If
I like these solutions a little better because there is no way that you can instantiate the settings class.
If you just want your User class to be globally accessible (which implies there is only one given User at a time), then you could do something similar with the User class.
EDIT: Your User class would look like:
Public NotInheritableClass User
Public Const Name as String = "Some Name"
Public Property YouCanChangeThisProperty as String = "Change Me"
Private Sub New()
End Sub
End Class
To use it:
User.YouCanChangeThisProperty = "Changed"
MessageBox.Show("User name: " & User.Name & "; the other property is now: " & User.YouCanChangeThisProperty")
This will give you a message box with:
"User name: Some Name; the other property is now: Changed"
You can create New Class named User
Public Class User
Private mstrName As String
Private mdBirth As Date
Public Property Name() As String
Get
Return mstrName
End Get
Set(ByVal vName As String)
mstrName = vName
End Set
End Property
Public Property BirthDate() As Date
Get
Return mdBirth
End Get
Set(ByVal vBirth As Date)
mdBirth = vBirth
End Set
End Property
ReadOnly Property Age() As Integer
Get
Return Now.Year - mdBirth.Year
End Get
End Property
End Class
You can use this class like this :
Dim Name1 as New User
Name1.Name = "ADMIN"
Name1.BirthDate = CDate("February 12, 1969")
Then Check it (by Msgbox or whatever) :
Msgbox(Name1.Name)
Msgbox(Name1.BirthDate.ToString & " and Now is " & format(Name1.Age) & " years old")