MvvmLightLibsStd10 and UWP - c#

How can you create a binding between a ViewModel and a View?
In the past there was a Locater created in App.xaml and then on the view you had this:
DataContext="{Binding MainViewModel, Source={StaticResource ViewModelLLocator}}"
I can't even click in the Properties of the View and then create DataContext binding.

In recent versions of MVVM light they changed how ViewModelLocator works due to it taking a dependency on Microsoft.Practices.ServiceLocation and the former not being .NET Standard compliant. It now should use GalaSoft.MvvmLight.Ioc to locate the ViewModel using SimpleIoc.
Here's an example how I used it in a recent UWP project.
In App.xaml
private ViewModels.ViewModelLocator Locator => Application.Current.Resources["Locator"] as ViewModels.ViewModelLocator;
In MainPage.xaml
DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">
In MainPage.cs
private MainViewModel ViewModel
{
get { return DataContext as MainViewModel; }
}
In ViewModelLocator.cs
namespace YourNamespace.ViewModels
{
public class ViewModelLocator
{
public ViewModelLocator()
{
Register<MainViewModel, MainPage>();
}
public MainViewModel MainViewModel => SimpleIoc.Default.GetInstance<MainViewModel>();
public void Register<VM, V>()
where VM : class
{
SimpleIoc.Default.Register<VM>();
NavigationService.Configure(typeof(VM).FullName, typeof(V));
}
}
}

Ok I found it out:
You need to add this in App.xaml:
private static ViewModelLocator _locator;
public static ViewModelLocator Locator => _locator ?? (_locator = new ViewModelLocator());
And then in the View.xaml:
this.DataContext = App.Locator.MainViewModel;

Related

WPF TabControl with MVVM using Dependency Injection

I'm very new to WPF but quite experienced with .NET and C#. I am trying to create (what I though would be) a fairly simple CRUD admin desktop application for a website I plan on building.
WPF seems to be way more complicated than I expected it to be and after lots of Googling I've basically realised that everyone uses the MVVM pattern - fine. Now, with my existing .NET experience, I know I definitely want to to be using dependency injection. I've discovered that everything seems to be done within the ViewModel in WPF, including all the services and everything - fine again.
Now, onto my problem. I have set up a basic tab control and I'm binding the tab values to an enum using Enum.GetValues(). I want the view to change when I select a tab and the view will depend on which tab is selected. The problem is, I can't seem to get the view to show - it just shows a blank screen. The view is a custom UserControl I've created and defined as a resource and contains a grid and a bunch of buttons and stuff. I've omitted this from below as it doesn't seem relevant.
My MainWindow.xaml is pretty simple and looks like this:
<Window x:Class="Stc.Admin.MainWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="clr-namespace:Stc.Admin.ViewModels"
xmlns:views="clr-namespace:Stc.Admin.Views"
xmlns:local="clr-namespace:Stc.Admin"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding CurrentTab}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:GamesViewModel}">
<views:Games />
</DataTemplate>
</TabControl.Resources>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding DataContext.CurrentViewModel, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Here's my MainViewModel.cs:
public class MainViewModel
{
private readonly IViewModelFactory<GamesViewModel> _gamesViewModelFactory;
private ViewType _currentTab;
public ViewType CurrentTab
{
get
{
return _currentTab;
}
set
{
_currentTab = value;
ChangeView(_currentTab);
}
}
public ObservableCollection<ViewType> Tabs { get; }
public ViewModelBase CurrentViewModel { get; set; }
public MainViewModel(IViewModelFactory<GamesViewModel> gamesViewModelFactory)
{
_gamesViewModelFactory = gamesViewModelFactory;
Tabs = new ObservableCollection<ViewType>(Enum.GetValues(typeof(ViewType)).Cast<ViewType>().ToArray());
}
private void ChangeView(ViewType viewType)
{
switch (viewType)
{
case ViewType.Games:
CurrentViewModel = _gamesViewModelFactory.CreateViewModel();
break;
case ViewType.Listings:
break;
case ViewType.Users:
break;
case ViewType.Languages:
break;
case ViewType.Currencies:
break;
default:
break;
}
}
}
public enum ViewType
{
Games,
Listings,
Users,
Languages,
Currencies
}
GamesViewModel has service dependencies so it needs to be created using the factory.
And my DI setup in App.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
IServiceProvider serviceProvider = this.createServiceProvider();
Window window = new MainWindow();
window.DataContext = serviceProvider.GetRequiredService<MainViewModel>();
window.Show();
base.OnStartup(e);
}
private IServiceProvider createServiceProvider()
{
IServiceCollection services = new ServiceCollection();
services.AddDbContext<StcContext>(options =>
options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Stc;Integrated Security=True"));
services.AddSingleton<ICrudService<Game>, CrudService<Game>>();
services.AddSingleton<IViewModelFactory<GamesViewModel>, GamesViewModelFactory>();
services.AddScoped<MainViewModel>();
return services.BuildServiceProvider();
}
}
I have sorted this issue now. Being new to WPF, I didn't realise that I have to use INotifyPropertyChanged to get the UI to update after changing a property value on my ViewModel. I'd seen this used in a lot of the articles and tutorials I was seeing but didn't really understand what it was or how to apply it to my application.
The change I made was to implement this interface on my base ViewModel like so:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I then change my MainViewModel to inherit from the base class and changed the setter of the CurrentTab property to call OnPropertyChanged (with the name of the property) after I've changed the view/viewmodel property:
private ViewType _currentTab;
public ViewType CurrentTab
{
get
{
return _currentTab;
}
set
{
_currentTab = value;
ChangeView(_currentTab);
OnPropertyChanged(nameof(CurrentViewModel));
}
}
I believe this is telling the UI that something has changed and it needs to redraw itself. Correct me if I'm wrong or if that's an oversimplification.

WPF MVVM Child ViewModel properties not updating view

So I have a WPF app utilizing the MVVM pattern which uses the MVVM Light library. For INotifyPropertyChanged I am using the Fody.Weavers library which is working well so far.
I have a MainWindow.xaml which has a ContentControl, its Content property is bound to a property on its view model for navigation. This works well also.
MainWindow.xaml:
<ContentControl Content="{Binding SelectedViewModel}"></ContentControl>
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
// Note that Fody library handles INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public object SelectedViewModel { get; set; }
public MainViewModel()
{
SelectedViewModel = new HomeViewModel();
}
public RelayCommand<PasswordBox> AdminLoginCommand => new RelayCommand<PasswordBox>(AdminLogin);
private void AdminLogin(PasswordBox passwordBox)
{
// Login Logic...
// Does not work
SelectedViewModel = new HomeViewModel();
// Does not work either
if (SelectedViewModel is HomeViewModel)
{
((HomeViewModel)SelectedViewModel).CheckAccess();
}
// Does not work either
if (SelectedViewModel is HomeViewModel)
{
((HomeViewModel)SelectedViewModel).CanAccessTestButton = true;
}
}
}
However when I call the CheckAccess method on the SelectedViewModel, directly change the CanAccessTestButton property from MainViewModel, or set SelectedViewModel with a new HomeViewModel from MainViewModels AdminLogin method, they get updated as I see when I step through the code, but the binding does not update the UI. I have tried these methods independently.
I think Home.xaml is not picking up on the property when changed from the parent view model. When the constructor of HomeViewModel is initialized on first load it binds correctly to whatever CanAccessTestButton is set to, any other calls from MainViewModel do not seem to update the view.
Funnily enough, when I try to change the property from within the HomeViewModel using a RelayCommand bound to a another button in Home.xaml, it works fine.
How can I get it to work from the parent?
Home.xaml:
<Button Content="Test" IsEnabled="{Binding CanAccessTestButton}"/>
HomeViewModel.cs:
public class HomeViewModel : ViewModelBase
{
// Note that Fody library handles INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public bool CanAccessTestButton { get; set; }
public HomeViewModel()
{
OtherButtonCommand = new RelayCommand(OtherButtonClick);
CheckAccess();
}
public RelayCommand OtherButtonCommand { get; set; }
private void OtherButtonClick()
{
// WORKS!!!
CheckAccess()
}
public void CheckAccess()
{
CanAccessTestButton = AppContext.Instance.LoggedInUserHasAccess();
}
}
Edit:
MVVM Light has a ViewModelLocator.cs class which you need to declare each ViewModel in:
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<HomeViewModel>();
}
public MainViewModel Main => ServiceLocator.Current.GetInstance<MainViewModel>();
public HomeViewModel Home => ServiceLocator.Current.GetInstance<HomeViewModel>();
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
Then in each View, you reference the view model you want to bind to in (markup simplified for brevity)
MainWindow.xaml:
<Window DataContext="{Binding Main, Source={StaticResource Locator}}">
Home.xaml:
<UserControl DataContext="{Binding Home, Source={StaticResource Locator}}">
On startup, HomeViewModel constructor gets called twice, first from ViewModelLocator.cs, then again from MainViewModel.cs. Maybe the ViewModelLocator has the reference to the view that I see on screen. Any ideas on how to accomplish what I wish to achieve?
I noticed the HomeViewModel constructor gets called once from ViewModelLocator, then again within MainViewModel when the Content binding is set
That's your problem. You are creating another instance of the view model and bind to this one.
Remove the DataContext attribute from the UserControl in Home.xaml:
<UserControl DataContext="{Binding Home, Source={StaticResource Locator}}">
You want to bind to the HomeViewModel instance that you create yourself, not the one that the view model locator creates for you. So you don't need any view model locator here.

Navigate through UserControl with MVVM LIGHT (WPF)

First of all I apologize for my poor english which is not my first language.
I'm new in MVVM so my question is probably a very newbie one ;)
I'm encountering some issue with switching View in a C# Application using WPF and MVVM LIGHT. I've read a lot of articles but i still can't figured out how to do it in a clean way.
So here is my question: What is the best way to achieve the navigation between UserControl contained in a MainWindows, assuming that:
I've a ViewModel for each UserControl and one for the Main Windows.
The buttons for switching between usercontrols are contained into UserControl itself
I've a ViewModelLocator
I need to sometimes Destroy/re-create a userControl's ViewModel
I want to respect the MVVM Pattern.
I want to keep it simple
Since nobody answers to my question, this is what I finally did.
It might not be the best way but at least it works well.
I hope it'll helps some newbies like me who are struggling learning this pattern:
Put a CurrentViewModel Object in the MainViewModel:
public class MainViewModel : ViewModelBase,IMainViewModel
{
/* Other piece of code */
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
_currentViewModel = value;
RaisePropertyChanged(() => CurrentViewModel);
}
}
}
Obviously bind this to the Mainview (Just the relevant code):
<UserControl Content="{Binding Path=CurrentViewModel}"/>
Put the DataTemplate in the App.xaml:
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
<DataTemplate DataType="{x:Type localViewModel:HomeViewModel }">
<localView:AccueilView/>
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModel:ErrorViewModel }">
<localView:ErrorView/>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
Register the ViewModel with Simple IOC in the ViewModelLocator:
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IHomeViewModel, DesignHomeViewModel>();
}
else
{
SimpleIoc.Default.Register<IHomeViewModel, HomeViewModel>();
}
Set the getter of all the ViewModel in the ViewModelLocator to Static (just one for the exemple)
public static IHomeViewModel Home
{
get{return ServiceLocator.Current.GetInstance<IHomeViewModel>();}
}
Since it's static you can access the ViewModel you want from the MainViewModel:
public class MainViewModel : ViewModelBase,IMainViewModel
{
public ViewModelBase HomeVM
{
get
{
return (ViewModelBase)ViewModelLocator.Home;
}
}
}
Provide the ability to unregister the ViewModel and recreates it:
public static void CleanUpHome()
{
SimpleIoc.Default.Unregister<HomeViewModel>();
SimpleIoc.Default.Register<IHomeViewModel, HomeViewModel>();
}
The "child" View Model communicates with the MainViewModel through messages:
public class ErrorViewModel : ViewModelBase, IErrorViewModel
{
/*Other piece of code */
public void HomeReturn()
{
var msg = new ChangeView(ChangeView.EnumView.Home);
Messenger.Default.Send<ChangeView>(msg);
ViewModelLocator.CleanUpErrors();
}
}
The MainViewModel Register to the message and processes it:
public class MainViewModel : ViewModelBase,IMainViewModel
{
public MainViewModel()
{
Messenger.Default.Register<ChangeView>(this, (action) => ReceiveMessage(action));
CurrentViewModel = HomeVM;
}
private void ReceiveMessage(ChangeView viewName)
{
switch (viewName.switchView)
{
case ChangeView.EnumView.Home:
CurrentViewModel = HomeVM;
break;
case ChangeView.EnumView.Error:
CurrentViewModel = ErrorVM;
break;
}
Messenger.Default.Unregister<ChangeView>(this, (action) => ReceiveMessage(action));
}
That's all.

WPF Mvvm navigation with parameters

Following this tutorial (among others) and reading questions asked here I've constructed a navigation mechanism that will allow me to pass parameters between my ViewModels:
Object base - every view model inherits from it:
public abstract class ObjectBase : INotifyPropertyChanged
{
//INotifyPropertyChanged members
...
//Navigation handling
public abstract ObjectBase BackLocation { get; }
public abstract event Action<ObjectBase> NavigateTo;
public abstract string ViewHeader { get; }
}
MainViewModel - in charge of navigation:
public class MainViewModel : ObjectBase
{
private ObjectBase _selectedView;
private CommandBase _backCommand;
public MainViewModel()
{
SelectedView = new FirstViewModel();
}
public ObjectBase SelectedView
{
get { return _selectedView; }
set
{
SetProperty(ref _selectedView, value);
//register to the navigation event of the new view
SelectedView.NavigateTo += (target)=> { SelectedView = target; };
}
}
//This command is bound to a Back button on the main view
public CommandBase BackCommand
{
get { return _backCommand ?? (_backCommand = new CommandBase(Back)); }
}
private void Back(object obj)
{
if (SelectedView.BackLocation != null)
{
SelectedView = SelectedView.BackLocation;
}
else
{
Application.Current.Shutdown();
}
}
}
And the main view:
<Window ...
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type vm:FirstViewModel}">
<views:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SecondViewModel}">
<views:SecondView/>
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding SelectedView}"/>
</Window>
My problem is: If I set the DataTemplates in the main view like the above it makes each view aware of it's DataContext so if I want to add the DataContext explicitly to a view in order to use intellisense like this:
<UserControl x:Class="Wpf_NavigationTest.Views.FirstView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:Wpf_NavigationTest.ViewModels">
<!--this causes the view model's constructor to get called again-->
<UserControl.DataContext>
<viewModels:FirstViewModel/>
</UserControl.DataContext>
<Grid>
<TextBlock Text="User control 1" FontSize="40"/>
</Grid>
the View Model's constructor is called twice, losing the parameters passed by the Navigate event.
The problem here is that you are setting the DataContext inside your UserControl, and also in your main view model.
<UserControl.DataContext>
<viewModels:FirstViewModel/>
</UserControl.DataContext>
The code above is instantiating a new FirstViewModel every time this UserControl is created. Therefore when the control gets created by the ContentControl (based on the DataTemplate), it then goes ahead and also creates a new FirstViewModel.
So, the solution here is to remove the UserControl.DataContext declaration in the UserControl, and you can instead set the DataContext of the ContentControl to that of your SelectedView.
<ContentPresenter Content="{Binding SelectedView}"
DataContext="{Binding SelectedView}"/>
In order to use multiple view models to a single view, you can simply add another DataTemplate:
<DataTemplate DataType="{x:Type vm:ThirdViewModel}">
<views:SecondView/>
</DataTemplate>
For Design-Time data (to get the intellisense), you can make use of d:DataContext as explained in this article.
This will require you to set up some view models as static resources, I would recommend creating them in a separate ResourceDictionary.

ContentControl not updating

I'm trying to have a MainWindow that is bound to the a view. I change that view in code and expect it to update in the Main Window, however that is not happening.
I have this code in my XAML
<Grid>
<ContentControl Content="{Binding Source={StaticResource ViewModelLocator}, Path=MainWindowViewModel.CurrentControl}" />
</Grid>
I then change my Control via this code
public class MainWindowViewModel : ReactiveObject
{
private UserControl _CurrentControl = null;
public UserControl CurrentControl
{
get
{
if (_CurrentControl == null)
{
_CurrentControl = new HomePage();
}
return _CurrentControl;
}
set
{
this.RaiseAndSetIfChanged(x => x.CurrentControl, value);
}
}
}
As you can see I'm using the ReactiveUI library.
Is ContentControl the wrong thing to use in that view or am I just not binding and updating correctly?
There is actually a far better way to do this, using ViewModelViewHost:
<Grid DataContext="{Binding ViewModel, ElementName=TheUserControl}">
<ViewModelViewHost ViewModel="{Binding CurrentControlViewModel}" />
</Grid>
Now, your class will look something like:
public class MainWindowViewModel : ReactiveObject
{
private ReactiveObject _CurrentControlViewModel = new HomePageViewModel();
public ReactiveObject CurrentControlViewModel {
get { return _CurrentControl; }
set { this.RaiseAndSetIfChanged(x => x.CurrentControlViewModel, value); }
}
}
And somewhere in your app's startup, you should write:
RxApp.Register(typeof(IViewFor<HomePageViewModel>), typeof(HomePage));
What's ViewModelViewHost?
ViewModelViewHost will take a ViewModel object that you provide via Bindings, and look up a View that fits it, using Service Location. The Register call is how you can associate Views with ViewModels.
why you call your class MainWindowViewModel? when you wanna do mvvm you shouldn't have properties with type UserControl in your VM.
the usual mvvm way looks like this:
viewmodel with INotifyPropertyChanged
public class MyViewmodel
{
public IWorkspace MyContent {get;set;}
}
xaml content control with binding to your VM
<ContentControl Content="{Binding MyContent}"/>
datatemplate --> so that wpf knows how to render your IWorkspace
<DataTemplate DataType="{x:Type local:MyIWorkSpaceImplementationType}" >
<view:MyWorkspaceView />
</DataTemplate>
I think you have several muddled concepts here and they are getting in each others way.
Firstly you aren't actually using ANY of the reactiveUI code, it never gets called. Since your get accessor implements a lazy instantiation pattern then it means the set accessor is ignored. This means that the view never notifies the property change, so you never get updates.
I'd recommend using something more like
private UserControl _currentControl;
public MainWindowVirwModel()
{
CurrentControl = new HomePage();
}
public UserControl CurrentControl
{
get { return _curentControl;}
set { this.RaiseAndSetIfChanged(...); }
}
In addition, this still mixes up View components i.e. HomePage, inside your ViewModel tier which will making unit testing far more difficult.

Categories

Resources