How to set the model in a viewmodel (MVVM) - c#

I have an HelloWorldWPFApplication class with the following method:
public override void Run()
{
var app = new System.Windows.Application();
app.Run(new ApplicationShellView());
}
The ApplicationShellView has the following XAML:
<winbase:ApplicationShell x:Class="HelloWorldWPFApplication.View.ApplicationShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:winbase="clr-namespace:Framework.Presentation.Control.Window;assembly=Framework"
xmlns:vm="clr-namespace:HelloWorldWPFApplication.ViewModel"
Title="{Binding WindowTitle, Mode=OneWay}">
<Window.DataContext>
<vm:ApplicationShellViewModel />
</Window.DataContext>
</winbase:ApplicationShell>
If my ViewModel (ApplicationShellViewModel) has the following method, the window will have the title set to "Test":
public string WindowTitle
{
get
{
return "Test";
}
}
My problem is that I want to set the title based on properties within the HelloWorldWPFApplication class. I added the following to the HelloWorldWPFApplication's base class (which uses the INotifyPropertyChanged interface):
private WpfApplicationBase<WpfApplicationDataBase> applicationModel;
public WpfApplicationBase<WpfApplicationDataBase> Application
{
get { return this.applicationModel; }
set { this.Set<WpfApplicationBase<WpfApplicationDataBase>>(ref this.applicationModel, value); }
}
So effectively, I plan on reusing the existing HelloWorldWPFApplication object as the model (in MVVM).
I changed the WindowTitle property as follows:
public string WindowTitle
{
get
{
return String.Format("{0} {1}",
this.applicationModel.Data.FullName,
this.applicationModel.Data.ReleaseVersion).Trim();
}
}
Of course, at this stage my project creates a window without a title, as the application field has not been set. I don't want to create a new application object within the view model as one already exists. I want to use this existing object. What is the best way to achieve this?
I am very new to MVVM/WPF - and from my basic understanding of MVVM I don't want to put any code-behind in the view. I could have a static field set on a static class to the application object, and then assign this field in my view model (this works, but not sure having "global" variables is the best approach).
I have also tried creating the view model before showing the window, but have encountered a problem I have yet to solve. In this implementation my run method appears as follows:
public override void Run()
{
var window = new ApplicationShell(); // inherits from System.Windows.Window
var vm = new ApplicationShellViewModel();
vm.Application = this; // this line won't compile
window.DataContext = vm;
this.Data.WpfApplication.Run(window);
}
I get a compile error:
Error 1 Cannot implicitly convert type 'HelloWorldWPFApplication.Program.HelloWorldApplication' to 'Framework.Business.Logic.Program.Application.WpfApplicationBase'
I'm confused with the error as my HelloWorldWPFApplication class inherits from WpfApplicationBase:
public class HelloWorldApplication<T> : WpfApplicationBase<T>
where T : HelloWorldApplicationData
Additionally, HelloWorldApplicationData inherits from WpfApplicationDataBase.
I get the pretty much the same problem with the following implementation:
public override void Run()
{
var window = new ApplicationShell();
var vm = new ApplicationShellViewModel();
var app = new HelloWorldApplication<HelloWorldApplicationData>();
vm.Application = app; // Cannot implicitly convert type error again
window.DataContext = vm;
this.Data.WpfApplication.Run(window);
}
Exact error:
Error 1 Cannot implicitly convert type 'HelloWorldWPFApplication.Program.HelloWorldApplication' to 'Framework.Business.Logic.Program.Application.WpfApplicationBase'

First off, the "Application" class in WPF should be used for one thing, and one thing only: starting the program. It is not a model.
That said, I would just pass everything in sequence (this can apply to a proper model as well):
MyViewModel viewmodel = new MyViewModel(this);
var app = new System.Windows.Application();
app.Run(new ApplicationShellView(viewmodel));
Of course, remove the data context set from XAML. This does require modifying your code behind to accept the VM object and set it to the DataContext in your constructor, but thats a standard way of passing the VM to the View.
You could also use a Service Locator to find your model, or a number of other ways. Unfortunately, its hard to say which one is right, since your model is so weird.
As a complete aside; the title of your program is very much a part of the View, and probably doesn't need to be bound at all (your name is static, so making your application class the model isn't buying you anything).

Related

WPF: navigating between user controls resets viewmodel

I have an application where I am using usercontrols as "pages" of the application. I have a currentpage binding in ApplicationViewModel on my MainWindow, and I navigate between pages by changing the binding of currentpage with commands attached to a side menu control. I am using the MVVM pattern and all of my ViewModels derive from a BaseViewModel class.
The navigation works, but when I input text into a text box, then navigate away and then back, the user-input text is reset to it's default binding.
I've already tried updating the source trigger and setting the mode to TwoWay. My "page" has a viewmodel which it is bound to, and otherwise works.
On my page, in the parent grid all controls are in:
DataContext="{x:Static core:MyPageViewModel.Instance}">
And the control:
<TextBox Text="{Binding TextBoxTest, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
And in my viewmodel:
public static MyPageViewModel Instance => new MyPageViewModel();
public string TextBoxTest { get; set; } = "Change Me!";
I would like the value I enter to remain when I navigate away, and then return, to the page. I assume it's because when I navigate away from my usercontrol I'm unloading it, and when I navigate back I'm getting a new instance of the viewmodel. I just don't know how to keep a single one that remains in memory.
You should post more code, it's not clear from the pieces.
I can try to guess anyway the problem is here:
public static MyPageViewModel Instance => new MyPageViewModel();
This generates a new ViewModel every time it is accessed by your view, because it is the equivalent of writing:
public static MyPageViewModel Instance { get { return new MyPageViewModel(); } }
instead, you should write something like
public static MyPageViewModel Instance { get; } = new MyPageViewModel();
This way, the first time it is accessed, it returns the default value (new MyPageViewModel()), and now that static variable will always point to the same view model, instead of creating a new one.
Guido C. was exactly correct. I changed my viewmodel instance from the way I had it in my question to this:
public static MyPageViewModel Instance { get; } = new MyPageViewModel();
And it worked.

How can I unit test, that one viewmodel sends to another viewmodel proper string?

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.

Main structure for a MVVM WPF

In one hand I have my model which had to collect data from several files and build a oriented object database, and in another I have my interface in which I want to display data from my database . So I use binding but my ComboBox, etc.. remain empty. I have the feeling that my database is built then erased when the interface is launched. Here's the code of my Main defined in the App.xaml.cs:
public partial class App : Application
{
[STAThread]
public static void Main()
{
var application = new App();
application.InitializeComponent();
DirectoryInfo dir = new DirectoryInfo("P:\\....");
Model model = new Model(dir);
model.entityBox.initialize();
application.Run();
}
}
Code for binding in MainWindow.xaml:
<Window.DataContext>
<local:EntityBox></local:EntityBox>
</Window.DataContext>
<Grid>
<ComboBox x:Name="critereComboBox" ItemsSource="{Binding Criteres}"/>
In EntityBox.cs:
private List<string> _criteres = new List<string>();
public void initialize()
{
_criteres.Add("TXC");
_criteres.Add("TYC");
_criteres.Add("TZC");
_criteres.Add("MXC");
_criteres.Add("MYC");
_criteres.Add("MZC");
}
public List<string> Criteres
{
get{ return _criteres; }
}
You need to initialize combobox inside context class, because when you use XAML to bind your data context, the context class is created independently by XAML, the model creation in Main function has literally no effect to your Control.
You also need to implement INotifyPropertyChanged to your Model (ViewModel?) class. I am also suggest you to step into MVVM approach.
I suppose it's a one-way binding. A short answer is to use ObservableCollection
private ObservableCollection<string> _criteres = new ObservableCollection<string>();
As it will notify when you call Add, but you might need to call them in UIDispatcher.

Advice on Views navigation using Caliburn.Micro MVVM WPF

I'm new on Caliburn Micro and want some advice on which path to take to devolop my app interface and navigation between views.
My idea is to have a MainWindow which will contain a menu of buttons, each one related with a specific view. Each view will be stored in a separated WPF UserControl. The mainWindow will also contain a TabControl bound to an ObservableCollection of tabs on viewmodel. Everytime a button on menu is clicked, I want to add a new tab with a ContentPresenter inside that will dynamically load a view and its corresponding viewmodel.
So my questions:
1) Should I use a Screen Collection here?
2) Should the UserControl implement Screen interface?
3) How do I tell MainWindow ViewModel which view to load on the new added tab maintaining viewmodels decoupled?
Thanks to everyone in advance.
UPDATE
After a lot of reading and some help of the community I managed to resolve this. This is the resultant AppViewModel:
class AppViewModel : Conductor<IScreen>.Collection.OneActive
{
public void OpenTab(Type TipoVista)
{
bool bFound = false;
Screen myScreen = (Screen)Activator.CreateInstance(TipoVista as Type);
myScreen.DisplayName = myScreen.ToString();
foreach(Screen miItem in Items)
{
if (miItem.ToString() == myScreen.ToString())
{
bFound = true;
ActivateItem(miItem);
}
}
if (!bFound) ActivateItem(myScreen);
}
public ObservableCollection<MenuItem> myMenu { get; set; }
public ObservableCollection<LinksItem> myDirectLinks { get; set; }
public ICommand OpenTabCommand
{
get
{
return new RelayCommand(param => this.OpenTab((Type) param), null);
}
}
public AppViewModel()
{
OpenTab(typeof(ClientsViewModel));
MenuModel menu = new MenuModel();
myMenu = menu.getMenu();
myDirectLinks = menu.getLinks();
}
public void CloseTab(Screen param)
{
DeactivateItem(param, true);
}
}
I have to keep the ICommand from OpenTabCommand because the name convention of Caliburn.micro doesn't seems to work inside DataTemplate. Hope it could help someone else. Thanks to all
I've done something very similar using Caliburn.Micro, and based it on the SimpleMDI example included with the examples, with a few tweaks to fit my needs.
Much like in the example, I had a main ShellViewModel:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
}
with a corresponding ShellView containing a TabControl - <TabControl x:Name="Items">, binding it to the Items property of the the Conductor.
In this particular case, I also had a ContextMenu on my ShellView, bound (using the Caliburn.Micro conventions), to a series of commands which instantiated and Activated various other ViewModels (usually with a corresponding UserControl, using the ActivateItem method on the Conductor.
public class YourViewModel: Conductor<IScreen>.Collection.OneActive
{
// ...
public void OpenItemBrowser()
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
In that case, I didn't require the ViewModels to be created with any particular dependency, or from any other locations in the program.
At other times, when I've needed to trigger ViewModel from elsewhere in the application, I've used the Caliburn.Micro EventAggregator to publish custom events (e.g. OpenNewBrowser), which can be handled by classes implementing the corresponding interface (e.g. IHandle<OpenNewBrowser>), so your main ViewModel could have a simple Handle method responsible for opening the required View:
public class YourViewModel: Conductor<IScreen>.Collection.OneActive, IHandle<OpenNewBrowser>
{
// ...
public void Handle(OpenNewBrowser myEvent)
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
This section of the documentation will probably be useful, especially the Simple MDI section.
Additional code I mentioned in the comments:
I sometimes use a generic method along these lines ensure that if I have an existing instance of a screen of a particular type, switch to it, or create a new instance if not.
public void ActivateOrOpen<T>() where T : Screen
{
var currentItem = this.Items.FirstOrDefault(x => x.GetType() == typeof(T));
if (currentItem != null)
{
ActivateItem(currentItem);
}
else
{
ActivateItem(Activator.CreateInstance<T>());
}
}
Used like:
public void OpenBrowser()
{
this.ActivateOrOpen<BrowserViewModel>();
}

Lack of knowledge to initializing viewmodel

Okay. So what I need to do is to initialize a ViewModel using a constructor. The problem is I can't create the constructor due lack of knowledge. I'm new to MVVM (or c# in general for that matter) and had to get some help to implement this code:
public class ViewModel
{
private static ViewModel instance = new ViewModel();
public static ViewModel Instance
{
get
{
return instance;
}
}
}
However, I fail to create a constructor to place this code.
DataContext = ViewModel.Instance
It is meant to go into two different pages to pass a value between TextBoxes.
I'm also confused as to whether I should put the ViewModel in both the main window and the page or in just one of the two.
So, anyone can help?
Follow this pattern:
This part is how your model classes should look like,
Even if you use entity framework to create your model they inherit INPC.. so all good.
public class Model_A : INotifyPropertyChanged
{
// list of properties...
public string FirstName {get; set;}
public string LastName {get; set;}
// etc...
}
each view model is a subset of information to be viewed, so you can have many view models for the same model class, notice that in case your make the call to the parameter-less c-tor you get auto instance of a mock model to be used in the view model.
public class ViewModel_A1 : INotifyPropertyChanged
{
public Model_A instance;
public ViewModel()
{
instance = new instance
{ //your mock value for the properties..
FirstName = "Offer",
LastName = "Somthing"
};
}
public ViewModel(Model_A instance)
{
this.instance = instance;
}
}
And this is for your view, if you view in the ide designer you will have a mock view model to show.
public class View_For_ViewModelA1
{
public View_For_ViewModel_A1()
{
//this is the generated constructor already implemented by the ide, just add to it:
DataContext = new ViewModel_A1();
}
public View_For_ViewModel_A1(ViewModel_A1 vm)
{
DataContext = vm;
}
}
XAML Side:
<Window x:Class="WpfApplication1.View_For_ViewModel_A1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ViewModel="clr-namespace:WpfApplication1"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"
d:DataContext="{d:DesignInstance ViewModel:ViewModel_A1, IsDesignTimeCreatable=True}"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
</Grid>
</Window>
In a more advanced scenario you would want to have a single view model class to relate to several model classes.. but you always should set a view to bind to a single view model.
if you need to kung-fu with your code - make sure you do that in your view model layer.
(i.e. creating a view-model that have several instances of different model types)
Note: This is not the complete pattern of mvvm.. in the complete pattern you can expose command which relate to methods in your model via your view-model and bind-able to your view as well.
Good luck :)
I basically follow this pattern:
public class ViewModelWrappers
{
static MemberViewModel _memberViewModel;
public static MemberViewModel MemberViewModel
{
get
{
if (_memberViewModel == null)
_memberViewModel = new MemberViewModel(Application.Current.Resources["UserName"].ToString());
return _memberViewModel;
}
}
...
}
To bind this to a page is:
DataContext = ViewModelWrappers.MemberViewModel;
And if I'm using more than 1 ViewModel on the page I just bind to the wrapper.
DataContext = ViewModelWrappers;
If you or anybody else, who's new to the MVVM, gets stuck here, for example at the "INotifyPropertyChanged could not be found". I recommend trying some example-MVVM's or tutorials.
Some I found useful:
http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial
https://www.youtube.com/watch?v=EpGvqVtSYjs&index=1&list=PL356CA0B2C8E7548D

Categories

Resources