How to communicate between main view model and user control view model - c#

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.

Related

Difficulties with data-binding

Databinding is a difficult concept that I still can't quite grasp despite reading through dozens of topics already.
I want to have a TextBox that would change its text every time a field 'status' is being changed in my code-behind, for debugging purposes.
Here's what I have so far:
public partial class ReviewsControl : UserControl
{
private Status status = MainControl.AppStatus;
public string StatusDisplay
{
get
{
return status.ToString();
}
}
}
And here's my take on the view:
<TextBlock x:Name="StatusBlock" HorizontalAlignment="Left" Margin="10,450,0,0" TextWrapping="Wrap" Text="{Binding StatusDisplay, Source=ReviewsControl, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Height="40" Width="205"/>
The code above doesn't even show anything, let alone do that dynamically. What should I change if I want XAML to detect changes in the in my C# code and change the textbox accordingly?
I too had issues early on. Your view (display to end-users) does not care how or where things come from, you just know what will be exposed in your View Model controller to bind to. To have things updated in your view, the most common is binding and having your view model include the INotifyPropertyChanged interface. This is so you can force raising the event when a property changes, whatever is "listening" for it will update itself..
Simple class, you can just grab from the : INotify, the event exposed to allow things to get registered to, and your method to actually RAISE the event to pass up stream to those listening for changes.
public class SomeBaseClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Once that is done, your going to expose properties in your class by making as public with getter/setters. You don't bind to a field.
public string SomeThingYouWantToExpose {get; set; }
And in your code, however you are getting data, label refresh, whatever, you would set and raise the property-changed
public void GettingSomeData()
{
// … doing something to get / prepare / whatever...
SomeThingYouWantToExpose = "some new label";
// Now raise which the view bound to this property will updated itself
RaisePropertyChanged( "SomeThingYouWantToExpose" );
}
Now, in your view, you would bind to whatever your view model object is and then the property on the class. Don't know if you specifically need the x:Name reference which basically makes this a field in your control. Not necessary in this example unless you are trying to bind other controls in same display as a result of this field..
<TextBlock Height="40" Width="205" Margin="10,450,0,0"
HorizontalAlignment="Left" VerticalAlignment="Top"
DataContext="{Binding YourViewModelObjectName}"
Text="{Binding SomeThingYouWantToExpose}"
TextWrapping="Wrap" />
Hopefully these pieces within your scenario will make sense and move you forward in your project. Any additional clarification, let me know.
CLARIFICATION on the DATA CONTEXT BINDING
The way I have implemented in my apps, I would have a
MyView -- via the visual declaration... be it a window, grid, complex user control with many controls, whatever...
MyDataModel - a class that is used to query data from whatever data source, such as SQL-Server.
MyView_ViewModel - a custom class that has the INotifyPropertyChanged incorporated and where I expose different properties and other objects I want to expose / make available to the view
So, in the MyData_ViewModel, I would create the view and also create my view model. After creating the view, I would set the overall view's DataContext to the "MyView_ViewModel"
public class MyData_ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public void LoadingMyDataAndView()
{
// Controller I use to get whatever data I need from ex: SQL
_myDataModel = new MyDataModel();
// the view / window / control I want to present to users
_myView = new MyView();
// Now, set the data context on the view to THIS ENTIRE OBJECT.
// Now, anything on THIS class made as public can be have a binding
// directly to the control in the view. Since the DataContext is
// set here, I can bind to anything at this level or lower that is public.
_myView.DataContext = this;
}
private MyView _myView;
private MyDataModel _myDataModel;
// such as example exposed property
public string SomeThingYouWantToExpose {get; set; }
public void GettingSomeData()
{
var something = _myDataModel.GetSomeData();
// … doing something to get / prepare / whatever...
SomeThingYouWantToExpose = "some new label";
// Now raise which the view bound to this property will updated itself
RaisePropertyChanged( "SomeThingYouWantToExpose" );
}
}
Hopefully this EXAMPLE shows how the pieces tie together. The view would no longer need the individual DataContext set since the whole view is set, just needs to bind to the individual public property.
Assuming that the TextBlock is a child element of the UserControl, i.e. that
<TextBlock x:Name="StatusBlock" Text="{Binding StatusDisplay}" ... />
is declared in the UserControl's XAML, the Binding's RelativeSource should be set to the parent UserControl like this:
<TextBlock Text="{Binding StatusDisplay,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Since StatusDisplay is a property of a UserControl, i.e. a DependencyObject, it should be declared as a dependency property:
public partial class ReviewsControl : UserControl
{
public static readonly DependencyProperty StatusDisplayProperty =
DependencyProperty.Register(
nameof(StatusDisplay), typeof(string), typeof(ReviewsControl);
public string StatusDisplay
{
get { return (string)GetValue(StatusDisplayProperty); }
set { SetValue(StatusDisplayProperty, value); }
}
}

Wpf Binding Confusion

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.

Mvvm model ViewModel

It can be named MVVM model or not? Because View interacts with DataModel through
ViewModelData. Does View should interact only with ViewModelData? I did read somewhere that right MVVM model should implement INotify in ViewModel not in Model. Is it right?
namespace WpfApplication135
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModelData();
}
}
public class ViewModelData
{
public DataModel DM { get; set; }
public ViewModelData()
{
DM = new DataModel();
}
}
public class DataModel : INotifyPropertyChanged
{
public int label;
public int Label
{
get
{
return label;
}
set
{
label = value;
RaisePropertyChanged("Label");
}
}
public DataModel()
{
Action Update = new Action(Run);
IAsyncResult result = Update.BeginInvoke(null, null);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
public void Run()
{
int i=0;
while(true)
{
System.Threading.Thread.Sleep(2000);
Label = ++i;
}
}
}
}
xaml
<Grid>
<Label Content="{Binding DM.Label}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
</Grid>
The initial thought for MVVM was indeed that the View should not know (not depend on) the Model.
In practice this meant re-implementing all those Model properties in the ViewModel (see the light-yellow box in the picture below), a lot of work. And extra painful when your Model can easily implement INPC, for example when it is generated from a database schema. Entity Framework in database-first mode lets you inject the INPC code through the T4 templates.
The consensus quickly became that it is OK to forward a ViewModel.Model property and bind to that, just like your DM property. See the light-blue box in the picture.
The issue is visualized well in this picture, note the large number of arrows in the top right corner. They depict the various solutions for databinding and you can use any combination of them.
One point of MVVM is that your view should not know your data model and vice versa. That's why there is a ViewModel. Right now, your ViewModel is wasted code. It does nothing.
You may want to read up on MVVM a bit more, because explaining it is way too broad for the Q&A format here.

Implement the windows phone app in mvvm model

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.

Binding a View to a ViewModel, without an "empty" constructor

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.

Categories

Resources