How do I keep my DataService up to date with ObservableCollection? - mvvm

I have a class called CustomerService which simply reads a collection of customers from a file or creates one and passes it back to the Main Model View where it is turned into an ObservableCollection. What the best practice for making sure the items in the CustomerService and ObservableCollection are in sync. I'm guessing I could hookup the CustomerService object to respond to RaisePropertyChanged, but isn't this only for use with WPF controls? Is there a better way?
using System;
public class MainModelView
{
public MainModelView()
{
_customers = new ObservableCollection<CustomerViewModel>(new CustomerService().GetCustomers());
}
public const string CustomersPropertyName = "Customers"
private ObservableCollection<CustomerViewModel> _customers;
public ObservableCollection<CustomerViewModel> Customers
{
get
{
return _customers;
}
set
{
if (_customers == value)
{
return;
}
var oldValue = _customers;
_customers = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(CustomersPropertyName, oldValue, value, true);
}
}
}
public class CustomerService
{
/// <summary>
/// Load all persons from file on disk.
/// </summary>
_customers = new List<CustomerViewModel>
{
new CustomerViewModel(new Customer("Bob", "" )),
new CustomerViewModel(new Customer("Bob 2", "" )),
new CustomerViewModel(new Customer("Bob 3", "" )),
};
public IEnumerable<LinkViewModel> GetCustomers()
{
return _customers;
}
}

Handle the CollectionChanged event of "Customers". When it changes, call your service to keep it synced.
When binding your "Customers" make sure you specify "Mode=TwoWay" in your xaml.

Related

Automapper configuration setup

I am using entity framework for my DAL and want to convert entities objects to business objects and vice versa. This is taking place in my BLL project. I am hoping to setup automapper in my BLL project to take... let say Customer.cs auto generated by EF and convert it to CustomerWithDifferentDetail.cs (my business obj)
I attempted to create an AutoMapperBLLConfig.cs under BLL project with the following code:
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile(new CustomerProfile());
});
}
public class CustomerProfile : Profile
{
protected override void Configure()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerWithDifferentDetail>();
cfg.CreateMap<CustomerWithDifferentDetail, Customer>();
});
}
}
Then I created CustermerService.cs under BLL project with the following code to test if it's working:
public void CustomerToCustomerWithDifferentDetail()
{
AutoMapperBLLConfiguration.Configure();
Customer source = new Customer
{
Account = 1234,
Purchase_Quantity = 100,
Date = "05/05/2016",
Total = 500
};
Models.CustomerWithDifferentDetail testCustomerDTO = Mapper.Map<Customer, Models.CustomerWithDifferentDetail>(source)
}
I get this Error:
Missing type map configuration or unsupported mapping.
I am not sure what I did wrong. I don't have a start_up or global.aspx. This is a class library. I'm not sure what I'm missing or did wrong.
I have a separate project calls Models which hold all the business objects including CustomerWithDifferentDetail.cs. In this case, CustomerWithDifferentDetail only has two properties: Account and Total. If mapped, it should give me Account = 1234 and Total = 500 - basically the same data as entity object just in different shape.
======================= UPDATE=================================
AutoMapperBLLConfig.cs - stay the same as noted above
CustomerProfile.cs
public class CustomerProfile : Profile
{
protected override void Configure()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerWithDifferentDetail>().ReverseMap(); //cut it down to one line with ReverseMap
});
}
CreateMap<Customer, CustomerWithDifferentDetail>().ReverseMap(); //missed this one line before; hence, the error
}
CustomerService.cs
static CustomerService()
{
AutoMapperBLLConfiguration.Configure(); //per #Erik Philips suggestion, move this call to a static constructor
}
public void CustomerToCustomerWithDifferentDetail()
{
Customer source = new Customer
{
Account = 1234,
Purchase_Quantity = 100,
Date = "05/05/2016",
Total = 500
};
Models.CustomerWithDifferentDetail testCustomerDTO = Mapper.Map<Customer, Models.CustomerWithDifferentDetail>(source);
}
Result: my testCustomerDTO returns exactly what I expected.
Since you are using the instance method of AutoMapper:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerWithDifferentDetail>();
cfg.CreateMap<CustomerWithDifferentDetail, Customer>();
});
Then you need to use the instance for mapping:
Models.CustomerWithDifferentDetail testCustomerDTO =
config.Map<Customer, Models.CustomerWithDifferentDetail>(source)
I personally haven't really thought this through in my applications (I need to move to the instance method instead of the static method). (Migrating from status API).
Off the cuff, based on your code, I'd probably do something like:
public class PersonDataObject
{
public string Name { get; set; }
}
public class PersonBusinessObject
{
private readonly MapperConfiguration _mapper;
public string Name { get; set; }
PersonBusinessObject()
{
_mapper = new MapperConfiguration(cfg =>
{
cfg.CreateMap<PersonDataObject,PersonBusinessObject>();
});
}
public static PersonBusinessObject MapFrom(PersonDataObject data)
{
return _mapper.Map<PersonBusinessObject>(data);
}
}
Then you can simply:
PersonDataObject data = new PersonDataObject();
PersonBusinessObject business = PersonBusinessObject.MapFrom(data);

DbSet<>.Local ObservableCollection not saving to EF context database

I have a WPF MVVM application with a DataGrid bound to an ObservableCollection returned by DbSet<>.Local. The grid displays content from the database correctly, and changes to the grid change the ObservableCollection, but no changes are saved back to the database.
Context.cs
public class AppContext: DbContext
{
public AppContext() : base("name=DefaultConnection")
{
}
public DbSet<Field> Fields { get; set; }
}
ViewModel.cs
public class EditorViewModel : NotificationObject
{
private ObservableCollection<MyEntity> _myEntities;
private string _message;
public EditorViewModel()
{
var db = new AppContext();
db.MyEntities.Load();
this.MyEntities = db.MyEntities.Local;
}
public ObservableCollection<MyEntity> MyEntities
{
get
{
return _myEntities;
}
set
{
if (_myEntities != value)
{
_myEntities = value;
RaisePropertyChanged("MyEntities");
}
}
}
}
I had thought that changes to the ObservableCollection would automatically write back to the database? Or does SaveChanges need to be called somewhere?
So the answer to this is that using DbSet<>.Local keeps the ObservableCollection in sync with the context, you then just need to call SaveChanges on the context to write back to the database.
http://msdn.microsoft.com/en-gb/data/jj592872.aspx

MVVM : how to make view model set fields of clean model to persist view changes to database

In MVVM application with clean model (not implementing interfaces like INotifyPropertyChabged), the View Model Contains properties bound to the View and these properties get its values from the model object contained in the view model and should set the value of its properties when view changes one of the controls that are bound to these properties.
the propblem is when the view change; the changes are captured by the bound view model properties but the properties can't set the model object fields, the model doesn't change. I need the model fields to accept setting by the view model properties, then i can persist the updated model into the database taking into account that it is a clean model.
Here part of the view model code
public class SubsystemDetailsViewModel: INotifyPropertyChanged, ISubsystemDetailsViewModel
{
#region Fields
//Properties to which View is bound
private int? _serial;
public int? Serial
{
get { return Subsystem.Serial; }
set
{
//Subsystem.Serial=value;
_serial = value;
OnPropertyChanged("Serial");
}
}
private string _type;
public string Type
{
get { return Subsystem.Type; }
set
{
//Subsystem.Type = value;
_type = value;
OnPropertyChanged("Type");
}
}
//remaining properties ....
#endregion
//Service
private readonly ISubsystemService _subsystemService;
//Reference to the View
public ISubsystemDetailsView View { get; set; }
//Event Aggregator Event
private readonly IEventAggregator eventAggregator;
//Commands
public ICommand ShowTPGCommand { get; set; }
public DelegateCommand UpdateCommand { get; set; }
//
private bool _isDirty;
//Constructor ************************************************************************************************
public SubsystemDetailsViewModel(ISubsystemDetailsView View, ISubsystemService subsystemService, IEventAggregator eventAggregator)
{
_subsystemService = subsystemService;
this.View = View;
View.VM = this;
//EA-3
if (eventAggregator == null) throw new ArgumentNullException("eventAggregator");
this.eventAggregator = eventAggregator;
//Commands
this.ShowTPGCommand = new DelegateCommand<PreCommissioning.Model.Subsystem>(this.ShowTestPacks);
this.UpdateCommand = new DelegateCommand(this.UpdateSubsystem, CanUpdateSubsystem);
}
//****************************************************************************************************************
//ICommand-3 Event Handler
//this handler publish the Payload "SelectedSubsystem" for whoever subscribe to this event
private void ShowTestPacks(PreCommissioning.Model.Subsystem subsystem)
{
eventAggregator.GetEvent<ShowTestPacksEvent>().Publish(SelSubsystem);
}
//===============================================================================================
private void UpdateSubsystem()
{
_subsystemService.SaveChanges(Subsystem);
}
private bool CanUpdateSubsystem()
{
return _isDirty;
}
//*******************************************************************************************
public void SetSelectedSubsystem(PreCommissioning.Model.Subsystem subsystem)
{
this.SelSubsystem = subsystem;
}
//************************************************************************************************************
/// <summary>
/// Active subsystem >> the ItemSource for the View
/// </summary>
private PreCommissioning.Model.Subsystem _subsystem;
public PreCommissioning.Model.Subsystem Subsystem
{
get
{
//return this._subsystem;
GetSubsystem(SelSubsystem.SubsystemNo);
return this._subsystem;
}
set
{
if (_subsystem != value)
{
_subsystem = value;
OnPropertyChanged("Subsystem");
}
}
}
//Call the Service to get the Data form the Database
private void GetSubsystem(string SSNo)
{
this._subsystem = _subsystemService.GetSubsystem(SSNo);
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
_isDirty = true;
UpdateCommand.RaiseCanExecuteChanged();
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Subsystem is the model object which is populated using GetSubsystem() method. the view model properties like Serial get its value from the model as shown. i tried to set the model properties as shown in the commented out line in set part of the property but no change happen to the Subsystem object, always keep its original values
If GetSubsystem returns a new subsystem every time, that's your problem. In the 'set' for the properties you're binding to the view, you're calling the public property "Subsystem", not the private field you've created. So, every single time you set a property from the view, you are calling Subsystem.get which calls GetSubsystem(SelSubsystem.SubsystemNo);.
I think, in your ViewModel properties', you want to change it to:
//Properties to which View is bound
public int? Serial
{
get { return _subsystem.Serial; }
set
{
_subsystem.Serial=value; // NOTE THE USE OF THE PRIVATE FIELD RATHER THAN THE PROPERTY
OnPropertyChanged("Serial");
}
}
public string Type
{
get { return _subsystem.Type; }
set
{
_subsystem.Type = value; // NOTE THE USE OF THE PRIVATE FIELD RATHER THAN THE PROPERTY
OnPropertyChanged("Type");
}
You need to have a reference in your view-model to the model and the view-model will pass the values to the model. Your view-model will implement INotifyPropertyChanged and will be the datacontext of your view. In your view-model, write your bound properties like this:
private string yourProperty;
public string YourProperty
{
get { return yourProperty; }
set
{
if (value == yourProperty)
return;
yourProperty= value;
YOUR_MODEL_REFERENCE.YourProperty= yourProperty;
this.RaisePropertyChanged(() => this.YourProperty);
}
}

How can I marry AutoCompleteBox.PopulateComplete method with the MVVM paradigm?

Here is the setup:
I have an autocompletebox that is being populated by the viewmodel which gets data from a WCF service. So it's quite straightforward and simple so far.
Now, I am trying to follow the principles of MVVM by which the viewmodel doesn't know anything about the view itself. Which is good, because I bound the Populating event of the autocomplete box to a method of my viewmodel via triggers and commands.
So the view model is working on fetching the data, while the view is waiting. No problems yet.
Now, the view model got the data, and I passed the collection of results to a property bound to the ItemSource property of the control. Nothing happens on the screen.
I go to MSDN and to find the officially approved way on how this situation is supposed to be handled (http://msdn.microsoft.com/en-us/library/system.windows.controls.autocompletebox.populating(v=vs.95).aspx):
Set the MinimumPrefixLength and MinimumPopulateDelay properties to
values larger than the default to minimize calls to the Web service.
Handle the Populating event and set the PopulatingEventArgs.Cancel
property to true.
Do the necessary processing and set the ItemsSource property to the
desired item collection.
Call the PopulateComplete method to signal the AutoCompleteBox to show
the drop-down.
Now I see a big problem with the last step because I don't know how I can call a method on a view from the view model, provided they don't know (and are not supposed to know!) anything about each other.
So how on earth am I supposed to get that PopulateComplete method of view called from the view model without breaking MVVM principles?
If you use Blend's Interactivity library, one option is an attached Behavior<T> for the AutoCompleteBox:
public class AsyncAutoCompleteBehavior : Behavior<AutoCompleteBox>
{
public static readonly DependencyProperty SearchCommandProperty
= DependencyProperty.Register("SearchCommand", typeof(ICommand),
typeof(AsyncAutoCompleteBehavior), new PropertyMetadata(null));
public ICommand SearchCommand
{
get { return (ICommand)this.GetValue(SearchCommandProperty); }
set { this.SetValue(SearchCommandProperty, value); }
}
protected override void OnAttached()
{
this.AssociatedObject.Populating += this.PopulatingHook;
}
protected override void OnDetaching()
{
this.AssociatedObject.Populating -= this.PopulatingHook;
}
private void PopulatingHook(object sender, PopulatingEventArgs e)
{
var command = this.SearchCommand;
var parameter = new SearchCommandParameter(
() => this.AssociatedObject
.Dispatcher
.BeginInvoke(this.AssociatedObject.PopulateComplete),
e.Parameter);
if (command != null && command.CanExecute(parameter))
{
// Cancel the pop-up, execute our command which calls
// parameter.Complete when it finishes
e.Cancel = true;
this.SearchCommand.Execute(parameter);
}
}
}
Using the following parameter class:
public class SearchCommandParameter
{
public Action Complete
{
get;
private set;
}
public string SearchText
{
get;
private set;
}
public SearchCommandParameter(Action complete, string text)
{
this.Complete = complete;
this.SearchText = text;
}
}
At this point you need to do 2 things:
Wire up the Behavior
<sdk:AutoCompleteBox MinimumPopulateDelay="250" MinimumPrefixLength="2" FilterMode="None">
<i:Interaction.Behaviors>
<b:AsyncAutoCompleteBehavior SearchCommand="{Binding Search}" />
</i:Interaction.Behaviors>
</sdk:AutoCompleteBox>
Create a DelegateCommand which handles your aysnc searching.
public class MyViewModel : ViewModelBase
{
public ICommand Search
{
get;
private set;
}
private void InitializeCommands()
{
this.Search = new DelegateCommand<SearchCommandParamater>(DoSearch);
}
private void DoSearch(SearchCommandParameter parameter)
{
var client = new WebClient();
var uri = new Uri(
#"http://www.example.com/?q="
+ HttpUtility.UrlEncode(parameter.SearchText));
client.DownloadStringCompleted += Downloaded;
client.DownloadStringAsync(uri, parameter);
}
private void Downloaded(object sender, DownloadStringCompletedEventArgs e)
{
// Do Something with 'e.Result'
((SearchCommandParameter)e.UserState).Complete();
}
}

Communication between ViewModels

I have two questions regarding communication between ViewModels.
I am developing a customer management program. I'm using Laurent Bugnion's MVVM Light framework.
In the main page, there's a list of customers. when each customer is clicked, a child windows shows up with information about that customer. the user should be able to open up multiple child windows at the same time and compare information between customers. how do you pass customer object from the main page's ViewModel to the child window's ViewModel in an MVVM-friendly fashion?
In the child window that shows customer information, there are a number of tabs, each showing different areas of information. I've created separate ViewModels for each of the tabs. how can you share the current customer information between each tab's viewmodels?
Thanks a lot!
In my project I'm passing ViewModels to child windows too. I create a dependency property for the ViewModel in my child window's code behind and in the setter of this property I pass the ViewModel along to my child window's ViewModel. This means you're creating a separate ViewModel class just for your child window.
To answer your second question, you could have your child window's ViewModel contain properties that each tab cares about, but have their data context still be the same as the child window's data context so they have access to shared properties. This is actually very easy since they automatically get the child window's data context.
Here's an example illustrating the two concepts above.
The child window view DetailsWindow.xaml (note that I've gotten in the habit of naming my child window views *Window.xaml instead of *View.xaml)
<controls:ChildWindow x:Class="DetailsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:Views="clr-namespace:Views"
Title="Details"
DataContext="{Binding DetailsWindowViewModel, Source={StaticResource Locator}}"
>
<Grid>
<sdk:TabControl>
<sdk:TabItem Header="First Tab" Content="{Binding FirstTabContent}" />
<sdk:TabItem Header="Second Tab" Content="{Binding SecondTabContent}" />
</sdk:TabControl>
</Grid>
</controls:ChildWindow>
The child window view's code behind DetailsWindow.xaml.cs and its interface IDetailsWindow.cs
public partial class DetailsWindow : ChildWindow, IDetailsWindow
{
private IDetailsWindowViewModel ViewModel
{
get { return this.DataContext as IDetailsWindowViewModel; }
}
public DetailsWindow()
{
InitializeComponent();
}
#region Customer dependency property
public const string CustomerViewModelPropertyName = "Customer";
public ICustomerViewModel Customer
{
get
{
return (ICustomerViewModel)GetValue(CustomerViewModelProperty);
}
set
{
SetValue(CustomerViewModelProperty, value);
if (ViewModel != null)
{
ViewModel.Customer = value;
}
}
}
public static readonly DependencyProperty CustomerViewModelProperty = DependencyProperty.Register(
CustomerViewModelPropertyName,
typeof(ICustomerViewModel),
typeof(CustomerDetailsWindow),
null);
#endregion
}
public interface IDetailsWindow
{
ICustomerViewModel Customer { get; set; }
void Show();
}
The child window view model DetailsWindowViewModel.cs and its interface IDetailsWindowViewModel
public class DetailsWindowViewModel : ViewModelBase, IDetailsWindowViewModel
{
public DetailsWindowViewModel(IMessenger messenger)
: base(messenger)
{
}
#region Properties
#region Customer Property
public const string CustomerPropertyName = "Customer";
private ICustomerViewModel _customer;
public ICustomerViewModel Customer
{
get { return _customer; }
set
{
if (_customer == value)
return;
var oldValue = _customer;
_customer = value;
RaisePropertyChanged(CustomerPropertyName, oldValue, value, true);
}
}
#endregion
#region FirstTabContent Property
public const string FirstTabContentPropertyName = "FirstTabContent";
private FrameworkElement _firstTabContent;
public FrameworkElement FirstTabContent
{
get { return _firstTabContent; }
set
{
if (_firstTabContent == value)
return;
_firstTabContent = value;
RaisePropertyChanged(FirstTabContentPropertyName);
}
}
#endregion
#region SecondTabContent Property
public const string SecondTabContentPropertyName = "SecondTabContent";
private FrameworkElement _secondTabContent;
public FrameworkElement SecondTabContent
{
get { return _secondTabContent; }
set
{
if (_secondTabContent == value)
return;
_secondTabContent = value;
RaisePropertyChanged(SecondTabContentPropertyName);
}
}
#endregion
#endregion
}
public interface IDetailsWindowViewModel
{
ICustomerViewModel Customer { get; set; }
FrameworkElement FirstTabContent { get; set; }
FrameworkElement SecondTabContent { get; set; }
void Cleanup();
}
And you can show the child window from your MainPageViewModel.cs like this.
public class MainViewModel : ViewModelBase, IMainViewModel
{
private readonly IDetailsWindow _detailsWindow;
public MainViewModel(IMessenger messenger, IDetailsWindow DetailsWindow)
: base(messenger)
{
_detailsWindow = DetailsWindow;
}
private void DisplayCustomerDetails(ICustomerViewModel customerToDisplay)
{
_detailsWindow.Customer = customerToDisplay;
_detailsWindow.Show();
}
}
Note that I create interfaces for all of my view models and child windows and I use an DI/IoC container in my ViewModelLocator so that all of my ViewModels' dependencies are injected for me. You don't have to do this, but I like how it works.