I think my question is fairly trivial, but I'm not getting my problem right. I want to use a passive-Presenter in the MVP pattern. My present concrete view is an Excel Add-in, but I don't want this to limit me in future.
On this view I have a RibbonDropDown, the items I need to get from the Model, and pass to the View via the Presenter. I also want an Interface between the Presenter and View so that they are more loosely coupled.
This MVP example was quite useful, but it seems I can't bind the datasource like they've done here to their example of a list of products:
public IList<ProductItem> Products
{
set
{
this.uiProducts.DataSource = value;
this.uiProducts.DataBind();
}
}
So, if this is my Presenter:
public class Presenter
{
private IViewInterface iView;
public Presenter(IViewInterface pView)
{
iView = pView;
}
public void Initialize()
{
List<String> ServArr = new List<String>();
ServArr.Add("a server 1");
ServArr.Add("a server 2");
iView.ServArr = ServArr;
}
}
and this is the interface for the View:
public interface IViewInterface
{
List<String> ServArr { get; set; }
}
and this is a snippet of the View:
public partial class SympivotyRibbon : IViewInterface
{
private Presenter presenter;
public List<String> ServArr
{
set { ServArr = new List<String>(value); }
//get { return ServArr; }
}
private void SympivotyRibbon_Load(object sender, RibbonUIEventArgs e)
{
presenter = new Presenter(this);
presenter.Initialize();
for (int i = 0; i < ServArr.Count; i++)
{
RibbonDropDownItem item = Globals.Factory.GetRibbonFactory().CreateRibbonDropDownItem();
item.Label = ServArr[i];
dropDownServer.Items.Add(item);
}
}
}
Then I get the compiler error in the for-loop:
The property or indexer 'SympivotyRibbon.ServArr' cannot be used in this context because it lacks the get accessor
I don't know how to put in a getter together with the setter, so that I can populate the control. If I uncomment the get-part above, I get a StackOverflow exception, probably because it is circular.
I assume the answer should be easy, I just don't know how.
Related
These past days I've tried to understand the concept of invoked methods and domain contexts, but some things are still not so clear to me.
In my StudentDomainService I have this method:
[Invoke]
public IEnumerable<Student> GetStudents()
{
return _studentRepository.GetStudents();
}
This returns a list of students that I want to bind to my datagrid using a viewmodel.
In my StudentViewModel I have:
private StudentDomainContext _studentDomainContext;
private InvokeOperation<IEnumerable<Student>> _students;
public StudentViewModel()
{
_studentDomainContext = new StudentDomainContext();
_students = _studentDomainContext.GetStudents(OnInvokeCompleted, null);
}
public void OnInvokeCompleted(InvokeOperation<IEnumerable<Student>> studs)
{
if (studs.HasError)
{
studs.MarkErrorAsHandled();
}
else
{
var result = studs.Value;
}
}
public InvokeOperation<IEnumerable<Student>> Students
{
get { return _students; }
set
{
_students = value;
RaisePropertyChanged("Students");
}
}
And in StudentView I have a datagrid and I'm trying to bind to the Students property like this:
ItemsSource="{Binding Students}"
But when I start the application the data grid appears but it's not displaying anything. Can anyone please tell me what I'm doing wrong?
UPDATE I think the problem is that the OnInvokeCompleted() method is not executing on the UI, but I'm not really sure.
Basically, I have a 4 files that I'm dealing with:
DBDisplay.xaml
DBDisplay.xaml.cs
DBDisplayViewModel.cs
DBConn.cs
In my ViewModel I am trying to populate the following DataGrid from my .xaml file:
<DataGrid ItemsSource="{Binding Path=Users}"/>
With the following code:
public class DBDisplayViewModel
{
public ICollectionView Users { get; set; }
DBConn dbCon; // the connection object
DataSet dataSet;
DataRow dataRow;
private void Load()
{
string connectionString = Properties.Settings.Default.UserDB;
dbCon = new DBConn(connectionString);
dbCon.openConnection();
dataSet = dbCon.getDataSet(Queries.SelectAll);
DataTable table = dataSet.Tables[0];
PopulateTextFields(table, 1);
//Something to go here to populate the DataGrid
}
private void PopulateTextFields(DataTable table, int i)
{
dataRow = table.Rows[i];
}
public DBDisplayViewModel()
{
Load();
Users = CollectionViewSource.GetDefaultView(SOMETHING_HERE);
}
private void Closed(object sender, EventArgs e)
{
dbCon.closeConnection();
}
}
So SOMETHING_HERE should be linking to my database (as this is how I connected to a list of users before)
Also I'm thinking I need something like
DataGrid.DataSource = table; //DataGrid would be linking to the xaml code
To populate the DataGrid
I'm at and ends here, so if anyone can help, I'd be very happy!
As you are a newcomer to WPF, so I will keep things simple. To show a list of records, you need a collection. This collection you can get in your code using something like :
Users = CollectionViewSource.GetDefaultView(dataset1.Tables[0].DefaultView);
I am afraid you are not going the MVVM way. I will explain in simple terms. Ideally you should have a model class and collection of this class objects should be returned by your data access code. More importantly your view model has multiple responsibilities which it should not (read S from SOLID principles). It should be responsible for changing UI state and/or displaying data on View. There should be a separate class which will fetch data from database into ViewModel.
DBDisplay.xaml.cs
public DBDisplay()
{
InitializeComponent();
var viewModel = new DBDisplayViewModel();
viewModel.UserRepository = new UserRepository(); // You could use dependency injection but I left for simplicity.
this.DataContext = viewModel;
}
DBDisplayViewModel.cs
public class DBDisplayViewModel
{
private ObservableCollection<User> users;
public DBDisplayViewModel() {
Load();
}
public IUserRepository UserRepository
{
get; set;
}
public ObservableCollection<User> Users
{
get {
if(users == null) {
users = new ObservableCollection<User>();
}
return users;
}
set {
if(value != null) {
users = value;
}
}
}
private void Load() {
List<User> users = UserRepository.GetUsers();
Users = new ObservableCollection<User>(users);
}
}
IUserRepository.cs
public interface IUserRepository
{
List<User> GetUsers();
}
UserRepository.cs
public class UserRepository : IUserRepository
{
public List<User> GetUsers() {
List<User> users;
// put your data access code here
// and transform list of user model using dataset or SQL data reader.
return users;
}
}
User.cs (this is model)
public class User
{
// some properties
}
I am learning the whole new Universal Apps creation together with Prism and Unity, but I got a few questions I am not sure about:
I have the following simple data object:
public class Customer : IEditableObject, IEquatable<Customer>
{
private Customer backup;
public string Name { get; set; }
public string Surname { get; set; }
public DateTime DateOfBirth { get; set; }
public void BeginEdit()
{
this.backup = this.MemberwiseClone() as Customer;
}
public void CancelEdit()
{
this.Name = this.backup.Name;
this.Surname = this.backup.Surname;
this.DateOfBirth = this.backup.DateOfBirth;
}
public void EndEdit()
{
this.backup = this.MemberwiseClone() as Customer;
}
public bool WasChangeMade()
{
if (this.Equals(backup))
return false;
else
return true;
}
public bool Equals(Customer other)
{
return this.Name == other.Name &&
this.Surname == other.Surname &&
this.DateOfBirth == other.DateOfBirth;
}
}
Under my Main Page I have a simple ListBox, where I show collection of these Customers. Everything good so far.
Afterwards, when under my ListBox user selects any one of these Customer, then he can click Edit Settings button and edit properties of this selected Customer. It is a simple command:
cmd_EditCustomer = new DelegateCommand(() =>
{
_navigationService.Navigate(App.Experiences.Detail.ToString(), SelectedCustomer);
});
Which simply navigates to a new page (detail page, where user can do the changes) and the argument I pass here is the Selected Customer.
My DetailPage View Model looks like following:
public class DetailPageViewModel : ViewModel, Interfaces.IDetailPageViewModel
{
public DelegateCommand cmd_SaveChanges { get; set; }
public Customer SelectedCustomer { get; set; }
private readonly INavigationService _navigationService;
private readonly IDialogService _dialogService;
public DetailPageViewModel(INavigationService navigationService,
IDialogService dialogService)
{
_navigationService = navigationService;
_dialogService = dialogService;
InitializeCommands();
}
public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState)
{
this.SelectedCustomer = navigationParameter as Customer;
this.SelectedCustomer?.BeginEdit();
}
private void InitializeCommands()
{
cmd_SaveChanges = new DelegateCommand(() =>
{
SelectedCustomer?.EndEdit();
_dialogService.Show("Changes Saved!");
_navigationService.Navigate(App.Experiences.Main.ToString(), null);
});
}
}
As you can see, this is a very simple application, which I only use for learning purposes. Here are my questions:
1) Is it good to pass Selected Customer in such a way as I did? (in the parameter of the INavigationService), or should I implement other logic?
2) When user makes a change to the Selected Customer and clicks Save Changes (the only command you can see there), it does not update the original Customer (from my original collection). How is this possible? How to achieve, that my Customer will be updated? Should I create PubSubEvent for this?
EDIT:
I have managed to locate the error - when user navigates back to MainPage, my MainPageViewModel is re-initializes, which re-populates collection of items. The question now is - how can I keep MainWindowViewModel alive thorough the applications life?
Re-populates collection of items from what?
You just need to save a new values, for example if you populate your customers from DB you have to call DB and save changes before navigate back etc, so after that when MainPageViewModel would be re-initializes you'll get your changes and changes performed by another users.
In the end, I found out that this was not a good way how to hold data in your application.
Based on what I have read, I should have implemented Repository Strategy, which is only referenced in a ViewModel such as:
public MainPageViewModel(IDataRepository dataRepository, INavigationService navService, ...){etc.}
Example of a simplified interface:
public interface IDataRepository
{
List<string> GetListOfStrings();
string GetUserEnteredData();
void SetUserEnteredData(string data);
}
This is how you initialize it in UnityContainer:
_container.RegisterType<IDataRepository, DataRepository>();
You can read more from Patterns & Practices team in here:
https://prismwindowsruntime.codeplex.com/
I have a ViewModel called MainViewModel (of course) which contains multiple Contructors as per the below:
[ImportingConstructor]
public MainViewModel(IWindowManager windowManager)
: this(windowManager, new DataProvider(), new LocalJsonPersistenceManager())
{
}
[PreferredConstructorAttribute]
public MainViewModel(IWindowManager windowManager, IInformationProvider infoProvider,
IPersistenceManager persistenceManager)
{
//generating data, handling clicks etc.
}
Inside that ViewModel is a public item that is constantly being updated (whenever a user clicks on a certain button and takes some actions on the form):
public Item ClickedItem
{
get { return clickedItem; }
set
{
clickedItem = value;
NotifyOfPropertyChange(() => ClickedItem);
if (ClickedItem != null)
{
FindNextItem();
}
}
}
Now i have a UserControl I am building that contains a ListView that I personnalised to make it a sticky headered listview (header moves up whenever the next header is reached blabla ...). because I can only do this via a GroupStyled ListView, I must build the data for the ListView in the C# code behind.
EDIT:
I am trying it using a ViewModelLocator as such:
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
// Create design time view services and models
//SimpleIoc.Default.Register<IDataService, DesignDataService>();
}
else
{
// Create run time view services and models
//SimpleIoc.Default.Register<IDataService, DataService>();
}
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
And I am calling up the data's specific value as such:
var vm1 = (new ViewModelLocator()).Main;
testtxt.Text = vm1.ClickedItem.Name;
But it keeps giving me an error message on runtime on the line:
return ServiceLocator.Current.GetInstance<MainViewModel>();
in the ViewModelLocator's block:
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
With the error message {"Type not found in cache: Caliburn.Micro.IWindowManager."} and an InnerException message of null.
It looks like MEF cannot construct the IWindowManager, which is a dependency of your ViewModel.
Try registering at least the default instance from Caliburn.
Update
taken straight from caliburn.Micro's MEF-Bootstrapper:
container = CompositionHost.Initialize(
new AggregateCatalog(
AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
)
);
var batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(container);
container.Compose(batch);
You can check the sample code from Caliburn.Micro Github Samples.
https://github.com/Caliburn-Micro/Caliburn.Micro/blob/master/samples/Caliburn.Micro.HelloMef/Caliburn.Micro.HelloMef/MefBootstrapper.cs
You need to register your WindowManager in MEF.
Is it possible to have a constructor in the ViewModel, which initializes the data service?
My data service is accessing the web-service of the data storage in a manner similar to this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Commands;
using MobSales.Logic.DataService;
using MobSales.Logic.Base;
using MobSales.Logic.Model;
namespace MobSales.Logic.ViewModels
{
public class CustomersViewModel:MvxViewModel
{
ICustomerService custService;
public CustomersViewModel(ICustomerService custService)
{
this.custService = custService;
if (custService != null)
{
custService.LoadCustomerCompleted += new EventHandler<CustomerLoadedEventArgs>(custService_LoadCustomerCompleted);
}
loadCustomerCommand = new MvxRelayCommand(LoadCustomer);
loadCustomerCommand.Execute();
}
private ObservableCollection<Customer> customers;
public ObservableCollection<Customer> Customers
{
get { return customers; }
set
{
customers = value;
FirePropertyChanged("Customers");
}
}
private CustomerViewModel customer;
public CustomerViewModel Customer
{
get { return customer; }
set
{
customer = value;
FirePropertyChanged("Customer");
}
}
private MvxRelayCommand loadCustomerCommand;
public MvxRelayCommand LoadCustomerCommand
{
get { return loadCustomerCommand; }
}
public void LoadCustomer()
{
custService.LoadCustomer();
}
void custService_LoadCustomerCompleted(object sender, CustomerLoadedEventArgs e)
{
if (e.Error != null)
{
return;
}
List<Customer> loadedCustomers = new List<Customer>();
foreach (var cust in e.Customers)
{
loadedCustomers.Add(new Customer(cust));
}
Customers = new ObservableCollection<Customer>(loadedCustomers);
}
}
I am getting an exception but can only see the following partial description:
Cirrious.MvvmCross.Exceptions.MvxException: Failed to load ViewModel for type MobSales.Logic.ViewModels.CustomersViewModel from locator MvxDefau…
The binding from View to ViewModel is realized as I've shown in this post: MVVMCross Bindings in Android
Thanks!
One of the unusual (opinionated) features of MvvmCross is that by default it uses ViewModel constructor parameters as part of the navigation mechanism.
This is explained with an example in my answer to Passing on variables from ViewModel to another View (MVVMCross)
The basic idea is that when a HomeViewModel requests a navigation using:
private void DoSearch()
{
RequestNavigate<TwitterViewModel>(new { searchTerm = SearchText });
}
then this will cause a TwitterViewModel to be constructed with the searchTerm passed into the constructor:
public TwitterViewModel(string searchTerm)
{
StartSearch(searchTerm);
}
At present, this means that every ViewModel must have a public constructor which has either no parameters or which has only string parameters.
So the reason your ViewModel isn't loading is because the MvxDefaultViewModelLocator can't find a suitable constructor for your ViewModel.
For "services", the MvvmCross framework does provide a simple ioc container which can be most easily accessed using the GetService<IServiceType>() extension methods. For example, in the Twitter sample one of the ViewModel contains:
public class TwitterViewModel
: MvxViewModel
, IMvxServiceConsumer<ITwitterSearchProvider>
{
public TwitterViewModel(string searchTerm)
{
StartSearch(searchTerm);
}
private ITwitterSearchProvider TwitterSearchProvider
{
get { return this.GetService<ITwitterSearchProvider>(); }
}
private void StartSearch(string searchTerm)
{
if (IsSearching)
return;
IsSearching = true;
TwitterSearchProvider.StartAsyncSearch(searchTerm, Success, Error);
}
// ...
}
Similarly, you can see how the conference service data is consumed in the Conference BaseViewModel
If your preference is to use some other IoC container or some other construction mechanism for your ViewModels, then you can override the ViewModel construction within MvvmCross.
Take a look at this question (and answers) for ideas on how to do this - How to replace MvxDefaultViewModelLocator in MVVMCross application
e.g. if you want to, then it should be fairly easy for you to adjust the MyViewModelLocator example in that question to construct your ViewModel with your service.