In continuation to my previous question "Updating/using a variable from another ViewModel", I decided to start using Caliburn Micro as a framework.
I set up the Event Aggerator according to this guide http://www.mindscapehq.com/blog/index.php/2012/02/01/caliburn-micro-part-4-the-event-aggregator/ .
The thing is that according to this guide, there should not be an "empty" constructor that takes 0 arguments.
Well, fine.
Now the problem is that I have no idea how to bind a ViewModel to a View now.
Before switching to this framework I used App.xaml and Static resources as datacontext's but I can do it no more since there is not an empty constructor.
How do I solve this problem? I've been trying to solve it for like an hour now and I have achieved absolutely nothing.
Some code:
[Export(typeof(ViewModelBase))]
public class ViewModelBase : INotifyPropertyChanged, IHandle<updateEvent>
{
private Class _studclass;
public AddStudentViewModel NewModel { get; private set; }
public Class StudentClass
{
get { return _studclass; }
set
{
_studclass = value;
NotifyPropertyChanged("StudentClass");
}
}
[ImportingConstructor]
public ViewModelBase(AddStudentViewModel newModel, IEventAggregator events)
{
StudentClass = new Class();
NewModel = newModel;
Student asaf = new Student();
asaf.Name = "Asaf";
StudentClass.StudentList.Add(asaf);
events.Subscribe(this);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public void Handle(updateEvent msg)
{
StudentClass.StudentList.Add(msg.Student);
}
}
That's the "main" viewmodel. However, I can't bind it to a view, so the data doesn't show... I even tried to set up a fake data... which didn't work so well as you may have guessed.
Caliburn.Micro uses conventions. So if you have a ShellViewModel in a ViewModels folder, it will look for a ShellView in a Views folder.
I would recommend having a look at the samples and the documentation. A good starting point is to install the Caliburn.Micro.Start NuGet package (which pulls in Caliburn.Micro) and then edit the App.xaml file as described in the documentation.
Essentially, the bootstrapper is an application resource which causes it to be instantiated, which then resolves your shell view model via your configured IoC container, locates the corresponding shell view, does the binding and displays it using the Caliburn.Micro window manager.
You also don't need to implement INotifyPropertyChanged on a view model base class. Caliburn.Micro includes a PropertyChangedBase type, and a Screen type, and Conductor types.
Related
I have a main view model and it loads some user controls (Stability,Tank) according to some cases. Those user controls have view models like StabilityVM, TankVM. My question is that how to send data from main view model to user control view model or vice versa. My main.xaml and its view model are shown below
Main window xaml part
<ContentControl Grid.Row="1" Content="{Binding
ActiveUserControl,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:TankVM}">
<uc:Tank></uc:Tank>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:StabilityVM}">
<uc:Stability></uc:Stability>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
Main View Model Part
public class MainVM : INotifyPropertyChanged
{
Stability stability;
Tank tank;
WeightItem weightItem;
public List<Grid> myControls = new List<Grid>();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private UserControl activeUserControl;
public UserControl ActiveUserControl
{
get { return activeUserControl; }
set { activeUserControl = value;
OnPropertyChanged(nameof(ActiveUserControl));
}
}
public MainVM()
{
//First user control class
stability = new Stability();
stability.Visibility = Visibility.Visible;
//Second user control class
tank = new Tank();
tank.Visibility = Visibility.Visible;
ActiveUserControl = stability;
ActiveUserControl.Visibility = Visibility.Visible;
}
}
To communicate between view models you can use the mediator pattern. Many WPF application frameworks like Caliburn.Micro or Prism offer a concrete implementation of this pattern, so you do not have to implement it yourself. Both of them offer a so-called event aggregator for communicating via loosely-coupled events.
As an example, in Prism you would define an event type with or without event arguments.
// Events derive from PubSubEvent or PubSubEvent<T> if you use event aruments
public class TankChangedEvent : PubSubEvent<TankChangedEventArgs>
{
}
// Event arguments for the above event, uses a property to capture the changed tank
public class TankChangedEventArgs
{
public Tank Tank { get; }
TankChangedEventArgs(Tank tank)
{
Tank = tank;
}
}
Then in the sender view model you use the event aggregator to publish the event. Note, that you need to create or inject an instance eventAggregator of the event aggregator.
var args = new TankChangedEventArgs(changedTank);
eventAggregator.GetEvent<PartsDeletedEvent>().Publish(args);
In the receiving view model you need to subscribe to the event e.g. in the constructor.
eventAggregator.GetEvent<LogicalDirectoriesChangedEvent>().Subscribe(OnTankChanged);
Create a method OnTankChanged to handle the event when it is received.
private void OnLogicalDirectoriesChanged(TankChangedEventArgs args)
{
// Do something with the event arguments
}
You can read about it in detail here. For an example in Caliburn Micro refer to this site. Although these mechanisms are built into the corresponding frameworks, you can also use them isolated in your project without migrating the whole application. However, there are also other implementations out there, but these frameworks are a good starting point, as they have a comprehensive documentation on that topic.
I have a UserControl view which require data to be pulled from the database to set the ObservableCollection in order to render a DataGrid.
I want to set the ObservableCollection when the UserControl is loaded not when the view model is constructed. Also, I prefer to asynchronously set the ObservableCollection so the UserContror opens immediacy then then the data-grid is set when the data becomes available.
In my view model I create a public method called SetVendors() which fetches the data from the database and sets ObservableCollection just before the view is notified. However, I am using the view class directly to set the SetVendors() method which sound like a bad practice for some reason to me.
Questions
Is it possible to set the data on view-load directly from the view-model? If so, how? Also, is it possible to set the data asynchronously so the view loads quick?
Here is my ViewModel code
public class VendorsListViewModel : ViewModel
{
protected IUnitOfWork UnitOfWork { get; set; }
public int CurrentPage { get; set; }
public int ModelsPerPage { get; set; }
public IPagedList PageMeta { get; set; }
private ObservableCollection<Vendor> _Vendors { get; set; }
public ObservableCollection<Vendor> Vendors
{
get
{
return _Vendors;
}
set
{
_Vendors = value;
NotifyPropertyChanged();
}
}
public VendorsListViewModel(IUnitOfWork unitOfWork)
: base()
{
UnitOfWork = unitOfWork;
CurrentPage = 1;
ModelsPerPage = 20;
}
public VendorsListViewModel()
: this(new UnitOfWork())
{
}
public void SetVendors()
{
var vendors = UnitOfWork.Vendors.Get(CurrentPage, ModelsPerPage);
Vendors = new ObservableCollection<Vendor>(vendors);
PageMeta = vendors.GetMetaData();
}
}
This is how currently set the data from the code behind the view which sound like a bad practice to me.
public partial class Index : UserControl
{
public Index()
{
InitializeComponent();
}
// This event is called from the view when the form is loaded using `Loaded="ViewLoaded"` XAML code
private void ViewLoaded(object sender, EventArgs e)
{
(DataContext as VendorsListViewModel).SetVendors();
}
}
Loading the data from the view´s code behind is indeed a bad practice as you are breaking the MVVM separation of concerns.
A better way to do that would be using a command, or, more precisely an event to command, check this article for a nice explanation.
To achieve this you can use the System.Windows.Interactivity library that comes with Blend for Visual Studio SDK for .NET, if you don't have it just run the Visual Studio installer and select it.
Then add a reference to the library by going to Add reference -> Assemblies -> Extensions.
Once you have this, things are simple really, you need to just:
First add the namespace to your View:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Then, bind the event to a command in your view model like this:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Now in your ViewModel we have a little problem, we need to provide a command to be called when the event is triggered, a command is an implementation of the ICommand interface, all MVVM frameworks out there will provide you with ready to roll ICommand implementations so you can either use one of these frameworks or you can implement ICommand yourself if you just need it for this particular example, wait! you don't need to implement it, you can just get it from this question ,-)
For my example, however, I will use MVVM Light toolkit which I recommend you to use as it will relief you from most of the MVVM annoyances that you will need to deal with in any real-world application.
You can get MVVM Light from NuGet:
Install-Package MvvmLight -Version 5.4.1
So now you can use MvvmLight implementation of ICommand which is called RelayCommand, you would do so in your ViewModel as follows:
[...]
public ICommand LoadedCommand { get; private set; }
public VendorsListViewModel(IUnitOfWork unitOfWork) : base() {
UnitOfWork = unitOfWork;
CurrentPage = 1;
ModelsPerPage = 25;
LoadedCommand = new RelayCommand(this.Loaded);
}
private void Loaded()
{
SetVendors();
}
As per your second question: absolutely, you can load the data asynchronously if you want to. This is a whole different question though, take a look at this article for more info: https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/
I'm trying to learn on MVVM. I've understand the concept, however, i'm confused about the binding. I'm not sure where to bind my Fill property. Please help. Tqvm in advanced.
View - name: MainScreen.xaml
<Path Fill="{Binding mainScreenClass, Converter={StaticResource colorConverter}}"/>
inCodeBehind
DataContext = new vmMainScreen();
ViewModel - name:vmMainScreen
public ICommand cmdMouseEnterNav { get; private set; }
public mMainScreen mainScreenClass { get; set; }
public vmMainScreen()
{
mainScreenClass = new mMainScreen();
mainScreenClass.propNaviconFill = new SolidColorBrush(Colors.White);
naviconMouseEventChecker();
}
private void naviconMouseEventChecker()
{
cmdMouseEnterNav = new SimpleCommand
{
ExecuteDelegate = x => mainScreenClass.propNaviconFill = (SolidColorBrush)(new BrushConverter().ConvertFrom("#c5a02b"))
};
}
Model - name:mMainScreen
public class mMainScreen : INotifyPropertyChanged
{
private Brush _NaviconFill = new SolidColorBrush(Colors.White);
public Brush propNaviconFill
{
get
{
return this._NaviconFill;
}
set
{
this._NaviconFill = value;
NotifyPropertyChanged("propNaviconFill");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
I understand that when i breakpoint on my colorConverter, I'm getting the class. Not the property of propNaviconFill. If i create another property with the Brush class on my ViewModel and bind it to Fill, there is no problem. But that means I'm not following the correct structure of MVVM. Thanks again.
You should bind to the property of your view model.
<Path Fill="{Binding propNaviconFill, Converter={StaticResource colorConverter}}"/>
Use the view model implementing the INotifyPropertyChanged as the data context of your view.
DataContext = new mMainScreen();
If you really want to use vmMainScreen as your data context, then vmMainScreen should implement INotifyPropertyChanged there and you should study how NotifyPropertyChanged was used to notify the view that the view model property has changed.
Keep in mind there are two basic types of MVVM:
1. View First
2. View Model First
Based on your example you are trying to do View First. This is easier to implement but comes with drawbacks on larger projects since the view controls the creation of the ViewModel it's harder to inject data or state into the ViewModel.
For all MVVM patterns you have the three parts:
Model - Basically a state bag. This thing is just like a customer class which most of the time implements INotifyProperty changed.
ViewModel - This is like the controller class in MVC. It has all the real logic and does the work.
View - This is your XAML and it only holds presentation logic. The code-behind class ie: MyWindow.xaml.cs should not be used except to setup the ViewModel if your going View First. (there are exceptions of course but generally it should basically be empty)
For View First
Your Window (or control) should create the ViewModel in the constructor and assign it to the DataContext.
Your ViewModel will have ICommand's, ObservableCollections and such that can be bound to controls in the View. So when your constructor fires you fill up your data and put it into the necessary structures; because of databinding this gets related to the View and shown.
Your Model (you usually have more than one, can have Customer, Order, StockTicker or whatever.) These are created by the ViewModel and put into things such as ObservableCollections for the View to databind to.
I am trying to implement MVVM for one of my Windows Phone app that i am developing and its growing to be big.
I have tried below code in Model class.I want to know how can i handle the scenario where user clicks on a button "Latest Entry" and it will connect to a service and executes a method asynchronously.Once the data is returned i have to display the latest record in UI which has 3 text fields EmpName,EmpID,Address.
Code in Model Class:
public class EmpDetailsModel:INotifyPropertyChanged
{
private string _EmpName;
public string EmpName
{
get { return _EmpName; }
set {
if (value != _EmpName)
{
_EmpName = value;
RaisePropertyChanged("EmpName");
}
}
}
private string _EmpId;
public string EmpId
{
get { return _EmpId; }
set {
if (value != _EmpId)
{
_EmpId = value;
RaisePropertyChanged("EmpId");
}
}
}
private string _Address;
public string Address
{
get { return _Address; }
set {
if (value != _EmpId)
{
_EmpId = value;
RaisePropertyChanged("Address");
}
}
}
#region myfirstmodel inotify members
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
The code to connect to service is below:
EmpAzureSer empAzureSer = new EmpAzureSer();
empAzueSer.GetLatestEntry += new GetLatestEntryCompletedEventHandler(LatestEntryCompleted);
private void LatestEntryCompleted(object sender, GetLatestEntryCompletedEventArgs e
{
//get the data from e as e.Name,e.Id and e.Address and bind them to UI.
}
view xaml code:
<Button Name="FetachLAtest" Click="FetachLatest_Click"></Button>
<TextBlock Name="EmployeeName"></TextBlock>
<TextBlock Name="EmployeeID"></TextBlock>
<TextBlock Name="EmployeeAddress"></TextBlock>
I was following the link http://msdn.microsoft.com/en-us/library/windowsphone/develop/gg521153(v=vs.105).aspx.
It was very helpful but I want to know where do i put the code to connect to service (model ? or Viewmodel ? How does the viewmodel should look like ?
There are various ways to implement MVVM into an application, it varies depending on developpers and application requirements.
But for started, let's try to keep things simple and to focus on ViewModels (because this seems to be where is your interest).
MVVM means Model View ViewModel, Model is your business/domain code, View is basically your XAML and its associated code behind, and ViewModel is the link/glue between Views and Models.
An important thing to note is that ViewModels mustn't know Views (meaning don't reference them). This ensures a better separation of concerns, and thus try to build an application easier to test and to maintain.
So to make a long story short, ViewModels don't know Views but they have to communicate with them... And this magic is made possible thanks to Bindings!
XAML/UI components display data, these data comes from the ViewModel which is bound to the View through Bindings mechanisms (provided on WP by the Silverlight framework).
This means the ViewModel contains all the data required by the View, actually a ViewModel represents all the data or behaviors of a View.
Being not the best person to describe the whole MVVM pattern and all its subtilities, i'll leave this sensitive task to most knowledgeable people in the field ;). Here are some really great links that should help you :
From Josh Smith
Wikipedia with code samples for ViewModel
If you already know MVC or MVP patterns, this one will help you to spot differences
All this being told, you must be a little bored with theory, so let's try to write some code. The problem is that there are many ways to organize your code, so all that follow is just a kind of pseudo code, it cannot be used directly into your application!
In your case, you could create just a ViewModel like this one
public class WhateverYouWantViewModel : INotifyPropertyChanged
{
private EmpDetailsModel _model;
public EmpDetailsModel Model
{
get { return _model; }
set
{
if (value != _model)
{
_model = value;
RaisePropertyChanged("Model");
}
}
}
public void GetLastestEntries()
{
// put in here the code calling your service
}
}
About assignements from data service to your this.Model, we are dealing with an asynchronous callback, so maybe it would be wiser to use the Dispatcher in case the callback is not called from the UI Thread :
EmpAzureSer empAzureSer = new EmpAzureSer();
empAzueSer.GetLatestEntry += new GetLatestEntryCompletedEventHandler(LatestEntryCompleted);
private void LatestEntryCompleted(object sender, GetLatestEntryCompletedEventArgs e
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
this.Model = new EmpDetailsModel()
{
//get the data from e as e.Name,e.Id and e.Address and bind them to UI.
};
});
}
Creating a new EmpDetailsModels before assigning it to this.Model will trigger RaisePropertyChanged and notify the View this property has changed. More specifically, the UI component bound to this property will be notified for being updated.
To bind your UI components to the ViewModel, you can do something like that :
<Button Name="FetachLAtest" Click="FetachLatest_Click"></Button>
<TextBlock Name="EmployeeName" Text="{Binding Model.EmpName}"></TextBlock>
<TextBlock Name="EmployeeID" Text="{Binding Model.EmpId}"></TextBlock>
<TextBlock Name="EmployeeAddress" Text="{Binding Model.Address}"></TextBlock>
Do not forget to set the DataContext of your View with your ViewModel instance.
Last but not least, you have to bind your "Latest Entry" Button to the ViewModel.GetLastestEntries method by calling it from your *View.FetachLatest_Click* event handler. All this can be achieved this way :
public partial class YourView : BasePage
{
private WhateverYouWantViewModel _viewModel;
public YourView()
{
InitializeComponent();
_viewModel = new WhateverYouWantViewModel();
this.DataContext = _viewModel;
}
private void FetachLatest_Click(object sender, RoutedEventArgs e)
{
_viewModel.GetLastestEntries();
}
}
And that's (almost) it! Why almost? Because the link between the View and the ViewModel is quite strong and defined into the code behind (which is something we are usually trying to avoid in MVVM).
Fortunately, there are some solutions to solve this issue :
What we call a ViewModelLocator could be used to store and to locate
ViewModels
A Command could be created in WhateverYouWantViewModel and bound to the "Lastest
Entry" Button, instead of calling directly the GetLastestEntries method in code behind
The downside of all this is that you would have to write more code and that's where MVVM framweworks come up! These frameworks will help you to write clean MVVM applications with minimum effort.
As a beginner, i would warmely advice you to visit MVVM Light Toolkit website. It contains lots of useful articles about the MVVM pattern, to learn how to design an MVVM application and to handle common scenarii using this framework. MVVM Light is not the only MVVM framework running on Windows Phone but i'm quoting it because it is widely used, it has a big community and it strives to keep things as simple as possible.
I'm aware this answer is only a starting point to achieve what you want. I only give you some ideas that need further study, but i hope it will help you move in the right direction.
How can I use one view model for many windows in WPF? I need model to be updated from one window only and handle these changes in others (for ex. property 'Locked').
I have one view model incapsulating the most general info that should be used not only on the A (suppose it is 'Company') window but also on windows child window B (suppose it is 'Person'). So the 'General' view model should be determined by A entity but be passes to all children entity. While updating this view model on A window - we should se changes on all B windows.
public partial class A : WindowBase
{
private GeneralViewModel general;
public GeneralViewModel General
{
get
{
return this.general ?? (this.general = new GeneralViewModel ());
}
}
}
public partial class B : WindowBase
{
private GeneralViewModel general;
public GeneralViewModel General
{
get
{
return this.general ?? (this.general = new GeneralViewModel ());
}
}
public B(GeneralViewModel g)
{
this.general = g;
}
}
I wish the model should be updated only in A and B was simply displaying that changes were maid. In case I pass model as it is shown in this code or if I implement 'General' as property with getter and setter changes are not applied.
Thanks for any help.
You could use a singleton-class as your ViewModel.
Example:
public Window()
{
this.DataContext = ViewModel.Instance.
}
EDIT:
public GeneralViewModel
{
public DataType Model
{
get { return DataType.Instance; }
}
}
Now everytime you access the Model in one of your GeneralViewModels, it is locked for all others.
Initialise your view model in a static member somewhere and have the windows return the value as their GeneralViewModel.
In the scenario you mentioned, your GeneralViewModel is a kind of Dependency to both your Window classes and for these purposes you can use some of the available IoC containers or MEF built into .Net 4. You can register your Dependencies including the GeneralViewModel in some application startup event.
Below is some sample code that will make your GeneralViewModel instance to be then located from the container it is registered with (MEF in below case):
[Export(typeof(B))]
public partial class B : WindowBase
{
private GeneralViewModel general;
public GeneralViewModel General
{
get
{
return this.general ?? (this.general = new GeneralViewModel ());
}
}
[ImportingConstructor]
public B(GeneralViewModel g)
{
this.general = g;
}
}
To learn more about MEF,see these articles:
CodePlex
Ten Reasons to use the Managed Extensibility Framework
Managed Extensibility Framework Overview
An Introduction to Managed Extensibility Framework (MEF) - Part I
There are many other DI and IoC containers available as Open Source downloads.
There is no problem if you use MVVM. In this case your ViewModel will correspond to some View which is basically the UserControl and you can place it to as many Windows as you wish.
And When you implement MVVM you should also use INotifyPropertyChanged or ObservableCollections