Dillema with newing an item in WPF MVVM - wpf

I have built a form that displays the properties of items contained in an observable collection. This observable collection is in a ViewModel to which the datacontext of the View is bound to.
When the New button on the form gets clicked, a new Item gets added to the observable collection, and with the help of the collectionview is set to the current item. The same form is used. This time, because of the new item, the fields are empty.
Now, I want there to be only one new item in the observable collection. Because of this, I need to somehow distinguish between an Item and a New Item.
So, before the New requirement I had the following Observable Collection in my ViewModel:
public ObservableCollection<ItemViewModel> ItemTypes { get; private set; }
But now I need to be able to distinguish between a New Item and an old item. I've been trying to do a couple of things like building a base class, of which I derive Item and New Item, but this seems really wastfull since I can't think of anything to put in the derived classes, they are really only there to help to see if there's one Empty Item in the observable.
What can I do?
Edit
private void CreateNewItemType()
{
if (DoesNewItemTypeAlreadyExist())
{
return;
}
Model.ItemType itemType = new Model.ItemType();
ItemTypeViewModel itemTypeViewModel = new ItemTypeViewModel(itemType)
{
IsNew = true
};
ItemTypes.Add(itemTypeViewModel);
itemTypesCollectionView.MoveCurrentTo(itemTypeViewModel);
}
private bool DoesNewItemTypeAlreadyExist()
{
return ItemTypes.Any(a => a.IsNew == true);
}

Create a bool property on your item (IsDirty for example). When new, IsDirty is False. The first time something is changed, set it to true. Then you can have a check on the command property for your Add button that creates the new item, to check and see if there are any IsDirty = False items in the collection. If so, then set that item to selected. If not, then add a new item and select it.
In the past what I have done to do change detection is create a dictionary of String, String. The dictionary contains the name of the property and the original value. In the Changing event handler for the property in my model class, I add an add or remove call that checks to see if an item for that property is there, if so, check current value with changing value. Call OnPropertyChanged("IsDirty") as appropriate.
Is dirty checks to see if there are any items in the dictionary. The beauty is that if you change the values back to their original values, the record will regain it's IsDirty=False status.
Example of implementation
Private Sub OnAddress1Changing(ByVal value As String)
If Not m_dirtyFields.ContainsKey("Address1") Then
AddDirtyField("Address1", Address1)
Else
If m_dirtyFields("Address1") = value Then RemoveDirtyField("Address1")
End If
End Sub
Public m_dirtyFields As New Dictionary(Of String, String)
Private Sub AddDirtyField(ByVal ColName As String, ByVal OrigValue As String)
If Not m_dirtyFields.ContainsKey(ColName) Then
m_dirtyFields.Add(ColName, OrigValue)
OnPropertyChanged("IsDirty")
End If
End Sub
Private Sub RemoveDirtyField(ByVal ColName As String)
If m_dirtyFields.ContainsKey(ColName) Then
m_dirtyFields.Remove(ColName)
End If
OnPropertyChanged("IsDirty")
End Sub
Public ReadOnly Property IsDirty() As Boolean
Get
If m_dirtyFields.Count > 0 Then 'There are items so record is dirty
Return True
Else
Return False
End If
End Get
End Property

Related

Manipulating TreeView With CheckBox

I have a TreeView with checkbox. I have seen a few examples online to show how to bind parent/child checkbox together using INotifyPropertyChanged.
However, what I am trying to achieve is something more than that.
Assuming I have 2 different object
Main Object
public class MainObject
{
public string Name;
public List<SubObject> SubObjects;
public bool? IsChecked;
//other parameters
}
Sub Object
public class SubObject
{
string Label;
public bool? IsChecked;
//other parameters
}
TreeView Display
- Main_Object_1(Name)
- Sub_Object_1(Label)
- Sub_Object_2(Label)
- Main_Object_2(Name)
- Sub_Object_3(Label)
I have 3 objectives:
1) Parent/Child checkbox association
Main Object and sub object will reflect the correct status depending on which checkbox is clicked.
If Main object checkbox is checked, all the sub object will be checked.
If one of the sub object is checked, main object will be null (fully filled with some color)
2) Different set association
At any one time one set (Main_Object_1 or Main_Object_2) can be checked.
Assuming only "Main_Object_2"and "Sub_Object_3" is checked.
When check on "Main_Object_1" or "Sub_Object_1" or "Sub_Object_2", "Main_Object_2" and "Sub_Object_3" is unchecked.
3) Performing other task beside updating the checkbox display
I will need to perform other task depending on which node is checked/unchecked.
Any pointer on how to get this done?
Thanks.
I managed to get out the output I wanted by adding a IsChecked property and parent property and Set property. After which I added some logic to manipulate the checkbox status. For objective 3, I simply add a checked event handler to perform the task I needed.

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.

Cascading Comboboxes WPF mvvm and EF

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.

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

Correctly bind object instance property to a Label in code

This is supposed to be easy, yet I can't get it to work.
This is a simplified example so I can illustrate my problem. I have a List(Of Object) and I want to bind the Content of a Label to some property of an object in the list. I want to do this in code (Because those labels will be generated in runtime).
I create my object that will hold the value for the label and the list, that will hold those objects:
' The List to hold objects
Public Class BList
Public Shared listy As New List(Of BindTest)
End Class
' Object to hold label text
Public Class BindTest
Public Property Namy As String
End Class
Then I try to create the object and add it to the list. And try to set the binding for the label (for sake of simplicity, lets say I want bind to the first list item).
Dim bb As New BindTest
bb.Namy = "FirstName"
BList.listy.Add(bb)
B_label.SetBinding(Label.ContentProperty, "Namy")
B_label.DataContext = BList.listy.Item(0)
So far it works fine and label shows "FirstName" as expected. But then if I try to change the value of that first item like this:
BList.listy.Item(0).Namy = "Something else"
nothing happens and the label is not updated.
Thanks to Mike Eason.
I needed to implement INotifyPropertyChanged. I somehow thought, that it would be implemented automatically with all that WPF default-way of databinding everything. Oh well, one needs implement this manually.
So for the code to work, this part:
' Object to hold label text
Public Class BindTest
Public Property Namy As String
End Class
..must be changed to this:
' Object to hold label text
Public Class BindTest
Implements INotifyPropertyChanged
Private _Namy As String
Public Property Namy
Set(value)
_Namy = value
_PropertyChanged("Namy")
End Set
Get
Return _Namy
End Get
End Property
Private Sub _PropertyChanged(Optional ByVal PropertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
End Sub
Private Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
End Class

Resources