Good evening,
I am looking for someone to help me with my understanding of how to incorporate the Silverlight DataForm with my ViewModel that using WCF Ria Services. What I am trying to accomplish is using the DataForm manage my collection of entity and utilize its built in navigation, Add, Edit and Delete functionality. Howerver, I am having trouble tying it all together with my ViewModel and Ria Services.
From my understanding, the DataForm needs to be bound to an ObservableCollection<T>. However when I query from the WCF Ria Service context. ie.
_context.Load(_context.GetAllCustomersQuery(), loadCustomersQueryCallback, true);
I will receive back an IQueryable in the callback method, which i would have to cast as an ObservableCollection<T> like so?
Customers = new ObservableCollection<Customer>(_context.Customers);
Customers is a property in my ViewModel like so...
public ObservableCollection<Customer> Customers
{
get { return _customers; }
set
{
if (_customers != value)
{
_customers = value;
OnPropertyChanged("Customers");
}
}
}
The DataForm is bound to the Customers Property of the ViewModel and I am able to see the data from my datasource, I can navigate between entities, I can edit an existing entity and persist the changes back to the database however I cannot add or delete entites.
Is what I'm doing with the Customers property correct? have I "disconnected" from the context by casting into a new ObservableCollection and therefore have not actually added or removed the entities from the context?
Any help is greatly appreciated.
Regards,
I think you hit on the issue in your last question. When you create a new ObservableCollection, you've disconnected from collection change tracking (adds and deletes). Instead of using OC, there are a number of other options worth considering. In your case, it looks like EntitySet or EntityList may be the best options. For a full rundown of these types, take a look at my post on collection binding in RIA Services SP1.
You could use :
Customers.Clear(); // Or dispose every customer if it is Disposable
Customers = new ObservableCollection(result.ToList());
Related
I have a list of items representing packages in an MVVM control.
When you hover over the tooltip it needs to go to the database for additional information, lets just call it 'PackageDetails' for simplicity. I know how to handle the database loading with a ViewModel class but I'm having trouble figuring out when to instantiate it.
Approach 1) Have a 'lazy-load' property in the 'Package' object so when the tooltip is triggered the viewmodel will be created and immediately access the database.
This approach isn't ideal because each 'Package' object isn't a true viewmodel and came from WCF objects originally.
Approach 2) Use a converter as explained in this Josh Smith blog entry. His example seems to fit a converter well, but I don't think it really suits my situation well.
Approach 3) Somehow create a viewmodel in the XAML, but this seems like a bad idea.
What's a good approach to dynamically generate a viewmodel for a tooltip using MVVM
?
Binding models ( in your case the packages ) to the view only works for very simple situations where there is no more "processing" or business logic to implement.
I have experimented with a few options and in the ended up creating a VM wrapper for just about all my models. Going down this path makes having a tooltip property straight forward.
The other option that i have experimented with is to use partial classes to extend the wcf models. This works unless you are using dataannotations for validation ( wcf and dataannotations dont work together properly )
if you decide to wrap your models with a vm, then instantiating your list of VM wrappers is just one line of code using linq and lambdas
assuming you have a constructor on your VM that accepts your model as a parameter.
var listPackageVMs = new ObservableCollection<PackageVM> ( listPackageModels.Select(model=> new PackageVM(model)));
You could create a partial class to Package. I would avoid placing data access logic in an entity class, but this is the cheap and easy way.
namespace WCFServiceNamespace
{
// Since WCF generated entities are partial classes, we can inject features
public partial class Package
{
private readonly IDataAccessor _DataAccessor;
public Package()
: this(DataAccessor.Instance) // how you choose to inject a data accessor is up to you
{
}
public Package(IDataAccessor dataAccessor)
{
_DataAccessor = dataAccessor;
_ToolTip = new Lazy<string>(GetToolTip);
}
private readonly Lazy<string> _ToolTip;
public string ToolTip
{
get
{
// executes GetToolTip when the Value property of Lazy<T> is accessed
return _ToolTip.Value;
}
}
private string GetToolTip()
{
// we're assuming we can retreive the tooltip by ID, and that PackageId is defined in the generated WCF entity
return _DataAccessor.GetToolTipByPackageId(PackageId);
}
}
}
I have a few questions regarding to building WPF MVVM applications.
1) I'm using ICollectionView objects for databound controls such as ListView and ComboBox. I found this was the simplest way of gaining access to/tracking the selected item of these controls. What is the best way to replace the contents of ICollectionView? Currently I'm doing it like so:
private ICollectionView _files;
public ICollectionView Files {
get { return _files; }
}
void _service_GetFilesCompleted(IList<SomeFile> files) {
this.IsProcessing = false;
_files = CollectionViewSource.GetDefaultView(files);
_files.CurrentChanged += new EventHandler(FileSelectionChanged);
OnPropertyChanged("Files");
}
I didn't know whether it was necessary to reattach the handler every time I refresh the list of files?
2) Now that I've got my head round it, I am starting to like the MVVM pattern. However, one concept I'm not completely sure about is how to send notifications back down to the view. Currently I am doing this by binding to properties on my ViewModel. For example, in the above code I have an "IsProcessing" property that I use to determine whether to display a ProgressBar. Is this the recommended approach?
3) Following on from 2) - there doesn't seem to be a standard way to handle exceptions in an MVVM application. One thought I had was to have one method on my ViewModel base class that handled exceptions. I could then inject an IMessagingService that was responsible for relaying any error messages. A concrete implementation of this could use MessageBox.
4) I have a few tasks that I want to perform asynchronously. Rather than adding this logic directly in my service I created a decorator service that runs the underlying service methods on a new thread. It exposes a number of events that my ViewModel can then subscribe to. I have listed the code below. I understand that BackgroundWorker is a safer option but did not know whether it was suitable for running multiple asynchronous tasks at once?:
public void BeginGetFiles()
{
ThreadStart thread = () => {
var result = _serviceClient.GetUserFiles(username, password);
GetFilesCompleted(result.Files);
};
new Thread(thread).Start();
}
Finally, I realize that there are a number of MVVM frameworks projects that handle some of these requirements. However, I want to understand how to achieve the above using built-in functionality.
Thanks
If you have ListViews and ComboBoxes, you should really be considering an ObservableCollection<> to bind to these controls. Adding and removing items from the collection will automatically notify the control the property has changed.
If you're doing background processing, you can look at the BackgroundWorker or DispatcherTimer to handle updates to the UI. These both have the capability of acting on the UI thread, and can be thread safe.
To get the selected item from a combo box, expose an INotifyCollectionChanged object such as ObservableCollection and bind it to the itemsource, then create another property for the current item and binding ComboBox.SelectedItem (or ComboBox.SelectedValue if required) to it. You will need to manage the selection when updating the collection.
On the face of it, ICollectionView seems like the obvious choice but the WPF implementation really forces your hand on some threading code that you really shouldn't be troubled with.
I used ICollectionView and CollectionViewSource recently (for filtering) and have become frustrated with how many dispatcher issues have crept in. Today I am probably going to revert to the method i describe above.
I am really stuck trying to implement RIA Services with MVVM and a crud datagrid. I think i'm missing some key idea. All of the examples I have seen use a submit button or similar to send the data back to the client. I'd rather submit as soon as a row is added/deleted/modified. I thought of a couple of approaches:
Have my IEnuerable Itemsource property automatically bound to my context's entitycontainer. I tried this but I cant set the context's entities from the property.
Capture the row change events and manually tell my context that is has changed items. This doesnt feel right though in a MVVM design considering my Commodity property should reflect changes in the datagrid.
Additionally, how do I tell RIA Services what method it should be using for a particular crud operation? Does this happen on the client or server?
Note I am using the ComponentOne datagrid, though I dont think its behaviour would differ from the normal Silverlight datagrid for this simple example.
View:
<c1grid:C1DataGrid x:Name="C1Grid" AutoGenerateColumns="False"
ItemsSource="{Binding Commodities}">
<c1grid:C1DataGrid.Columns>
<c1grid:DataGridTextColumn Binding="{Binding Name, Mode=TwoWay}"></c1grid:DataGridTextColumn>
</c1grid:C1DataGrid.Columns>
</c1grid:C1DataGrid>
ViewModel:
public class CommodityViewModel : Model
{
private CommodityContext _context;
private Commodity _selectedCommodity;
public IEnumerable<Commodity> Commodities
{
get { return _context.Commodities;}
//Setter wont work here
}
public CommodityViewModel()
{
_context = new CommodityContext();
_context.Load(_context.GetCommoditiesQuery(), (LoadOperation<Commodity> loadOperation) =>
{
RaisePropertyChanged("Commodities");
_context.Commodities.EntityAdded += Commodities_EntityAdded;
}, null );
}
private void Commodities_EntityAdded(object sender, EntityCollectionChangedEventArgs<Commodity> e)
{
//how does RIA know to use my AddCommodity method on the server side? Where do I set this?
_context.SubmitChanges();
}
}
Ok so perhaps my answer is only relevant to users of the C1 DataGrid - but they have implemented a special API for interacting with RIA Services. Details available here.
I am making a RIA application using Silverlight and web services. The method of a web service returns a DataSet (a classic ASP.NET DataSet which is found in system.data).
How can I bind a DataGrid of Silverlight with the DataSet which is returned by the Web service?
Dataset is not supported in SL. If You have fixed datasource then you can create data contract (property class) and transfer data by observable collection which you can easily bind with your datagrid.
But if you want something generic which you would like to bind with your SL Datagrid then in that case you have to create Collection of collection
IEnumerable<IDictionary<string, string>> LData = new List<IDictionary<string, string>>();
while (sdrdr.Read())
{
dict = new Dictionary<string, string>();
for (int i = 0; i < sdrdr.FieldCount; i++)
{
dict.Add(sdrdr.GetName(i), sdrdr[i].ToString());
}
yield return dict;
}
and then you have to create anonymous type with reflection.emit in SL application which you can bind with your SL Datagrid.
I think you are going to struggle, given SL's access to the system.data is limited at best, you get System.Data.Services but that doesn't contain the dataset you are talking about.
If you are returning the data from a WCF service, then I would suggest the normal way is to define a data contract class with properties for each field etc and your WCF service method return a collection of that class.
On the proxy side, the auto generated proxy will see this as a List or ObservableCollection and you can then bind to that. That end's up being a bit dirty though since you want logic and to implement INotifyPropertyChanged and various other pieces, so you may well end up transferring the data out of the collection you are given into a local class that is acting as the VM, for the view to bind to.
When using .NET RIA Services and MVVM in Silverlight 3.0 is there a difference between the Metadata type from RIA Services and the ViewModel from the MVVM pattern? Are these the same thing or should they be keep separate?
The metadata type is a sealed internal class to the partial Entity class. There doesn't seem to be a proper separation there but the metadata type can also be decorated with attributes for Validation which makes it look like a ViewModel.
I've searched around but I didn't see anything that talks about this in any detail.
Agree with ChuckJ - generally the DomainContext forms part of a view model. For example, say I had a search page that allowed searching against a product catalog. Here is how I'd structure things:
On the server:
class Catalog : DomainService {
IQueryable<Product> GetProducts(string keyword) { ... }
}
The generated DomainContext:
class Catalog : DomainContext {
EntityList<Product> Products { get; }
void LoadProducts(string keyword);
}
The view model I would write:
class SearchViewModel {
Catalog _catalog = new Catalog();
public IEnumerable<Product> Results {
get { return _catalog.Products; }
}
public void Search(string keyword) {
_catalog.Products.Clear();
_catalog.LoadProducts(keyword);
}
}
And then finally in my xaml, I'd set my UserControl's DataContext to be an instance of SearchViewModel, and bind an ItemsControl to the Results property. I'd use the ViewModel pattern of your choice to bind a button click to Search (which is effectively a command that SearchViewModel exposes). I personally like something that I have working with Silverlight.FX as in:
<Button Content="Search"
fxui:Interaction.ClickAction="$model.Search(keywordTextBox.Text)" />
and as initially shown here.
As Chuck mentions I might indeed have other state in my view model, for example, the SelectedProduct that might be two-way bound to the SelectedItem of a ListBox in my xaml, and then bind the same SelectedProduct as the DataContext of a DataForm to show details of a selected product.
Hope that helps! I'll be blogging about this some more on my blog soon.
The RIA services data context was designed to play the role of a ViewModel in the MVVM pattern since they natively support data binding, but they don't use that term in their documentation. However, it really depends. You will probably need state in your view model than the RIA data context provides such as commands and other view related state. I think what you want to do is use the data contexts from the RIA services as a part of the view model.