Entity Framework - Sorting Relationproperties - wpf

my question is, how do I sort related items of an entity.
In an invoice editing window, I have one ComboBox displaying all customers:
XAML:
<UserControl.Resources>
<CollectionViewSource x:Key="cvsCustomers"
d:DesignSource="{d:DesignInstance local:Customer, CreateList=True}" />
...
</UserControl.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource cvsCustomers}}" .../>
Code-behind:
Dim cvsCustomers As System.Windows.Data.CollectionViewSource
cvsCustomers = CType(Me.FindResource("cvsCustomers"), System.Windows.Data.CollectionViewSource)
Dim qryCustomers = _
From c In myEntities.Customers _
Order By c.CustomerCode
Select c
cvsCustomers.Source = qryCustomers
Now I have a 2nd ComboBox displaying all contact persons of the selected customer. This works fine, but the entries in this second ComboBox are unsorted / sorted by ID.
XAML:
<ComboBox ItemsSource="{Binding Path=myInvoice.Customer.Contacts}" .../>
;
How do I get the list on the 2nd ComboBox sorted?
King regards,
and thanks in advance for tips/suggestions,
Nico

I think there are two possible things.
When loading the data: It sounds like you are using the lazyloading feature of EF to load the Contacts related to the Customer. To sort Contacts, you would need to set what is sort option, were it's and when it's, but with the lazyloading these cannot be set. So you can use the Eagerly loading or the Explicitly loading to get the Contacts ordered. Please check to Using DbContext in EF 4.1 Part 6: Loading Related Entities, based on EF4.1. Something like this, the Explicitly loading case:
//Oh, I'm not familiar with VB.NET, so it's C# code
var customer; // it's assumed to be initialized.
context.Entry(customer).Collection(c => w.Contacts)
.Query().Orderby(c =>c.Name).Load();
Through CollectionView after loading the data unordered: you can let the CollectionView order the Contacts. Please check to CollectionViewSource Class(MSDN).

Related

Repopulate Listbox Using a Collectionviewsource

My first question! I'm moderately familiar with vb but am a n00b with wpf.
I have a form with a listbox and a button. I populate the listbox from a dataset connected to an Access database. The table I am working with is named Entries and has just two columns, EntryID and Entry. When I click on the button, a new entry is added to the database but the listbox is not repopulated.
I have read every answer for this search: "listbox collectionviewsource repopulate" but none seem to address my specific problem.
I set up my Resources as follows:
<Window.Resources>
<CollectionViewSource x:Key="cvsEntries"
Source="{Binding Source={StaticResource TestDataSet}, Path=Entries}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Entry" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
I set up the listbox as follows:
<ListBox Name="lstEntries"
DataContext="{StaticResource cvsStandardEntries}"
ItemsSource="{Binding}"
DisplayMemberPath="Entry" />
My code behind for the click event is:
Private Sub btnAdd_Click(sender As Object, e As RoutedEventArgs) Handles btnAdd.Click
Dim taEntries As TestDataSetTableAdapters.EntriesTableAdapter = New TestDataSetTableAdapters.EntriesTableAdapter
taEntries.Insert(NewEntryID, NewEntry)
The database is properly updated but the listbox is not. Based on my understanding of binding, it should be. Am I doing something wrong in my overall approach or have I missed something in the listbox and/or button code.
I know I can add this code to the button:
With taEntries
.ClearBeforeFill = True
.FillByCategory(Entry, EntryID)
End With
I can force the listbox to update but that seems to violate my understanding of Binding and a bit of brute force.
Thanks in advance.
First, Access? sad face...
Second, your DataContext use there is a bit odd. Try this:
<ListBox Name="lstEntries"
ItemsSource="{Binding Source={StaticResource cvsStandardEntries}}"
DisplayMemberPath="Entry" />
Third, TestDataSet isn't observable, so changes made to your database won't be automatically populated back to WPF. Ideally, you should abstract your data layer away -in this case, since you're displaying lists of items in the UI that might change on the fly, you should be using something like ObservableCollection.
That said, if you don't want to go to that effort, you could refresh your collection view manually:
Dim collectionView As ICollectionView = TryCast(lstEntries.ItemsSource, ICollectionView)
collectionView.Refresh()

ListBox Binding with Global Index

My application has a couple of ObservableCollections, one which is nested within an element of the other. Each contain a number of fields e.g.
ObservableCollectionA (called Listings)
Title
Description
Address
Images As MediaItems
ObservableCollectionB (called MediaItems)
ImageName
Src
DisplayTime
Currently I have been accessing ObservableCollections as follows:
Listings(0).MediaItems(0).ImageName
My application has the main Window display the items from Listings and a UserControl which contains a ListBox which displays the items from MediaItems.
Currently my Window is bound to Listings using code in the New method:
Dim AppLocal As Program = Application.CurrentItem
AppLocal.CurrentItem = 0
Me.DataContext = Listings.Item(AppLocal.CurrentItem)
For the Listings ObservableCollection, the UserControl has a XAML DataContext which references a local method which pulls the records from the nested MediaItems ObservableCollection.
<UserControl.DataContext>
<ObjectDataProvider ObjectType="{x:Type local:ThumbImageLoader}" MethodName="LoadImagesv2" IsAsynchronous="True" />
</UserControl.DataContext>
<Grid x:Name="ThumbListBoxGrid">
<ListBox x:Name="ThumbListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
</Grid>
The method is here:
Public NotInheritable Class ThumbImageLoader
Public Shared Function LoadImagesv2() As List(Of MediaItems)
Dim AppLocal As Program = Application.Current
Dim ThumbImages As New List(Of MediaItems)
ThumbImages = Listings(AppLocal.CurrentItem).MediaItems
Return ThumbImages
End Function
End Class
Whilst developing the UI layout I have just been binding the first item (0 index). I now want to be able to set AppLocal.CurrentItem from anywhere in the application so the Window and the ListBox are updated.
Ideally I would like it so when the global property index changes, the UI is updated.
How do I do this?
Is there a better way to go about it?
Ben
Ok, I discovered the joy of CollectionView. Offered exactly what I was after and was excrutiatingly easy to implement. I was blown away at not only how easy it was to implement, but I managed to cut out more lines of code than I used to implement it.
I implemented a public CollectionViewSource
Public ListingDataView As CollectionViewSource
In my main Window, I implemeted it as follows:
<CollectionViewSource x:Key="ListingDataView" />
and bound my top-level Grid to it:
<Grid DataContext="{Binding Source={StaticResource ListingDataView}}">
In my Application Startup I set the CollectionView Source
AppLocal.ListingDataView = CType(Application.Current.MainWindow.Resources("ListingDataView"), CollectionViewSource)
AppLocal.ListingDataView.Source = Listings
The next part which impressed me the most was implementing it for my User Control. I remembered the UserControl is inheriting from the main window so it has access to the CollectionView already, so I ditched the separate Class and Method binding in favour for this:
<ListBox ItemsSource="{Binding Path=MediaItems}" VerticalAlignment="Top" IsSynchronizedWithCurrentItem="True" />
Now whene I want to set the Current List Index, I simply call this:
AppLocal.ListingDataView.View.MoveCurrentToPosition(AppLocal.CurrentProperty)
A few milliseconds later, the UI updates automatically.
Done!!
When you want multiple source data (like your observable collection properties and the index for the observable collection) to a single target you should use MultiBinding.
So in your case somethign like this should help...
<ListBox x:Name="ThumbListBox" IsSynchronizedWithCurrentItem="True" >
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource CollectionAndIndexCollaborator}">
<Binding Path="Listings" />
<Binding Path="Application.CurrentItem.CurrentItem" />
</MultiBinding>
</ListBox.ItemsSource>
</ListBox>
provided that ....
Your data context is some class that holds the Application object via a property of same name Application and the Listings collection via property of same name Listings.
Your DataContext class and Application class must have INotifyPropertyChanged implemented. It should also raise notifications for Application and Setter of CurrentItem and CurrentItem.CurrentItem properties.
CollectionAndIndexCollaborator.Convert() method returns the same indexed value as the final collection....
return ((ObservableCollection)values[0]).Item(int.Parse(values[1])) ;
where assuming MyListingType is the T of your Listings collection.
This way when anyone changes the global Application.CurrentItem.CurrentItem the multi binding above will get notified and will select the required Listings item.

How do I bind a lookup combobox?

My WPF4 combobox dropdown list is incorrectly displaying the class name of my EF4 entity. Here is the relevant XAML:
<Window.Resources>
<CollectionViewSource x:Key="myEntitiesViewSource"/>
</Window.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource myEntitiesViewSource}}" DisplayMemberPath="CategoryDescription" SelectedValuePath="CategoryID" />
Here is the code in my Window_Loaded event:
var categoryList = from p in _context.Categories
orderby p.CategoryNumber
select p;
System.Windows.Data.CollectionViewSource myEntitiesViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("myEntitiesViewSource")));
// Load data by setting the CollectionViewSource.Source property:
myEntitiesViewSource.Source = categoryList;
My database has a many to many relationship between Projects and Categories with a join table called ProjectCategories. The Categories entity was automagically created as a single entity to represent two database tables:
1) the lookup table of Categories containing an ID, CategoryDescription and CategoryNumber and
2) the join table ProjectCategories containing only two fields - the IDs from the tables Projects and Categories. The entity model lives in a separate project from my WPF window.
My goal is to allow the user to select a CategoryDescription from the dropdown list, then click an Add Category button to add the selected Category to a separate list of ProjectCategories. With the current code I see the correct CategoryDescription in the combobox text area but the dropdown list displays only the entity class name Categories (preceded by it's namespace) multiple times!
How do I make this simple lookup combobox bind correctly and display a list of CategoryDescriptions and a SelectedValue of CategoryID? Note: I'd accept a code only approach leaving out the CollectionViewSource in XAML if it's possible.
Thanks!
Nevermind. I asked this question and have answered it myself. There was nothing wrong with my code or XAML. The problem was caused by the use of a third party theme to style my controls. Once I removed the theme the combobox binding problem went away. For more details see this post.
What about something like this?
<ComboBox ItemsSource="{Binding Categories}"
SelectedItem="{Binding Category}" DisplayMemberPath="Description" />
Instead of using a Selected Value, I would store the whole object. The selected value approach is old ASP style for my taste.
SelectedItem="{Binding Category}" is your Category object. Basically it has stored the selected item of the ComboBox.
When the user clicks a button for example, you can fire a Command from the ViewModel and you will have the corresponding selected Category object.

ADO.NET Entity Framework: Problem with updates on related objects

I’m playing around with a little VB.NET app with WPf/MVVM and ADO.NET EF on a SQL Express-DB and I’m running into problems while trying to update related objects:
My DB has three tables “tb_Actors”, “tb_Movies” and a junction table “tb_movies_actors”. The EF designer creates two entities "Actors" und "Movies" and sets their navigation properties based on the foreign keys correctly. So was able to come up with a view which binds to a viewModels property which has all “Movies.Actors”.
The DataGrid in my view displays all actors correctly, and – if it’s a new actor which isn’t in my DB already- I am able to add new actors to a movie and persist the changes to the DB correctly.
However, if I want to add an actor to a movie who is already in my DB, I’ll get a double entry in my tb_actors table. First I’ve set the primary key fields (name and id) to UNIQUE, but then my code breaks. Then, I’ve added a little update routine which checks for each related actor of a movie if it’s a known actor, and changes the “new actors” id to the “old actors” id – this also breaks.
Is there a way to tell EF that it has to determine if an added related object (= already known actor added to movie) is already in the DB, and it therefore has to insert only a new entry to to junction table, but not to the related objects table?
My next step would be detaching the related objects and do all updates/inserts in my own data access code … but since I believe my problem is around a typical EF use case, there must be a more elegant way to deal with updates on related objects.
Any thoughts, answers, hints are highly appreciated!
* EDIT Here are the relevant code snippets *
1) I have the following LoadMovies Function in my MovieRepository data access class:
Private Function LoadMovies() As List(Of Movies)
movs = From m In dc.Movies.Include("Actors") Select m
Return movs.ToList
End Function
2) The following property of my viewModel exposes the actors related to a specific movie:
Public ReadOnly Property actors() As ICollectionView
Get
If evs Is Nothing Then
evs = New CollectionViewSource
evs.Source = _movie.Actors
End If
Return evs.View
End Get
End Property
3) In my MovieDetail view, I've a datagrid binding to the property:
<DataGrid Name="ActTestGrid" HorizontalAlignment="Left" VerticalAlignment="Stretch" ItemsSource="{Binding actors}" AutoGenerateColumns="False" Width="150" Height="120" Style="{StaticResource dgTemplate}" RowStyle="{StaticResource dgRowTemplate}" CellStyle="{StaticResource dgCellTemplate}" CanUserSortColumns="True" CanUserAddRows="True" CanUserDeleteRows="True" HeadersVisibility="None">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=name, UpdateSourceTrigger=PropertyChanged}" CanUserSort="true"/>
</DataGrid.Columns>
</DataGrid>
4) This is my updateMovie Function of my MovieRepository (as by now):
Public Sub UpdateMovie(ByVal movie As Movies)
If movie Is Nothing Then
Throw New ArgumentNullException("Movie")
Else
dc.SaveChanges()
End If
End Sub
EF does what it does and you can't tell it to do anymore, that is it won't validate your data. EF will insert what you tell it to insert (or try to). Data validation is your responsibility before you call save changes.
In order to get around this problem consider providing a combobox in the view with the list of actors names. The combobox has IsEditable set to true and the text property is bound to ActorName AS String in your ViewModel. If the user selects an existing actor EF will not try to insert a new one. If the user types in a new name EF will create a new actor.
Here is some code using a Brand Name:
Public Property BrandName() As String
Get
Return _brandName
End Get
Set
_brandName = value.Trim()
If _brandName <> String.Empty Then
Dim b As Brand = _brands.ToList().Find(Function(br) br.BrandName.ToUpper() = _brandName.ToUpper())
If b Is Nothing Then
Brand = New Brand()
Brand.BrandName = _brandName
Else
Brand = b
End If
Else
Brand = Nothing
End If
CheckIsDirty()
RaisePropertyChanged("BrandName")
End Set
End Property
And on the view:
<ComboBox Grid.Row="1" Grid.Column="0" Height="28" HorizontalAlignment="Stretch" Margin="110,0,28,0" VerticalAlignment="Top" TabIndex="1" ItemsSource="{Binding Brands}" DisplayMemberPath="BrandName" Text="{Binding BrandName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEditable="True"/>

Silverlight: Datagrid like grouping in ItemsControl

Is it possible to group items in a ItemsControl or Listbox in Silverlight? These controls are bound to a DomainDataSource.
Or are there any 3rd party controls that do this?
UPDATE:
This is the sort of UI I am trying to create.
You can do this by using nested ItemsControls bound to a PagedCollectionView.
Say I have a datasource - MyItems - with fields: Category, Section and Option. I can create a PagedCollectionView from an IEnumerable(of MyItems) and tell it which fields to group by.
Dim original As IEnumerable(Of MyItems) = GetMyItems()
Dim pcv = New PagedCollectionView(original)
pcv.GroupDescriptions.Add(New PropertyGroupDescription("Category"))
pcv.GroupDescriptions.Add(New PropertyGroupDescription("Section"))
Then I bind my first ItemsControl to the PagedCollectionView
hisMyItems.ItemsSource = pcv.Groups
The PCV creates a nested hierachy like:
-Name
-Items
where Name is the value in the grouped field and Items contains the rows/objects in that grouping. I guess you could also create the PCV in xaml if you prefer.
The xaml would look something like:
<controls:HeaderedItemsControl x:Name="hisMyItems" Header="{Binding Name}" ItemsSource="{Binding Items}" >
<controls:HeaderedItemsControl.ItemTemplate>
<DataTemplate>
<controls:HeaderedItemsControl Header="{Binding Name}" ItemsSource="{Binding Items}" ItemsPanel="{StaticResource ItemsPanelTemplate1}" >
<controls:HeaderedItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Option}" />
</DataTemplate>
</controls:HeaderedItemsControl.ItemTemplate>
</controls:HeaderedItemsControl>
</DataTemplate>
</controls:HeaderedItemsControl.ItemTemplate>
</controls:HeaderedItemsControl>
I hope that makes sense. I have tried to simplify things from my actual app but I could have made some mistakes in copying it over. Obviously you could use normal ItemsControls or other controls too and customize with templates etc.
The DataGrid control supports grouping.
Tim Heuer has a good blog on grouping with a datagrid.
link text
Perhaps the control you are really looking for is the Accordian Control from the Toolkit.
See sample of Accordian behaviour here.
Note that the actual appearance is as style-able as any other control. The basic function is to group categories of items that would otherwise be a straight-forward List.

Resources