Cascading Comboboxes WPF mvvm and EF - wpf

I'm trying to create cascading comboboxes on my view. I have a table, imported from sql with ADO.NET Entity Data Model:
Partial Public Class tbl_Activities
Public Property MRU As String
Public Property Market As String
Public Property Tower As String
Public Property FirstActivity As String
Public Property SecondActivity As String
First combobox should contain FirstActivity items and the second one SecondActivity items related to the first one.
I'm using method to populate the first one:
Public Sub FillFirstActivity()
Using context As New TimerConn
Dim result = (From act In context.tbl_Activities Where (act.MRU = MRU() And act.Market = MAR() And act.Tower = TOW()) Order By act.FirstActivity).Distinct().ToList()
For Each act In result
_First.Add(act)
Next
End Using
End Sub
Properties:
Public Property First() As ObservableCollection(Of tbl_Activities)
Get
Return _First
End Get
Set(value As ObservableCollection(Of tbl_Activities))
_First = value
End Set
End Property
Public Property Second() As ObservableCollection(Of tbl_Activities)
Get
Return _Second
End Get
Set(value As ObservableCollection(Of tbl_Activities))
_Second = value
End Set
End Property
Public Property SelectedFirst() As tbl_Activities
Get
Return _selectedFirst
End Get
Set(value As tbl_Activities)
_selectedFirst = value
Me.Second.Clear()
Me.Second.Add(_selectedFirst)
End Set
End Property
XAML:
<ComboBox ItemsSource="{Binding EmployeeViewM.First}" SelectedIndex="{Binding EmployeeViewM.SelectedFirst}" DisplayMemberPath="FirstActivity"/>
<ComboBox ItemsSource="{Binding EmployeeViewM.Second}" DisplayMemberPath="SecondActivity"/>
My problem
I'm not able to populate it correctly. In the first combobox I have doubles (Test1, Test2, Test2), and in the second combobox I get only item related to the item chosen from the first combobox:
First one should be distinct and the second one with all items for the current activity.
First Combobox = 'Test1', 'Test2'
Second Combobox = If 'Test1' then 'Test1Add', If 'Test2' then 'Test2Add', 'Test2Add1'
I don't know what I'm doing wrong here. Maybe I should split it into Two tables with PK - FK and then try? If so, how can I bind it to the comboboxes?
My Table view:
Thank you very much for any suggestion.

Related

ObservableCollection returning wrong type

For some reason I'm adding each item in the findall function to the observable collection, and I am getting back "groupname" in my listbox.. instead of the actual name of the group.
Dim ctx As New PrincipalContext(ContextType.Domain)
Dim qbeGroup As New GroupPrincipal(ctx)
Dim srch As New PrincipalSearcher(qbeGroup)
For Each item In srch.FindAll()
GroupsList.Add(New groups With { _
.name = item.Name
})
Next
Below is my simple observablecollection
Public Property name() As String
Get
Return m_name
End Get
Set(value As String)
m_name = value
End Set
End Property
Private m_name As String
Is there something else I need to be doing to return the actual string?
Edit: Xaml
<ListBox x:Name="lbAvGroups_Copy" HorizontalAlignment="Left" Height="470" VerticalAlignment="Top" Width="355" Margin="392,35,0,0"/>
I do the binding in code-behind, so this is what my binding looks like.
Public View As ICollectionView
Public GroupsList As new ObservableCollection(Of groups)
View = CollectionViewSource.GetDefaultView(GroupsList)
lbAvGroups.ItemsSource = View
When you bind a ListBox to a list of objects it will, by default, display the value returned by calling ToString on the objects. You can either define the ItemTemplate for the ListBox, or for simple text display you can set the DisplayMemberPath to the name of the property you want to show. So, in your case you can add
lbAvGroups.DisplayMemberPath = "name"
when binding the list box to display the name property of each item.

Collection was modified when trying to remove selected items of datagrid

by following this answer I coded almost same way. Below is part of my code.
XAML binding:
<cst:CustomDataGrid x:Name="grdItems" ItemsSource="{Binding myItems, IsAsync=True}" SelectedItemsList="{Binding SelectedItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ViewModel:
Protected Overrides Sub DeleteCurrent()
Dim msg As New UserMessage()
If SelectedItems IsNot Nothing Then
For Each SelectedItem In SelectedItems
myItems.Remove(SelectedItem)
Next
RaisePropertyChanged("Items")
End If
End Sub
Private m_myItems As ObservableCollection(Of item)
Public Property myItems() As ObservableCollection(Of item)
Get
Return m_myItems
End Get
Set(value As ObservableCollection(Of item))
m_myItems = value
End Set
End Property
Private m_SelectedItem As IList = New List(Of item)()
Public Property SelectedItems() As IList
Get
Return m_SelectedItem
End Get
Set(value As IList)
m_SelectedItem = value
' RaisePropertyChanged("SelectedItems")
End Set
End Property
Selection works perfectly fine. But when I am looping to remove selected each item, I am getting exception that "Collection was modified. Enumeration operation may not execute.". For example, I select 3 rows and press delete keyboard button, when I observe my SelectedItems object, count is 3 as expected but right after executing the line with myItems.Remove(SelectedItem), count goes down to 1. why is that happening, i couldnt figure out because I am not modifing SelectedItems but myItems.
PS; customdatagrid is exactly same code as the original post above. thats why i didnt add it here.
" i couldnt figure out because I am not modifing SelectedItems but myItems"
You're removing item from myItems which used as ItemsSource of the data grid. Removing item from ItemsSource automatically followed by removing it from SelectedItems, and that behavior makes perfect sense (you can't keep an item selected when it is no longer in the data grid).
That's why the error. You can't let the collection changed while enumerating through it.
One possible way to accomplish this is by copying selected items to temporary collection, to a temporary array, for example. Then enumerate the array instead of the original collection :
Dim copyOfSelectedItems(SelectedItems.Count-1) As Object
SelectedItems.CopyTo(copyOfSelectedItems,0)
For Each SelectedItem In copyOfSelectedItems
myItems.Remove(SelectedItem)
Next
just copy selecteditems to new collection when doing foreach loop, e.g using ToArray linq extension method:
For Each SelectedItem In SelectedItems.ToArray()
myItems.Remove(SelectedItem)
Next

Adding Items to a DataGrid (MVVM)

Goal
To add a list of a custom class object (DamagedItems) to a DataGrid using the Model, View, ViewModel (MVVM) way of doing things.
I want the user to be able to create entries of damaged parts (deemed improper during inspection of a machine).
What I have done
I have created:
A window: wDamagedItems.xaml in which it's DataContext is set to DamagedItemViewModel
A Model: DamagedItemModel.vb which implements INotifyPropertyChanged
A ViewModel: DamagedItemViewModel.vb where I set properties of classes such as my DamagedItemModel
An ObservableCollection: DamagedItemList.vb which inherits an ObservableCollection(Of DamagedItemModel)
Since my DataContext is set to the DamagedItemViewModel, here is how I setup the properties:
Public Class DamagedItemViewModel
Private _DamagedItem As DamagedItemModel
Private _Add As ICommand
Private _DamagedItems As DamagedItemList
Public Property DamagedItem As DamagedItemModel
Get
Return _DamagedItem
End Get
Set(value As DamagedItemModel)
_DamagedItem = value
End Set
End Property
Public Property DamagedItems As DamagedItemList
Get
Return _DamagedItems
End Get
Set(value As DamagedItemList)
_DamagedItems = value
End Set
End Property
Public Property Add As ICommand
Get
Return _Add
End Get
Set(value As ICommand)
_Add = value
End Set
End Property
Public Sub New()
DamagedItem = New DamagedItemModel("", "", "")
DamagedItems = New DamagedItemList
Add = New DamagedItemAddEntryCommand(Me)
End Sub
Public Function CanUpdate() As Boolean
If DamagedItem.Description = "" Then Return False
If DamagedItem.Initiales = "" Then Return False
Return True
End Function
Public Sub AddEntry()
DamagedItems.Add(DamagedItem) 'Items get added to the datagrid
DamagedItem = New DamagedItemModel 'Does not seem to clear textboxes
End Sub
End Class
Here is how my XAML is set up:
<DataGrid ItemsSource="{Binding Path=DamagedItems}" AutoGenerateColumns="True" HorizontalAlignment="Stretch" Margin="12,90,12,0" Name="DataGrid1" VerticalAlignment="Top" Height="229" / >
<TextBox Text="{Binding DamagedItem.Description, UpdateSourceTrigger=PropertyChanged}" Height="23" HorizontalAlignment="Left" Margin="88,24,0,0" VerticalAlignment="Top" Width="249" />
<TextBox Text="{Binding DamagedItem.Initiales, UpdateSourceTrigger=PropertyChanged}" Height="23" HorizontalAlignment="Left" Margin="88,58,0,0" VerticalAlignment="Top" Width="249" />
As you can see, my textboxes are bound to my Model (which is contained in my ViewModel, which is bound to that Window's DataContext). Whenever I click on my "Add" button, whatever is in the textbox gets added to the DataGrid, but the content in the text boxes stay there.
This step is fine, I write in what I want to add and click on "Add"
After clicking on "Add" i get the following results in the DataGrid, which is fine. The issue is my text boxes are still filled with data yet the Model was cleared (see code after DamagedItemViewModel AddEntry method).
Now when I try to add the following text:
Description: "Part is bent"
Initiales: "A.C"
I get the following result:
The first letter typed in the description gets inputted in the first entry of the DataGrid, then it erases the text in the description textbox. Only then can I keep typing what I want. The same thing occurs for the initiales text box.
Any ideas? If you wish to see more of my code, suggest which portion I should add.
Thank you in advance!
Yup, I remember running into this one. You have to implement iNotifyPropertyCHnaged. This is how the viewmodel class "notifies" the user interface that there has been a change to the underlying property of a binding:
look here:
http://msdn.microsoft.com/en-us/library/ms743695.aspx
You will have to implement this for every property you want reflected back to the view. SO what I do is have a base viewmodel class (ViewModelBase which exposes method RasiePropertyChanged) which implements iNotifyPropertyChanged and then my viewmodles inherit from it. Then I notify the property changed in the property set of the property:
ie:
Public Property Selection As job
Get
Return Me._Selection
End Get
Set(ByVal value As job)
If _Selection Is value Then
Return
End If
_PreviousJob = _Selection
_Selection = value
RaisePropertyChanged(SelectionPropertyName)
End Set
End Property
This seems frustrating at first but is needed to keep the decoupling that MVVM supports. Its easy to implement.

ComboBox does not reflect its ItemsCount

I am trying to build a small application with a combobox and the issue is that the items are not getting updated in it properly, sometimes 2 items sometime 4 items are only getting visible however the items count is getting updated properly. Following is the xaml code:
<ComboBox Name="NumbersCombo" Width="118">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Following is the code for itemssource on page load, where con.Numbers is dictionary of string and Numbers class, hence Values are sent for attachment:
NumbersCombo.ItemsSource = con.Numbers.Values
Following is the code for adding new items to combobox:
Dim temp As New BSPLib.ContactLib.ContactCon(con.prime.Conid, False)
con.Numbers.Add(temp.ConRowID, temp)
NumbersCombo.ItemsSource = con.Numbers.Values
TestLabel1.Content = NumbersCombo.Items.Count
Following is the code for the class:
Public Class ContactCon
Property ConId As String
Property ConRowID As String
Property Title As String
Property Mob1 As String
Property Mob2 As String
Property Land1 As String
Property Land2 As String
Property Email1 As String
Property Email2 As String
Property Fax1 As String
Property Fax2 As String
Property Primary As Boolean
Public Sub New()
End Sub
Public Sub New(ByVal contactID As String, ByVal primarynumbers As Boolean)
ConId = contactID
ConRowID = contactID & "-" & Now.ToString
If primarynumbers = True Then
Title = "Primary Details"
Else
Title = "Additional Contact Numbers"
End If
Mob1 = ""
Mob2 = ""
Land1 = ""
Land2 = ""
Email1 = ""
Email2 = ""
Fax1 = ""
Fax2 = ""
Primary = primarynumbers
End Sub
End Class
Public Class Contact
Public prime As ContactPrime
Public addrs As Dictionary(Of String, ContactAddress)
Public Numbers As Dictionary(Of String, ContactCon)
Public Sub New()
Numbers = New Dictionary(Of String, ContactCon)
'assigning initial ids and values
Dim t As New ContactCon(prime.Conid, vbYes)
Numbers.Add(t.ConRowID, t) 'Primary Contact Number
End Sub
In simple, the gui of combo box is not showing the items but the items count is correct, could you please tell where I have gone wrong. Thank you.
If you want the combo box to be updated automatically when a particular ContactCon object or the collection of ContactCon objects changes then the ContactCon class needs to implement INotifyPropertyChanged and you need to set the ItemSource to a collection class that implements INotifyCollectionChanged (This means that you only have to set the ItemSource once). You can find out how to implement an observable dictionary in the answers section of the following question.
Making these changes will also fix the synchronisation problems you are seeing.
I agree with Dave +1
If you need dictionary for lookup speed or uniqueness on key then you need a dictionary that implements CollectionChanged.
The other option is to just use ObservableCollections direclty and use LINQ for lookup. If you have even 10000 LINQ is still pretty fast for lookup. Since you are not using virtualization on the combobox I suspect the list is not large.

WPF: "None" option when Databinding with DataGridComboBoxColumn

This is what I want:
There is a combo-box column bound to the ApplicationKey property of ClassA.
ClassA.ApplicationKey is a Nullable<Int32>
The combo-box is populated with ApplicationTokens from a static function all.
An ApplicationToken has a ApplicationName and ApplicationKey property
When an item is selected in the drop-down, the ClassA.ApplicationKey property is set to the ApplicationToken.ApplicationKey on the selected item.
The "None" option is currently represented by a Null. This can be changed.
Current code
<DataGridComboBoxColumn
Header="Application"
SelectedValueBinding="{Binding ApplicationKey}"
SelectedValuePath="ApplicationKey"
DisplayMemberPath="ApplicationName"
ItemsSource="{Binding Source={x:Static app:ApplicationLookup.GetAllOrNone}}"/>
Currently the binding works, except that I cannot select the "None" item from the list. The combobox shows it, but doesn't do anything when I try to select it with the mouse.
What is the standard way to offer none in a bound combo-box?
I don't know if this is the standard way of doing things but it seems to work:
All ApplicationTokens inherit from Token
Token has a "PrimaryKey" property.
There is a NullToken class defined as such:
Public Class NullToken
Inherits Token
Private ReadOnly m_DisplayValue As String
Private Sub New(ByVal displayValue As String)
m_DisplayValue = displayValue
End Sub
Public Overrides Function ToString() As String
Return m_DisplayValue
End Function
Public Overrides ReadOnly Property PrimaryKey As Integer?
Get
Return Nothing
End Get
End Property
Public Shared ReadOnly BlankToken As New NullToken("")
Public Shared ReadOnly NoneToken As New NullToken("None")
Public Shared ReadOnly AllToken As New NullToken("All")
End Class
ApplicationLookup.GetAllOrNone returns a collection of Token with the correct NullToken as the first item.
What I have done when I needed a None or (Select All) type of user gesture for a comboBox is create some static value for the token and just bind to a collection that includes the token in the first position. Then account for it in whatever is handling the change in value:
public string MidfixText {
get { return _midfixText; }
set {
...
_filter(!_midfixText.Equals(Strings.ProjectSelection_MidfixChoice_SelectAll));
}
}
HTH,
Berryl

Resources