I have a Main View with a toolbar and a TabControl region that has two views registered: View A, View B. All views should have as DataContext the same instance of ContactsViewModel, but in fact, each view is creating a new instance of ContactsViewModel.
This is the Main view code-behind:
public partial class ContactsView : UserControl
{
public IRegionManager regionManager;
private static Uri listViewUri = new Uri("/ContactsListView", UriKind.Relative);
private static Uri tilesViewUri = new Uri("/ContactsTilesView", UriKind.Relative);
public ContactsView(ContactsViewModel contactsViewModel, IRegionManager regionManager, IUnityContainer container)
{
this.ViewModel = contactsViewModel;
container.RegisterType<ContactsViewModel>();
this.regionManager = regionManager;
InitializeComponent();
}
public ContactsViewModel ViewModel
{
get { return this.DataContext as ContactsViewModel; }
set { this.DataContext = value; }
}
}
This is the view A code-behind:
public partial class ContactsListView : UserControl
{
public ContactsListView(IUnityContainer container)
{
ContactsViewModel viewModel = container.Resolve<ContactsViewModel>();
this.ViewModel = viewModel;
InitializeComponent();
SetupColumns();
}
public ContactsViewModel ViewModel
{
get { return this.DataContext as ContactsViewModel; }
set { this.DataContext = value; }
}
}
View B is similar to View A.
And this is the ViewModel:
public class ContactsViewModel : BindableBase
{
private readonly IRegionManager regionManager;
private readonly IEventAggregator eventAggregator;
private readonly IConfigurationContactsService contactsService;
private readonly DelegateCommand<object> deleteContactCommand;
private ObservableCollection<Contact> contactsCollection;
private ICollectionView contactsView;
public ContactsViewModel(IEventAggregator eventAggregator, IConfigurationContactsService contactsService, IRegionManager regionManager)
{
this.regionManager = regionManager;
this.contactsService = contactsService;
this.eventAggregator = eventAggregator;
this.deleteContactCommand = new DelegateCommand<object>(this.DeleteContact, this.CanDeleteContact);
this.contactsCollection = new ObservableCollection<Contact>(contactsService.GetContacts());
this.contactsView = CollectionViewSource.GetDefaultView(this.contactsCollection);
}
public ICollectionView ContactsView
{
get { return this.contactsView; }
}
public ObservableCollection<Contact> Contacts
{
get { return this.contactsCollection; }
}
public ICommand DeleteContactCommand
{
get { return this.deleteContactCommand; }
}
private void DeleteContact(object ignore)
{
IList<Contact> selectedContacts = contactsService.GetSelectedContacts();
foreach (Contact contact in selectedContacts)
{
if (contact != null)
{
contactsService.DeleteContact(contact);
}
}
SetProperty<ObservableCollection<Contact>>(ref this.contactsCollection, new ObservableCollection<Contact>(contactsService.GetContacts()), "Contacts");
}
private bool CanDeleteContact(object ignored)
{
return contactsService.GetSelectedContacts().Any();
}
}
How can I do ContactsListView (here called View A) to have the same instance of ContactsViewModel than the MainView?
EDITTED
Code in Main View and View A editted so in Main View I register the ViewModel into the container and in View A I Resolve the viewmodel. Still getting three instances. When the view model is resolved, a new instance is created.
As Richards suggested, I fixed the issue by registering the viewmodel as a singleton:
container.RegisterInstance<ContactsViewModel>(contactsViewModel);
Related
I am learning Xamarin forms.
I wanted to be able to bind some ViewModel to some values in a DataManager class (Singleton).
Say the Singleton is a BleManager and all ViewModel need to use it to get or set some information to or from the BLE device.
I Know how to bind my VM to the XAML view code.
But now I need to be able to get the viewModels to update local Data when the BLEManager updates some info, like battery level.
so for example (semi sudo-code).
public class BLEInterface: INotifyPropertyChanged
{
protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public async Task<float> GetDeviceName()
{
await Task.Delay(1000);
return float.random(0f:100f)
}
}
public sealed class BLEManager: INotifyPropertyChanged
{
private static BLEManager shared;
private static object objectLockCheck = new Object();
private BLEInterface BleModel { get; set; }
private float batteryLevel;
public float BatteryLevel {
get => batteryLevel;
set {
batteryLevel = value;
OnNotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private BLEManager()
{
}
public async Task ConnectToBLE()
{
await Task.Delay(1000);
BleModel = new TestModel();
}
public async void GetBatteryLevel()
{
BatteryLevel = await bleModel.GetDeviceName();
}
public static BLEManager Shared
{
get
{
if(shared == null)
{
lock (objectLockCheck)
{
if(shared == null)
{
shared = new BLEManager();
}
}
}
return shared;
}
}
}
The Part I need to know is how can my viewModel hook on changes from the BleManager battery level property.
In the code below, I am trying to inject a ViewModel into a View, while the ViewModel requires a Model to wrap and another service that is in the container. The Model is not registered as it is not really a "service".
How do I:
a) not have to provide the IService instance as an argument (let the container resolve it),
b) not have to register a factory for my ViewModels (there will be many)
So what I'm really asking the container to do is treat my Model (that I pass as an argument) as if it were a registered "service" for the duration of this call to GetInstance.
If this is not possible with LightInject, are there any containers out there that have something like this?
public static class Program
{
public static void Main()
{
var container = new LightInject.ServiceContainer();
var service = new Service1();
container.RegisterInstance<IService>(service);
// Have to register the factory
container.Register<IService, PersonModel, PersonViewModel>(
(f, s, p) => new PersonViewModel(s, p));
container.Register<View>();
var person = new PersonModel(); // this is contextual -- not a service.
object view = CreateView(container, typeof(View), service, person);
// ultimate desired code:
//var view = container.GetInstance(typeof(View), new object[] { person });
}
private static object CreateView(ServiceContainer container, Type viewType, IService service, object model)
{
var ctor = viewType.GetConstructors()[0];
var parameters = new List<object>();
foreach (var param in ctor.GetParameters())
{
var attr = param.GetCustomAttributes(typeof(ModelAttribute), false).FirstOrDefault();
if (model != null && attr != null)
{
parameters.Add(model);
}
else
{
parameters.Add(container.GetInstance(param.ParameterType, new object[] { service, model }));
}
}
return Activator.CreateInstance(viewType, parameters.ToArray());
}
}
public interface IService
{
}
public class Service1 : IService
{
}
public class PersonModel
{
}
public class PersonViewModel
{
public PersonModel PersonModel { get; set; }
public PersonViewModel(IService service, [Model] PersonModel person)
{
PersonModel = person;
}
}
public class View
{
public PersonViewModel PersonViewModel { get; set; }
public View(PersonViewModel vm)
{
PersonViewModel = vm;
}
}
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public class ModelAttribute : Attribute
{
}
I have solved the issues with a combination of techniques...
a) use a Scope and register the ViewModel and View with PerScopeLifetime.
b) use a "ModelTracker" registered with a factory to allow an instance not created by the container to be injected (since models will be created by client code or a DbContext).
This combination also allows me to not register a factory for every ViewModel type -- but instead use the built-in mass registration functions (like RegisterAssembly).
public static class Program
{
public static void Main()
{
var container = new LightInject.ServiceContainer();
container.RegisterInstance<IService>(new Service1());
container.Register<View>(new PerScopeLifetime());
container.Register<PersonViewModel>(new PerScopeLifetime());
container.Register<ModelTracker>(new PerScopeLifetime());
container.Register<PersonModel>((f) => (PersonModel)f.GetInstance<ModelTracker>().Instance);
using (var scope = container.BeginScope())
{
var tracker = scope.GetInstance<ModelTracker>();
tracker.Instance = new PersonModel() { Name = "person1" };
var view = scope.GetInstance<View>();
}
}
}
public class ModelTracker
{
public object Instance { get; set; }
}
public class PersonModel
{
public string Name { get; set; }
}
public class PersonViewModel
{
private readonly IService service;
private readonly PersonModel person;
public PersonViewModel(IService service, PersonModel person)
{
this.service = service;
this.person = person;
}
}
public class View
{
public PersonViewModel PersonViewModel { get; set; }
public View(PersonViewModel vm)
{
PersonViewModel = vm;
}
}
public interface IService { }
public class Service1 : IService { }
I want to use IOC with my service and I want to instead inject a class not an interface in the constructor as below in the services layer but I do not want to create a new object from the calling layer like var service = new InvoiceService(new ChangeInvoiceDueDateCommand()) instead I want to create something like this from my controller in MVC where the IInvoiceService is injected into the controller constructor but the problem I see is that
public InvoiceController(IInvoiceService invoiceService, IMapper mapper)
{
_invoiceService = invoiceService;
_mapper = mapper;
}
and then called like this
public ActionResult ChangeInvoiceDueDate(InvoiceChangeDueDateViewModel invoiceChangeDueDateViewModel )
{
var request = _mapper.Map<InvoiceChangeDueDateViewModel, ChangeInvoiceDuedateRequest>(invoiceChangeDueDateViewModel);
InvoiceChangeDueDateResponse response = _invoiceService.ChangeDueDate(request);
return View();
}
Service Layer
public class InvoiceService : IInvoiceService
{
private readonly ChangeInvoiceDueDateCommand _changeInvoiceDueDateCommand;
public InvoiceService(ChangeInvoiceDueDateCommand changeInvoiceDueDateCommand)
{
_changeInvoiceDueDateCommand = changeInvoiceDueDateCommand;
}
public InvoiceChangeDueDateResponse ChangeDueDate(ChangeInvoiceDuedateRequest invoiceChangeDueDateRequest)
{
_changeInvoiceDueDateCommand.Execute(invoiceChangeDueDateRequest);
return new InvoiceChangeDueDateResponse {Status = new Status()};
}
}
Command
public class ChangeInvoiceDueDateCommand : ICommand<ChangeInvoiceDuedateRequest>
{
private readonly IRepository<Invoice> _invoiceRepository;
readonly InvoiceDueDateChangeValidator _validator;
public ChangeInvoiceDueDateCommand(IRepository<Invoice> invoiceRepository)
{
_invoiceRepository = invoiceRepository;
_validator = new InvoiceDueDateChangeValidator();
}
public void Execute(ChangeInvoiceDuedateRequest request)
{
if (_validator.IsDuedateValid(request.NewDuedate))
{
Invoice invoice = _invoiceRepository.GetById(request.Id);
invoice.ChangedDueDate(request.NewDuedate);
_invoiceRepository.SaveOrUpdate(invoice);
}
else
{
throw new InvalidDueDateException();
}
}
}
ICommand
public interface ICommand<T> where T : IRequest
{
void Execute(T request);
}
IRequest
public interface IRequest
{
int Id { get; set; }
}
I worked it out. It was just a Windsor syntax issue. It ended up being as simple as registering the Command using the container.Register(Component.For<ChangeInvoiceDueDateCommand>());
I tried bind property of custom type in View Model with custom View, but in Binder method "SetValue(object value)" value is always null. Why it happens? Is it impossible bind property of custom type with custom view in MvvmCross?
My ViewModel:
public class RecipesFiltersVM : MvxViewModel
{
public SearchField DishField { get; private set; }
public SearchField CuisineField { get; private set; }
public SearchField IngredientField { get; private set; }
private readonly IFiltersService _filtersService;
public RecipesFiltersVM(IFiltersService filtersService)
{
_filtersService = filtersService;
UpdateSearchFields ();
}
private async void UpdateSearchFields ()
{
var allFilters = await _filtersService.LoadAllFilters ();
DishField = new SearchField (
allFilters
.Where(f => f.Type == FilterType.Dish)
.ToList()
);
CuisineField = new SearchField (
allFilters
.Where(f => f.Type == FilterType.Cuisine)
.ToList()
);
IngredientField = new SearchField (
allFilters
.Where(f => f.Type == FilterType.Ingredient)
.ToList()
);
}
}
My SearchField:
public class SearchField : MvxViewModel
{
private String _searchResult;
public String SearchResult {
get { return _searchResult; }
set {
_searchResult = value;
RaisePropertyChanged (() => SearchResult);
UpdateFoundFilters ();
}
}
private ObservableCollection<Filter> _foundFilters;
public ObservableCollection<Filter> FoundFilters {
get { return _foundFilters; }
set {
_foundFilters = value;
RaisePropertyChanged (() => FoundFilters);
}
}
}
In CustomView:
public class SearchFieldView : UIView
{
public UITextField SearchResult { get { return _searchResult; } }
private UITextField _searchResult;
public UITableView FoundFilters { get { return _foundFilters; } }
private UITableView _foundFilters;
}
In Binder:
public class SearchFieldViewWithSearchFieldBinder : MvxTargetBinding
{
protected SearchFieldView SearchFieldView {
get { return (SearchFieldView)Target; }
}
public SearchFieldViewWithSearchFieldBinder (SearchFieldView target)
: base (target)
{
}
public override void SetValue (object value)
{
//value is always null!
}
public override Type TargetType {
get
{
return typeof(SearchField);
}
}
public override MvxBindingMode DefaultMode {
get
{
return MvxBindingMode.TwoWay;
}
}
}
Setup:
protected override void FillTargetFactories (Cirrious.MvvmCross.Binding.Bindings.Target.Construction.IMvxTargetBindingFactoryRegistry registry)
{
registry.RegisterCustomBindingFactory<SearchFieldView> (
"SearchField",
indicators => new SearchFieldViewWithSearchFieldBinder(indicators)
);
base.FillTargetFactories (registry);
}
In ViewController:
var set = this.CreateBindingSet<RecipesFiltersDialog, RecipesFiltersVM>();
set.Bind (_dish).For("SearchField").To (vm => vm.DishField);
set.Bind (_cuisine).For("SearchField").To (vm => vm.CuisineField);
set.Bind (_ingredient).For("SearchField").To (vm => vm.IngredientField);
set.Apply ();
UPD
Solved with two ways update ViewModel code. First I changed custom property declaration like:
private SearchField _dishField;
public SearchField DishField {
get
{
return _dishField;
}
set
{
_dishField = value;
RaisePropertyChanged (() => DishField);
}
}
Second I initialize my properties in ViewModel constructor before UpdateSearchFields () execution:
public RecipesFiltersVM(IFiltersService filtersService)
{
_filtersService = filtersService;
DishField = new SearchField (new List<Filter> ());
CuisineField = new SearchField (new List<Filter> ());
IngredientField = new SearchField (new List<Filter> ());
UpdateSearchFields ();
}
You need to create your own custom bindings for your custom view. For instance if you have followed the normal MvvmCross practice of using public C# properties and C# events to define the items in your custom view then you should be able to do something like this:
public class SearchFieldView : UIView, IMvxBindable
{
public IMvxBindingContext BindingContext { get; set; }
public SearchFieldView()
{
this.CreateBindingContext();
}
public UITextField SearchResult { get { return _searchResult; } }
private UITextField _searchResult;
public UITableView FoundFilters { get { return _foundFilters; } }
private UITableView _foundFilters;
[MvxSetToNullAfterBinding]
public object DataContext
{
get { return BindingContext.DataContext; }
set { BindingContext.DataContext = value; }
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
BindingContext.ClearAllBindings();
}
base.Dispose(disposing);
}
}
And then it should just "work." For inspiration checkout things like MvxView or MvxTableViewCell.
Alternatively you may be able to do your own MvxPropertyInfoTargetBinding like how MvvmCross is handling the UISegmentedControlBindings by implementing something like MvxUISegmentedControlSelectedSegmentTargetBinding.
Solved with Stuart's comment in two ways update ViewModel code. First I changed custom property declaration like:
private SearchField _dishField;
public SearchField DishField {
get
{
return _dishField;
}
set
{
_dishField = value;
RaisePropertyChanged (() => DishField);
}
}
Second I initialize my properties in ViewModel constructor before UpdateSearchFields () execution:
public RecipesFiltersVM(IFiltersService filtersService)
{
_filtersService = filtersService;
DishField = new SearchField (new List<Filter> ());
CuisineField = new SearchField (new List<Filter> ());
IngredientField = new SearchField (new List<Filter> ());
UpdateSearchFields ();
}
How do I bind MouseDoubleClick event of wpfdatagrid in the view as I'm using mvvm and Prism 2.
I prefer adding a MouseDoubleClickBehaviour and then you can attach it to any control, which will bind to your ViewModel. Calling commands from the View's code-behind creates direct dependencies which I don't like.
public static class MouseDoubleClickBehaviour
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(MouseDoubleClickBehaviour), new UIPropertyMetadata(null, OnCommandChanged));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(MouseDoubleClickBehaviour), new UIPropertyMetadata(null));
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
public static object GetCommandParameter(DependencyObject obj)
{
return obj.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject obj, object value)
{
obj.SetValue(CommandParameterProperty, value);
}
private static void OnCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
var grid = target as Selector;
////Selector selector = target as Selector;
if (grid == null)
{
return;
}
grid.MouseDoubleClick += (a, b) => GetCommand(grid).Execute(grid.SelectedItem);
}
}
Then you can do this in your XAML
<ListView ...
behaviours:MouseDoubleClickBehaviour.Command="{Binding Path=ItemSelectedCommand}"
behaviours:MouseDoubleClickBehaviour.CommandParameter="{Binding ElementName=txtValue, Path=Text}"
.../>
Listen to the MouseDoubleClick event in the code-behind of the View and call the appropriate method on the ViewModel:
public class MyView : UserControl
{
...
private MyViewModel ViewModel { get { return DataContext as MyViewModel; } }
private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
ViewModel.OpenSelectedItem();
}