WPF avoid adding a duplicate row - wpf

I'm using vb.net framework 4.5 and WPF project.
I have a button, the function adds a certain product info to a datagrid. In my vb code file I set a product class
Public Class MyProduct
Public Property ItemNumber As String
Public Property ItemDescription As String
Public Property ItemUnitPrice As Double
Public Property ItemQty As Integer
End Class
The button touchdown event
Private Sub Button_TouchDown(sender As Object, e As TouchEventArgs)
Dim dmb As New MyProduct
dmb.ItemNumber = "abc001"
dmb.ItemDescription = "bla bla bla"
dmb.ItemQty = 1
dmb.ItemUnitPrice = 123.45
MyDataGrid.Items.Add(dmb)
End Sub
Currently, if I tap multiple times of this button, the data grid will add multiple duplicated rows for same product. My goal is when multiple same product add to datagrid, only one row shows and each additional tap/click action on the same button will only increase the ItemQty number.
How can I do that? Thanks!

First, you need to prevent inserting twice :
Private Sub buttonAdd_Click(sender As Object, e As RoutedEventArgs) Handles buttonAdd.Click
Dim dmb As New MyProduct
dmb.ItemNumber = New Random().Next(5).ToString()
dmb.ItemDescription = "bla bla bla"
dmb.ItemQty = 1
dmb.ItemUnitPrice = 123.45
Dim dmbSearched As MyProduct = Nothing
For Each dmbs As MyProduct In MyDataGrid.Items
If dmbs.ItemNumber = dmb.ItemNumber Then
dmbSearched = dmbs
Exit For
End If
Next
If dmbSearched Is Nothing Then
MyDataGrid.Items.Add(dmb)
Else
dmbSearched.ItemQty += 1
End If
End Sub
Second the MyProduct class must raise an event when the quantity is changed, otherwise there is no visible change :
Public Class MyProduct : Implements INotifyPropertyChanged
Private Property m_ItemQty As Integer
Public Property ItemQty As Integer
Get
Return m_ItemQty
End Get
Set(value As Integer)
m_ItemQty = value
FirePropertyChanged()
End Set
End Property
Public Sub FirePropertyChanged(<CallerMemberName> Optional propName As String = "")
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
End Sub
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Public Property ItemNumber As String
Public Property ItemDescription As String
Public Property ItemUnitPrice As Double
End Class
Regards

Related

Create table dynamically in WPF (VB.Net)

I want to create an editable table and it size is defined by user (size can be 10*10 or 20*20 or 30*30).
--
I found this topic (here) but it's running in WinForms, and the DataGridView is not supported by WPF.
I tried with a DataGrid, but the following row doesn't working :
Me.DataGridTableau.ItemsSource = dt
--
I tried with a RadGridView (Telerik) but rows are only updatable by ItemsSource property, and like I don't know how many columns will be, I can't create an object which represents the table (x properties for x columns).
Can anybody help me?
You can set the ItemsSource of a DataGrid to any IEnumerable, including a DataView of a DataTable:
Me.DataGridTableau.ItemsSource = dt.DefaultView
If anybody need it, I found a solution using a RadGridView (Telerik) :
Create this class :
Imports System.Dynamic
Imports System.Collections.Generic
Imports System.ComponentModel
Public Class MyDataRow
Inherits DynamicObject
Implements INotifyPropertyChanged
ReadOnly data As IDictionary(Of String, Object)
Public Sub New()
data = New Dictionary(Of String, Object)()
End Sub
Public Sub New(ByVal source As IDictionary(Of String, Object))
data = source
End Sub
Public Overrides Function GetDynamicMemberNames() As IEnumerable(Of String)
Return data.Keys
End Function
Public Overrides Function TryGetMember(ByVal binder As GetMemberBinder, ByRef result As Object) As Boolean
result = Me(binder.Name)
Return True
End Function
Public Overrides Function TrySetMember(ByVal binder As SetMemberBinder, ByVal value As Object) As Boolean
Me(binder.Name) = value
Return True
End Function
Default Public Property Item(ByVal columnName As String) As Object
Get
If data.ContainsKey(columnName) Then
Return data(columnName)
End If
Return Nothing
End Get
Set(ByVal value As Object)
If Not data.ContainsKey(columnName) Then
data.Add(columnName, value)
OnPropertyChanged(columnName)
Else
If data(columnName) <> value Then
data(columnName) = value
OnPropertyChanged(columnName)
End If
End If
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(name As String)
Try
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
Catch
If Debugger.IsAttached Then Debugger.Break()
Throw ' rethrow exception
End Try
End Sub
Protected Sub OnPropertyChanged(event_args As PropertyChangedEventArgs)
RaiseEvent PropertyChanged(Me, event_args)
End Sub
End Class
In your VM, you need a public property :
Private _tableau As ObservableCollection(Of MyDataRow)
Public Property Tableau() As ObservableCollection(Of MyDataRow)
Get
Return _tableau
End Get
Set(ByVal value As ObservableCollection(Of MyDataRow))
_tableau = value
OnPropertyChanged("Tableau")
End Set
End Property
You need to create a method to load your table :
Private Function LoadTableau() As ObservableCollection(Of MyDataRow)
Dim taille As Integer = Me.GetTailleTableau()
If taille = 0 Then Return Nothing
Dim data As New ObservableCollection(Of MyDataRow)()
For i As Integer = 0 To (taille - 1)
Dim row = New MyDataRow()
For j As Integer = 0 To (taille - 1)
'row(String.Format("Column{0}", j)) = String.Format("Cell {0} {1}", i, j)
row(j) = ""
Next
data.Add(row)
Next
Return data
End Function
You need to load your table :
Me.Tableau = Me.LoadTableau()
And you need to bind your table :
<telerik:RadGridView x:Name="RadGridViewTableau" ItemsSource="{Binding Tableau}" >
I hope this help :)

Error while checking row, after Filtering observational collection

I'm having trouble with editing an observable collection... I bind columns in codebehind like so...
Dim dgCheckBoxColumn As New DataGridCheckBoxColumn()
Dim column_username As New DataGridTextColumn()
Dim textColumn2 As New DataGridTextColumn()
Dim textColumn3 As New DataGridTextColumn()
dgCheckBoxColumn.Header = "Selected"
dgCheckBoxColumn.Binding = New Binding("Selected")
dgvResults.Columns.Add(dgCheckBoxColumn)
column_username.Header = "User Name"
column_username.Binding = New Binding("accountName")
dgvResults.Columns.Add(column_username)
textColumn2.Header = "First Name"
textColumn2.Binding = New System.Windows.Data.Binding("firstName")
dgvResults.Columns.Add(textColumn2)
textColumn3.Header = "Last Name"
textColumn3.Binding = New System.Windows.Data.Binding("lastName")
dgvResults.Columns.Add(textColumn3)
Then I create my observablecollection...
Dim oc_userlist As New ObservableCollection(Of user)
Imports System.ComponentModel
Public Class user
Implements INotifyPropertyChanged
Private m_accountname As String
Private m_firstname As String
Private m_lastname As String
Private _Selected As Boolean
Public Property Selected() As Boolean
Get
Return _Selected
End Get
Set(value As Boolean)
_Selected = value
NotifyPropertyChanged("IsChecked")
End Set
End Property
Public Property accountName() As String
Get
Return m_accountname
End Get
Set(value As String)
m_accountname = value
End Set
End Property
Public Property firstName() As String
Get
Return m_firstname
End Get
Set(value As String)
m_firstname = value
End Set
End Property
Public Property lastName() As String
Get
Return m_lastname
End Get
Set(value As String)
m_lastname = value
End Set
End Property
#Region "INotifyPropertyChanged Members"
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
#End Region
#Region "Private Helpers"
Private Sub NotifyPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
#End Region
End Class
Then I add new user to collection, like so...
oc_userlist.Add(New user With { _
.Selected = False, _
.accountName = "awiles", _
.firstName = "Anthony", _
.lastName = "Wiles"
})
I'm trying to filter using a textbox, with the textchanged event, and it's working wonderfully...
Private Sub TextBox_TextChanged(sender As Object, e As TextChangedEventArgs)
Dim result = oc_userlist.Where(Function(w) w.accountName.Contains(txtFilter.Text.ToString) _
Or w.firstName.Contains(txtFilter.Text.ToString) _
Or w.lastName.Contains(txtFilter.Text.ToString))
dgvResults.ItemsSource = result
End Sub
The problem I'm having is once this is filtered, using the textbox, then I try to select something, it tells me
'EditItem' is not allowed for this view.
I'm having issues trying to get around this issue, can anybody point me in the correct direction?
The reason could be the result of your Linq query (with the Where) is not a List(Of T) but an IEnumerable.
Try adding a call to ToList()
Dim result = oc_userlist.Where(Function(w) w.accountName.Contains(txtFilter.Text.ToString) _
Or w.firstName.Contains(txtFilter.Text.ToString) _
Or w.lastName.Contains(txtFilter.Text.ToString)) _
.ToList()
Then tell if it works

Other way to get/set Visual Basic ObservableCollection property

Id like to know if there are other way to set/get value of an observableCollection Property. Please see below example
Let say i have
Public Class Record
public Property Field1 as string
public Property Field2 as string
End Class
Public RecordCollection as new ObservableCollection(Of Record)
Public sRecord as Record
Public Sub TestRecord()
sRecord = new Record
sRecord.Field1 = "test"
sRecord.Field2 = "test2"
RecordCollection.add(sRecord)
listview1.ItemsSource = RecordCollection
End Sub
My Question is How can i set or gets Record properties like below example?
Public Sub TestRecord()
sRecord = new Record
sRecord("Field1") = "test"
sRecord("Field2") = "test2"
End Sub
or is there any other way to this?
Here example of using System.Reflection for your case
Testing class
Public Class Record
Public Property Field1 As String
Public Property Field2 As String
End Class
Example
Dim data As New DataTable()
data.Columns.Add("Field1", GetType(String))
data.Columns.Add("Field2", GetType(String))
Dim dr As DataRow
dr = data.NewRow()
dr.SetField("Field1", "0")
dr.SetField("Field2", "Zero")
data.Rows.Add(dr)
dr = data.NewRow()
dr.SetField("Field1", "1")
dr.SetField("Field2", "One")
data.Rows.Add(dr)
For Each row As DataRow In data.Rows
Dim temp As New Record()
For Each prop As PropertyInfo In GetType(Record).GetProperties()
If data.Columns.Contains(prop.Name) = True Then
prop.SetValue(temp, row(prop.Name))
End If
Next
Console.WriteLine($"{temp.Field1}, {temp.Field2}")
Next
Will print result in the console:
0, Zero
1, One
But again, consider of using Entity Framework or some other ORM(object-relational mapper) framework.
You can use the Default Property functionality in the Record class:
Public Class Record
Private fields As New Dictionary(Of String, String)
Public Property Field1 As String
Get
Return fields("Field1")
End Get
Set(value As String)
fields("Field1") = value
End Set
End Property
Public Property Field2 As String
'Same code as Field1 but with "Field2" of course
Default Public Property Item(field As String)
Get
Return fields(field)
End Get
Set(value)
fields(field) = value
End Set
End Property
End Class
Access is then possible like:
Dim record As New Record
record("Field1") = "Hello Default Property!"
record.Field2 = "Hello Field2 !"
Console.WriteLine(record("Field1"))

Input textbox data into array of objects

I am using the module to declare the public class patient across all forms of my program. What do I have to do in order to input data into the array from three text boxes. One for Names, Heights, and Weights. Thanks
Public Module Module1
Public PatientCount As Integer = 0
Public Class Patient
Public Property Name As String = String.Empty
Public Property Height As Decimal = 0
Public Property Weight As Decimal = 0
End Class
End Module
Dim patients As List(Of Patient) = New List(Of Patient)
For displaying patients in listbox
For Each p As Module1.Patient In Patients
lstPatients.Items.Add(p.Name)
Next
CURRENT CODE:
MODULE
Public Module Module1
Public PatientCount As Integer = 0
Public Patients As List(Of Patient) = New List(Of Patient)
Public Class Patient
Public Property Name As String
Public Property Height As Decimal
Public Property Weight As Decimal
Public Sub New(ByVal name As String, ByVal height As Decimal, ByVal weight As Decimal)
name = _Name
weight = _Weight
height = _Height
End Sub
End Class
End Module
FORM 2 (Entry of Data)
Public Class Form2
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnMainMenu.Click
Me.Close()
End Sub
Private Sub btnEnterPatient_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEnterPatient.Click
patients.Add(New Patient(txtName.Text, CDec(txtHeight.Text), CDec(txtWeight.Text)))
PatientCount = PatientCount + 1
Label1.Text = PatientCount
End Sub
End Class
FORM 3 (Listing of Data)
Public Class Form3
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
For Each p As Patient In Patients
lstPatients.Items.Add("Name: " & p.Name)
lstPatients.Items.Add("Weight: " & p.Weight)
lstPatients.Items.Add("Height: " & p.Height)
lstPatients.Items.Add("___________")
Next
End Sub
End Class
To store the data of an x number of patients, you could have this code in a button-clicked event handler.
Private Sub ButtonOK_Click()Handles BtnOk.Click()
patients.Add(New Module1.Patient(txtName.Text, CDec(txtHeight.Text), CDec(txtWeight.text))
End Sub
And then add this to your Patient class:
Public Module Module1
Public Class Patient
Public Property Name As String = String.Empty
Public Property Height As Decimal = 0
Public Property Weight As Decimal = 0
Public Sub New(_name as String, _height as decimal, _weight as decimal)
Name = _name
Weight = _weight
Height = _height
End Sub
End Class
End Module
Dim patients As List(Of Patient) = New List(Of Patient)
With this, you will create a list of patients every time the btnOk is pressed, and you can easily access any patient and their data.
To put the data in a listbox:
Private Sub BTNLIST_Click()Handles BTNLIST.Click
For Each p As Patient in patients
lstbox.Items.Add("Name: " & p.Name)
lstbox.Items.Add("Weight: " & p.Weight)
lstbox.Items.Add("Height: " & p.Height)
lstbox.Items.Add("___________")
Next
End Sub
OPTIONAL
You could even create a PatientList Class to help you further (but remove the patients List). You could use the PatientList class to add helpful functions, such as finding a patient by name or height.:
Public Class PatientList
Public List(Of Patient) Patients = New List(Of Patient)
Public ReadOnly Property PatientCount() As Integer
Get
Return Patients.Count
End Get
End Property
Public Sub AddPatientToList(name as String, height as decimal, weight as decimal)
Patients.Add(New Patient(name, height, weight)
End Sub
End Class
Dim patients as PatientList
Then add this in your button_click Handler:
Private Sub ButtonOK_Click()Handles BtnOk.Click()
patients.AddPatientToList(New Module1.Patient(txtName.Text, CDec(txtHeight.Text), CDec(txtWeight.text))
End Sub
HTH
One option would be to have a New constructor that takes the 3 values for the properties.
Public Module Module1
Public PatientCount As Integer = 0
Public Class Patient
Public Property Name As String = String.Empty
Public Property Height As Decimal = 0
Public Property Weight As Decimal = 0
Public Sub New(_name As String, _height As Decimal, _weight As Decimal)
Name = _name
Height = _height
Weight = _weight
End Sub
End Class
End Module
Dim patients As List(Of Patient) = New List(Of Patient)
Assuming you've validated the input from the textboxes and converted them to variables(NewName, NewHeight, NewWeight), you would add a new patient something like this:
patients.Add(New Patient(NewName,NewHeight,NewWeight))
If you want to add a patient then you need to create an instance of one and set it properties with the textboxes of whatever form your using. Then add the new patient to the list of patients, so something like this.
Dim patient as new Patient()
patient.Name = txtName.Text()
patient.Height = txtHeight.Text()
patient.Weight = txtWeight.Text()
patients.Add(patient)

WPF Datagrid DataGridComboBoxColumn Autogenerate at runtime

I have a datagrid in my vb.net 4.0 wpf project. I have seen many examples in XAML on how to bind a DataGridComboBoxColumn however I need to do this in code as I have auto-generating columns. I switch the datagridbinding source to multiple data sets.
Inside some of these custom classes are a couple lists. I can get text and checkboxes to auto-generate correctly. When it comes across my array (I have tried many different kinds) I just see a textboxcolumn with the words (Collection) in it.
For instance - One screen I am grabbing system information on WMI calls. One of the calls returns all IP addresses on the server (We can have up to 8 IP addresses) I do not want a column per IP address. I would like to include a list of those into the datagrid so you can drop down and see them.
Any suggestions on if this is possible or if I am doing something wrong?
Thank you
Sample Code
Imports System.Collections.ObjectModel
Class MainWindow
Dim ServerInfoArray As ObservableCollection(Of ServerInfo) = New ObservableCollection(Of ServerInfo)
Private ReadOnly _ipAddresses As ObservableCollection(Of String) = New ObservableCollection(Of String)
Private Sub GetInfo(ByVal list As List(Of String))
For Each server As String In list
Dim tempip As List(Of String) = New List(Of String)
Dim sinfo As ServerInfo = New ServerInfo
tempip.Add("192.129.123.23")
tempip.Add("23.213.223.21")
sinfo.IPArray = tempip
sinfo.Servername = server
ServerInfoArray.Add(sinfo)
Next
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
Dim serverlist As List(Of String) = New List(Of String)
serverlist.Add("Test")
serverlist.Add("Random")
serverlist.Add("Local")
GetInfo(serverlist)
End Sub
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Dim Col_Serial As DataGridTextColumn = New DataGridTextColumn()
Col_Serial.Binding = New Binding("Servername")
Col_Serial.Header = "Servername"
Col_Serial.Width = 40
Dim Col_IPArray = New DataGridComboBoxColumn()
Col_IPArray.Header = "IP Addresses"
Col_IPArray.IsReadOnly = True
Col_IPArray.ItemsSource = Me._ipAddresses
Col_IPArray.SelectedItemBinding = New Binding("IPArray")
DataGrid1.Columns.Add(Col_Serial)
DataGrid1.Columns.Add(Col_IPArray)
DataGrid1.ItemsSource = ServerInfoArray
End Sub
End Class
Class ServerInfo
Dim _Servername As String
Dim _IPArray As List(Of String)
Public Property Servername() As String
Get
Return _Servername
End Get
Set(ByVal value As String)
_Servername = value
End Set
End Property
Public Property IPArray As List(Of String)
Get
Return _IPArray
End Get
Set(ByVal value As List(Of String))
_IPArray = value
End Set
End Property
Public Sub New()
_Servername = Nothing
_IPArray = New List(Of String)
End Sub
End Class
Not sure if I am understanding perfectly but if you can live with AutoGenerateColumns = False then you can manipulate the columns, their properties and bindings from code like below. So you could use whatever logic you need to in order setup the columns from code. I tried to show how you can source the combobox column items from a different object than the items in the DataGrid as well.
This is meant to be a simple example so I just did everything in the code-behind but from an MVVM standpoint, it depends on your preferred approach but possibility is that you could accomplish the same logic from a controller class that spins up your view models, etc...
Hope it helps!
XAML...
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button x:Name="btnRefreshIPList">Refresh list of IPs</Button>
<DataGrid x:Name="dataGrid1"></DataGrid>
</StackPanel>
</Window>
Code behind...
Imports System.Collections.ObjectModel
Class MainWindow
'The list of IPs for column's ItemSource property
Private ReadOnly _ipAddresses As ObservableCollection(Of String)
'The items for binding to the DataGrid's ItemsSource
Private _items As List(Of MyObjectWithIPAddress)
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_ipAddresses = New ObservableCollection(Of String)
_items = New List(Of MyObjectWithIPAddress)
End Sub
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Me.dataGrid1.AutoGenerateColumns = False
dataGrid1.Columns.Clear()
'Example of text column (Text bound to Name property)
Dim dgTxtCol = New DataGridTextColumn()
dgTxtCol.Header = "Name"
dgTxtCol.Binding = New Binding("Name")
dataGrid1.Columns.Add(dgTxtCol)
'Example of combobox column (SelectedItem bound to IPAddress)
Dim dgCmbCol = New DataGridComboBoxColumn()
dgCmbCol.Header = "IP Address"
dgCmbCol.ItemsSource = Me._ipAddresses
dgCmbCol.SelectedItemBinding = New Binding("IPAddress")
dataGrid1.Columns.Add(dgCmbCol)
'Add items to DataGrid
_items.Add(New MyObjectWithIPAddress("foo1"))
_items.Add(New MyObjectWithIPAddress("foo2"))
Me.dataGrid1.ItemsSource = Me._items
End Sub
''' <summary>
''' To emulate fetching the object that has the IP list
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Private Function GetIpList() As MyObjectWithListOfIPs
Dim inst = New MyObjectWithListOfIPs
inst.IPList = New List(Of String)(New String() {"10.0.0.1", "10.0.0.2", "10.0.0.3"})
Return inst
End Function
''' <summary>
''' Updates the ObservableCollection instance based on business object
''' </summary>
''' <remarks></remarks>
Private Sub RefreshIpAddresses()
_ipAddresses.Clear()
For Each ip As String In GetIpList().IPList
_ipAddresses.Add(ip)
Next
End Sub
Private Sub btnRefreshIPList_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles btnRefreshIPList.Click
RefreshIpAddresses()
End Sub
End Class
''' <summary>
''' Object with response data (e.g., list of IPs)
''' </summary>
''' <remarks></remarks>
Class MyObjectWithListOfIPs
Private _ipList As List(Of String)
Public Property IPList() As List(Of String)
Get
Return _ipList
End Get
Set(ByVal value As List(Of String))
_ipList = value
End Set
End Property
End Class
''' <summary>
''' Disperate object that "has an" address
''' </summary>
''' <remarks></remarks>
Class MyObjectWithIPAddress
Public Sub New(name As String)
Me._name = name
End Sub
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Private _ipAddress As String
Public Property IPAddress() As String
Get
Return _ipAddress
End Get
Set(ByVal value As String)
_ipAddress = value
End Set
End Property
End Class

Resources