I'm not very proficient with WPF. What I want to do is bind to a property from my view and then set that value with something. I've got that part working without trouble, but I have a feeling I'm not properly using the MVVM pattern here. I have my property in the ViewModel, bound to the View however I can't seem to get the Model part working as I intend as the method where the property gets its value from is currently also in the ViewModel.
Here's what I currently have:
public class MainViewModel : ViewModelBase
{
private Awesome _model; //this is my model
private string _score;
public string Score
{
get { return GetScore(); }
set
{
_score = value;
}
}
public string GetScore()
{
try
{
using (StreamReader sr = new StreamReader(#"C:\somepath"))
{
String line = sr.ReadToEnd();
return line;
}
}
catch (Exception)
{
MessageBox.Show("File could not be found! :(");
throw;
}
}
}
This works fine, but everything is in the ViewModel right now. As far as I understand, GetScore() should be in the Model, but then I'm not sure how to set the property with it. What am I missing here?
The GetScore()-Method should not be in the Model. The model is the data-layer so there are only data-objects with there properties. Methods and other stuff are coordinated by the ViewModel. So you can let your GetScore-Method in your ViewModel or move it to another class and call it from your ViewModel.
By the way: Your property is a little bit weird. becaus in your setter you're setting a backend-field which will never be used again. Are you sure that this is what you want? You should also not always read a file in the getter.
Maybe you want to do something like:
public string Score
{
get { return _score ?? (_score = GetScore()); }
}
So you only read the file once and keep the value saved in _score.
Your GetScore() belongs to the ViewModel, as it is the data layer. (Of course you could move it to another class, but it's not wrog from my POV in the ViewModel, see it as an extended getter ;))
But
You should make GetScore private, because you have a property for it
You should not influence the UI from the ViewModel, so I would
advise you to not open the MessageBox from the ViewModel.
Use return this.GetScore()
Related
In a view model's constructor I have a command declaration that calls a method:
OpenGroupCommand = new DelegateCommand(OnOpenGroupExecute);
And the method looks like:
private void OnOpenGroupExecute(object obj)
{
string groupName = (string)obj;
Application.Current.MainPage.Navigation.PushAsync(new GroupPage(groupName));
}
How can I test, that groupName is passed to another view model correctly? In another view model groupName parameter is sent to GroupName property on VM instance:
public class GroupPageViewModel : ViewModelBase, IGroupPageViewModel
{
private string _groupName;
public GroupPageViewModel(string groupName)
{
LoadGroupName(groupName);
}
public void LoadGroupName(string groupName)
{
GroupName = groupName;
}
public string GroupName
{
get
{
return _groupName;
}
set
{
_groupName = value;
OnPropertyChanged();
}
}
}
On debug all works fine, but how can I unit test it? Where can I read a bit about testing and mocking stuff like this, even with Moq framework?
I believe your question is actually about how to test navigation between pages.
In the implementation of method OnOpenGroupExecute, because you are using Xamarin forms stuff to implement the navigation, you have to refer Xamarin Forms assemblies in your test project which makes the unit test depend on Xamarin Forms.
As suggested in this document https://learn.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/ , try to create an interface for navigation and navigate with viewmodel (more details on https://github.com/dotnet-architecture/eShopOnContainers)
And in your unit test project, implement a fake navigation service class like below and inject into the DI container:
public class FakeNavigationService : INavigationService //this interface is from MS eShopOnContainer project
{
private List<ViewModelBase> _viewModels = new List<ViewModel>();
public Task NavigateToAsync<TViewModel>() where TViewModel : ViewModelBase {
//create viewModel object from DI container
//var viewModel = ......
_viewModels.Add(viewModel);
}
public ViewModelBase CurrentPageViewModel {
get {
if (_viewModels.Count() < 1) {
return null;
}
return _viewModels[_viewModels.Count() - 1];
}
}
}
This is just a suggestion. If you have implemented most of features in your app, it takes time to change navigate-with-page to navigate-with-viewmodel.
Well, let's see what you have:
you have some code in a private method, unless you make that public you won't be able to test it directly, because you can't call it. I am not considering here any tricks that allow you to call private methods.
what does that method do? It is not clear at all, it receives an object, we don't know what's in it. You're converting it to string, but what if it is not a string? Can you convert that object to a string? who knows.
So we have a method, that we don't know what it does, we don't know what it receives as parameters, we can't call it directly, but we want to test it. This is not a good position to be in.
Step back a bit and ask yourself, what are you really trying to test?
You said : How can I test, that groupName is passed to another view model correctly?
what does "correctly" mean? You need to define what it means for that string to be correct. This will give a test scenario you can work with.
I expect to receive an object, which looks like A and I want to convert it to a string which looks like B. Forget about viewmodels for now, that's just unimportant noise.
You can change the method into a public one and you can test that for different types of input data, you're getting the right result. This is literally, working with an object and extract some stuff from it. When that method is correct, you can guarantee that the viewmodel will receive the right input and that is good enough from a unit testing point of view.
You can of course add more tests for various inputs, you can test for correct failure conditions etc.
I have TaskViewModel class with a lot of different properties. The simplified piece of code is below:
internal class TaskViewModel : ViewModelBase
{
private TaskModel _model;
public long Id
{
get { return _model.Id; }
set
{
_model.Id = value;
RaisePropertyChanged("Id");
}
}
public string Title
{
get { return _model.Title; }
set
{
_model.Title = value;
RaisePropertyChanged("Title");
}
}
public DateTime? Date
{
get { return _model.Date; }
set
{
_model.Date = value;
RaisePropertyChanged("Date";);
}
}
private RelayCommand _updateCommand;
public RelayCommand UpdateCommand
{
get
{
return _updateCommand
?? (_updateCommand = new RelayCommand(
() =>
{
// somehow update _model
}));
}
}
}
And I have TaskView where I could edit the instance of TaskViewModel. Also I have a few validation rules, for example, if Titleis empty I can't update model and have to reestablish previous Title. That's why I cannot use "{Binding Mode=TwoWay}.
The question is what is the best way to update view model.
I have two ways to do it:
Add property of TaskViewModel type to the instance and bind all properties of this to the view and than using ICommand for updating properties in main instance if all validations rules are performing. But in this case I need to keep whole copy of object.
Using "{Binding Mode=TwoWay, UpdateSourceTrigger=Explicit}" for necessary properties and than in code-behind using event handlers call binding.UpdateSource(). But in that case I have to implement validation logic in code-behind too, which looks like a bad way in mvvm-approach.
May be you should recommend the best way for this task.
Thanks in advance.
UPDATE:
For example of the typical validation case, Title mustn't be empty. If I changed the Title property from "Buy milk" to "Buy mi" it would be valid, but I don't want to update my model after every change of every property and save it to a storage. So I have to implement SaveCommand which will update the model. But also I need to have a possibility to rollback all the changes, so I can't change current view model properties directly by using Mode=TwoWay binding.
So the problem is how to update all changed properties on demand if they are valid?
Recently I've discovered a strange behavior when I assign a ViewModel which comes back from a WCF service call to a DataContext in a UserControl element. When I first assign the ViewModel everything is fine. All XAML bindings are showing the correct and expected values if available.
But let's say I've saved changes in the ViewModel via another service call and get back an updated version of the ViewModel and now like to reassign the new ViewModel to the DataContext sometimes the DataContext and the ViewModel (which has been assigned) are losing attribute values (they are null after assigning).
Let me show you a simplified example to demonstrate
namespace MyProject.ViewModels
{
[DataContract]
public class MyViewModel : ViewModelBase //ViewModelBase implements INotifyPropertyChanged
{
#region Properties
private MyViewModelListItem _myViewModelListItem; //another nested ViewModel
[DataMember]
public MyViewModelListItem myViewModelListItem { get { return _myViewModelListItem; } set { _myViewModelListItem = value; RaisePropertyChanged(() => myViewModelListItem); } }
private string _aStringVariable; //just a simple string
[DataMember]
public string aStringVariable { get { return _aStringVariable; } set { _aStringVariable = value; RaisePropertyChanged(() => aStringVariable); } }
private MyEnum _myEnum; //an enumeration
[DataMember]
public MyEnum myEnum { get { return _myEnum; } set { _myEnum = value;RaisePropertyChanged(() => myEnum); } }
#endregion
}
}
Here is an extract of a simplified code behind of an xaml UserControl
//some other code parts not related to the problem
// get the VM
MyViewModel vm = service.getMyViewModel();
if(vm != null){
//assign the datacontext
userControl.Grid.DataContext = vm; //everything is fine. all values are set as expected. Databinding works like a charm
}
Now let's imagine some changes in the vm via user input and data binding. The viewmodel is saved (calling a WCF service method) and after that I call the service again to get an updated version of the VM:
// get the VM again
vm = service.getMyViewModel();
if(vm != null){
//assign the datacontext
userControl.Grid.DataContext = vm;
}
When I set a breakpoint before the assignment of the new ViewModel all values of any attribute of the ViewModel are set (because the service filled them with values)
But then right after the assignment some of the attribute values are null. I cannot say which one. I'm using a lot of different ViewModels in my project and in this example maybe the string is not null and the nested ViewModel also. But the enumeration is suddenly null. And at another place in the project maybe something else is suddenly null.
I assume that the DataContext variable has a setter method which is doing something strange (or I get it totally wrong).
Does anybody know what's happening here? And in addition what could be a "good" way of preventing this (maybe reassigning is the wrong approach).
For now I just set the DataContext to null before reassigning the ViewModel but it seems not the best way, does it?
vm = service.getMyViewModel();
if(vm != null){
//assign the datacontext
userControl.Grid.DataContext = null;
userControl.Grid.DataContext = vm;
}
Edit:
just to clarify: No viewmodels are saved into a database. They are properly converted to corresponding entities and then these are saved.
changed this to userControl.Grid to be more specific in the examples
My view has a control inside of it that is capable of generating an image that is saved at a path I can specify (along with some other data). I don't own this control and can't get the interface to generate an image changed. I'm not quite sure how to handle this with MVVM.
The quick and dirty way would be for my view to define a method that takes the desired path, and have the viewmodel call that method.
View:
public void GenerateImage(string path) {
_control.SaveImage(path);
}
ViewModel:
(actually this is the body of a Command) {
var path = GeneratePath();
_view.GenerateImage(path);
...
}
I don't like this because I get the feeling that viewmodels are not meant to directly reference the view, instead they represent the view's state and communicate via property bindings. It works, and I'm doing this while waiting on answers. I'd like to find a way around it.
I could get cute and have the view pass a reference to the control to a Command (I'm in Xamarin Forms) via the Execute() parameter, and have the command cast and make the call. This seems like lipstick on a pig since it makes the viewmodel still aware of a particular class inside the view. But in writing this paragraph I think I came up with a solution I like.
I /could/ create:
interface IGenerateImage {
void GenerateImage(string path);
}
The obvious implementation would delegate the call to an encapsulated control. I feel like if the view passes an IGenerateImage then I'm not creating the viewmodel-to-view dependency that I'm trying to avoid, and I can test the logic without needing to instantiate expensive UI classes.
I like that answer, but I'm pretty sure there's an obvious solution I'm missing. Is there some other useful pattern for handling it? Or is it not a big deal if the viewmodel references the view?
You never want the View Model to know anything about the View.
It's a little unclear what you can and can't change in your post, so I'm assuming you can change the V/VM, but not _control.
The easiest way is to create an event in the View Model that the View can subscribe to.
Something like this:
View:
// Constructor
public View()
{
// However you're setting your VM, i.e. DI or new-ing up the VM
// Subscribe to the event
vm.ImageGeneratedEvent += this.OnImageGeneratedEvent;
}
private void OnImageGeneratedEvent(object sender, ImageGeneratedEventArgs args)
{
// Call your SaveImage in the event handler
_control.SaveImage(args.Path);
}
View Model:
public event EventHandler<ImageGeneratedEventArgs> ImageGeneratedEvent;
// Command body
{
var path = GeneratePath();
// Send event to the View
this.NotifyImageGeneratedEvent(path)
}
private void NotifyImageGeneratedEvent(string path)
{
ImageGeneratedEventArgs args = new ImageGeneratedEventArgs(path);
if (this.ImageGeneratedEvent!= null)
{
this.ImageGeneratedEvent(this, args);
}
}
ImageGeneratedEventArgs:
public class ImageGeneratedEventArgs : EventArgs
{
public string Path { get; set; }
public ImageGeneratedEventArgs(string path)
{
this.Path = path;
}
}
I am trying to come up with a good way of implementing the MVVM pattern using Entity-Framework where my entities are my models. My DataContext is my viewmodel. This is a small reproduction of the problem.
View
<TextBox Text="{Binding MyText}" />
ViewModel:
I have the requirement of needing to navigate record by record from my DB. When a button is clicked in the View a command is sent to the Viewmodel that executes nextRecord(). EF does its magic and _myObject is the next row/record from the database
public class myViewModel: INotifyPropertyChanged
{
private MyEntityObject _myObject;
public string MyText
{
get { return _myObject.MyText; }
set
{
if (_myObject.MyText != value)
{
_myObject.MyText = value;
OnPropertyChanged("MyText");
}
}
}
private void _nextRecord()
{
_myObject = myEntitiesContext.NextRecord() //pseudocode
}
}
Autogenerated Entity Model
public partial class MyEntityObject
{
public string MyText { get; set; }
}
Since the View has no knowledge of _myObject changing, it doesn't update when _myObject changes. A few approaches I have thought of.
I haven't tested wrapping my entities in a INotifyPropertyChanged wrapper class but am wary to do this as I have a lot of entity objects.
I could call OnPropertyChanged("...") for all properties, but some of my entities have a lot of properties to them, which would be ugly. Possible to use reflection to make it cleaner, but I may have properties that aren't databound.
I might be able to defer this to the UI, somehow refreshing the bindings when I click "Next Record", but this breaks MVVM and looks dirty
How can I get the UI to recognize changes from _myObject?
As I've mentioned in the comments, calling OnPropertyChanged("") or OnPropertyChanged(null) invalidates all properties and is equivalent to calling OnPropertyChanged for each and every property. This behavior is also documented here:
The PropertyChanged event can indicate all properties on the object
have changed by using either null or String.Empty as the property name
in the PropertyChangedEventArgs.
This means that you can simply add a call to OnPropertyChanged("") when you update your object to force WPF to reevaluate all bindings to your view model:
private void _nextRecord()
{
_myObject = myEntitiesContext.NextRecord();
OnPropertyChanged("");
}
That being said, I'd still go with #Anand's solution (+1). There's an ongoing debate on whether it's OK or not for the viewmodel to expose the model as a property, and I tend to go with exposing it until you need to introduce some view model specific logic. Most of the time you won't have to and it's not worth the trouble of wrapping model properties.
The problem with your code is that when _myObject changes the MyText property changed event is not fired. A work around would be to create a new property to hold you entity
and make this property as your Grids DataContext in your view as shown below. Now when this line is executed MyObject = myEntitiesObject.NextRecord() your view will be notified about the change.
public class myViewModel : INotifyPropertyChanged
{
private MyEntityObject _myObject;
public MyEntityObject MyObject
{
get { return _myObject; }
set {
if (_myObject != value)
{
_myObject = value;
OnPropertyChanged("MyObject");
}
}
}
private void _nextRecord()
{
MyObject = myEntitiesObject.NextRecord() //pseudocode
}
}
View:
<Grid DataContext="{Binding MyObject}">
<TextBlock Text="{Binding MyText}"/>
</Grid>
An extremely simple but not very elegant solution that I believe would meet needs: upon switching records, set the DataContext to null, then back to the ViewModel.
However, there are arguably more elegant alternatives that require more work to meet all requirements. See Anand's answer for an improvement upon this.
The tag in View should have the mode and UpdateSourceTrigger attribute set with values.