0

I am new to MVVM and if I am doing any logic wrong please let me know.

Basically frmEpisodeView.xaml should be the startup window which is set here:

StartupUri="View/frmEpisodeView.xaml"

When this windows load's I want to check if the user has already used the application and there is a registry key available.

I have set datacontext for frmEpisodeView.xaml from the code behind as below

DataContext = new EpisodeViewModel();

In My EpisodeViewModel.cs I do the logic to check the registry key

public EpisodeViewModel() {

    if (Registry.GetValue("HKEY_CURRENT_USER", "URL", "") == null)
    {        
        //OPEN FORM HERE FRMLOGINVIEW.XAML
        ServerURL = Registry.GetValue("HKEY_CURRENT_USER", "URL", "").ToString();
    }
}

In the logic if the registry key is empty, a form should appear where the user logs in which then will save the entry on that page.

How can I get the form to appear?

Eldho
  • 7,795
  • 5
  • 40
  • 77
Ben Clarke
  • 1,051
  • 4
  • 21
  • 47
  • Should this really be in your view model? Looks like view related code to me. It might be better off putting it in the code-behind of your `frmEpisodeView.xaml`. – Mike Eason Nov 18 '15 at 09:36
  • Ben... I'd look to move your registry checking to the `Model` for the View. This should set a property on the Model to say no I have not been logged in which the `ViewModel` can see and using a `View` finder can correctly request to another `View` to be displayed. – Stephen Ross Nov 18 '15 at 10:00
  • @MikeEason Which bit of the code would you add to the frmEpisodeView.xaml? Just the validation? – Ben Clarke Nov 18 '15 at 10:06
  • @StephenRoss I have not implemented a Model for the view, Do you have a quick example for this? – Ben Clarke Nov 18 '15 at 10:07
  • This issue is not specific to MVVM. The solution is essentially as it was without MVVM. Tell me, how would you do it without MVVM? – Liero Nov 18 '15 at 14:58

4 Answers4

3

The cleanest and i think easiest way to do this, is to write a helper class. Create an Interface for this Service and and then implement a method ShowWindow(object DataContext) to show a window you like and set the datacontext.

class WindowService:IWindowService
{
    public void showWindow<T>(object DataContext) where T: Window, new() 
    {
     ChildWindow window = new T();
     window.Datacontext = DataContext;
     window.Show();
    }
}

Another very elegant way is using an Action like this (Source):

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        DataContext = new MainViewModel(() => (new Window()).Show()); // would be actual window
    }
}

public class MainViewModel
{
    private Action popupAction;
    public MainViewModel(Action popupAction)
    {
        this.popupAction = popupAction;
    }

    public ICommand PopupCommand { get; set; }

    public void PopupCommandAction()
    {
        popupAction();
    }
}

public class SomeUnitTest
{
    public void TestVM()
    {
        var vm = new MainViewModel(() => { });
    }
}
Community
  • 1
  • 1
FKutsche
  • 392
  • 2
  • 17
  • Thanks for taking your time to answer my question. Where would my validation code be? I am a bit confused. – Ben Clarke Nov 18 '15 at 10:34
  • In my opinion validation should be a part of the model. The answer above mine is a good example – FKutsche Nov 18 '15 at 10:45
  • @FKutsche could you provide more detailed answer or example using IWindowService? or maybe some link to learn your approach. With Best Regards! – StepUp Nov 18 '15 at 13:29
  • Look at this example: http://stackoverflow.com/a/16994523/555878 . He is using some sort of message service to tell the view from the viewmodel to open a new window. This does not violate the MVVM rules. – FKutsche Nov 18 '15 at 15:15
0

So how I would initially lay out this as a quick example.

ViewModel

public class EpisodeViewModel
{
    private readonly IEpisodeModel episodeModel;

    private readonly IViewFinder viewFinder;

    public EpisodeViewModel(IEpisodeModel episodeModel, IViewFinder viewFnder)
    {
        this.episodeModel = episodeModel;
        this.viewFinder = viewFinder;
        CheckLoginPassed(this.episodeModel.LoginPassed);
    }

    private CheckLoginPassed(bool loginPassed)
    {
        if (!loginPassed)
        {
            this.viewFinder.LoadView<ILoginView>();
        }
    }
}

IView Interface

public interface IView
{
    void Show();
}

Model Interface

public interface IEpisodeModel
{
    bool LoginPassed
    {
        get;
    }
}

Model

public class EpisodeModel : IEpisodeModel
{
    private bool loginPassed;

    public EpisodeModel()
    {
        if (Registry.GetValue("HKEY_CURRENT_USER", "URL", "") == null)
        {
            loginPassed = false;
        }
    }

    public bool LoginPassed
    {
        get
        {
            return this.loginPassed;
        }
    }
}

IViewFinder Interface

public interface IViewFinder
{
    void LoadView<T>();
}

ViewFinder

public class ViewFinder : IViewFinder
{
    private readonly IEnumerable<IView> availableViews;

    public ViewFinder(IEnumerable<IView> availableViews)
    {
        this.availableViews = availableViews;
    }

    public void LoadView<T>()
    {
        var type = typeof(T);

        foreach (var view in this.availableViews)
        {
            if (view.GetType().IsAssignableFrom(type))
            {
                view.Show();
            }
        }
    }

I've written this with using an IoC in mind, if you don't have one I'd really look to getting one as it will be a massive help when resolving dependencies. This is just a basic example and I'd probably have a different object that was only for checking the registry that I provided the results to the Model from, but this should provide a start.

So in referring to the ILoginView this is an interface that simply inherits from IView, it doesn't actually provide any details. The IView interface is slightly weird as your views already implements a Show method whenever they also inherit from 'Window' so in effect you don't have to do anything, this simply provides an easier way to call show without having to actually know that what you are calling is a Window.

Another point is that my ViewFinder is relatively simple here although it would do for a small project I would probably look at something like MVVM Light to manage my MVVM handling as this comes with View handling and a simple Service Locator as standard.

Stephen Ross
  • 882
  • 6
  • 21
  • Thanks for taking your time to answer my question. In the example you have _IEpisodeModel_. Where is this class? Also you say in the text below _I've written this with using an IoC in mind_ What is IoC? – Ben Clarke Nov 18 '15 at 10:48
  • Sorry, everything with 'I' appended to me means 'Interface', in my example they provide the interface to the object and only provide what information each other object needs, the public properties/methods in my example, they don't have any business logic functionality. An 'IoC', this stands for 'Inversion of Control' and provide a method of not worrying about the actual implementations of interfaces allowing these to be swapped out without breaking functionality, a better answer is found here [another question](http://stackoverflow.com/questions/3058/what-is-inversion-of-control). – Stephen Ross Nov 18 '15 at 11:02
  • Part 2... On my IoC answer, I don't profess to be a master so the link will provide a more detailed answer than I can. On IoC implementations in C#, there are a few that you may wish to look at such as [autofac](http://autofac.org/), or [simple injector](https://simpleinjector.org/index.html), there are plenty more but these are my two `goto` IoC's. – Stephen Ross Nov 18 '15 at 11:05
  • When you say Interface do you mean the XAML page or a Interface class? – Ben Clarke Nov 18 '15 at 11:15
  • Interface as in what you define as a 'Interface class', the definition of this would be `public interface IEpisodeModel`. – Stephen Ross Nov 18 '15 at 12:06
  • What does that Interface class contain? – Ben Clarke Nov 18 '15 at 12:35
  • I have updated my answer with what the interface would look like for my example. As previously noted literally only the public property that the `ViewModel` will be able to access stating if the user has previously logged in or not. – Stephen Ross Nov 18 '15 at 13:03
  • Thanks for the update. I am implementing and researching while you are helping just so i understand. **1.** What does the IViewFinder interface class contain? **2.** In the ViewFinder class, I am getting an error `System.Type does not contain a definition for 'IsAssingableFrom'` – Ben Clarke Nov 18 '15 at 13:27
  • 1. Added to my answer to show the IViewFinder. 2. Sorry was coding this from memory, the method call should be `IsAssignableFrom(Type type)` – Stephen Ross Nov 18 '15 at 13:38
  • Thanks for this help! I have made the changes to the code and I still seem to be getting 2 errors. **1.** In ViewFinder the error is `Microsoft.Practices.Prism.Mvvm.IView does not contain a definition for 'Show'` **2.** In EpisodeViewModel I do not have an ILoginView class? What will be in this? – Ben Clarke Nov 18 '15 at 13:45
  • 1. I didn't realise you are using `Prism`, the IView that I refer to is not the same as the `Prism.Mvvm.IView` that you are currently referencing, I shall update my answer to show what this would be like. 2. The `ILoginView` is the actual View that you wish to show when the user has to login, this will be the class that is generated for the code behind and the `ILoginView` will be an interface that it implements. I unfortunately cannot show what this will look like, it's basically the code behind class to your login.xaml view. – Stephen Ross Nov 18 '15 at 13:53
  • Ok, Well I have replaced `ILoginView` with `frmLoginView` and I will test that. I will just wait for the IView class and implement that and i will give it a try. – Ben Clarke Nov 18 '15 at 14:02
  • When setting DataContext in my frmEpisodeView I now need to pass to constructors which are IEpisodeViewModel and IViewFinder. What do i pass? `public frmEpisodeView() { InitializeComponent(); DataContext = new EpisodeViewModel(); }` – Ben Clarke Nov 18 '15 at 14:14
  • This is where the IoC comes in, personally I would always inject the `ViewModel` into the `View`. Since currently this is a small example, just new up everything in the constructor of `frmEpisodeView`, new up an instance of the `ILoginView` and pass that to the `ViewFinder` which you can pass into the `EpisodeViewModel` with the `Model`. It all get's a little complicated to think about, but just make sure everything is newed up and all the constructors are satisfied and everything should fit into place. – Stephen Ross Nov 18 '15 at 14:26
  • When you say create a new instance of ILoginView isn't that the view of the page i want to show 'frmLoginView'. If so, It wont let me create a new instance of this in my frmEpisodeView. – Ben Clarke Nov 18 '15 at 14:34
  • I don't see why not, it's effectively just another class. As long as you don't call `Show()` within the constructor of the `frmLoginView`. The other option is to new everything up within the `app.xaml.cs` and injecting the 'IEpisodeViewModel` into the `frmEpisodeView`, but this is a whole other topic that will come once you get used to MVVM. – Stephen Ross Nov 18 '15 at 14:39
  • I was missing a `using` my bad. So I created a new instance of frmLoginView in the constructor of frmEpsiodeView. The ViewModel needs an IViewFinder and IEpisodeModel what do i pass to this constructor? – Ben Clarke Nov 18 '15 at 14:43
  • So to create the `ViewModel`, first create a `ViewFinder` passing in the instance of `frmLoginView` (such as `new ViewFinder(new [] { frmLoginView })`), then create a new `Model` like `new EpisodeModel`, then create a new `ViewModel` like `new EpisodeViewModel(viewFinder, episodeModel)`, where 'viewFinder' and 'episodeModel' are the names of the variables of the 'ViewFinder' and 'EpisodeModel'. – Stephen Ross Nov 18 '15 at 14:58
  • When creating a new `ViewFinder` it wont let me pass it my instance of `frmLoginView` here is the code i written: Instance of frmLoginView `frmLoginView loginView = new frmLoginView();` Instance of ViewFinder `ViewFinder viewFinder = new ViewFinder(loginView);` and the error is `Has some invalid arguments` – Ben Clarke Nov 18 '15 at 15:21
  • This is because the constructor of the `ViewFinder` takes an `IEnumerable`, you have to call the constructor like `new ViewFinder(new [] { loginView})` as explained in my previous comment. – Stephen Ross Nov 18 '15 at 15:29
  • When adding your code `ViewFinder a = new ViewFinder(new[] { loginView });` i got the same `Invalid Arguments` error. – Ben Clarke Nov 18 '15 at 15:35
  • Does `LoginView` inherit from our `IView` (NOT Prism's version)? This would cause this issue. If your `LoginView` has an interface such as `ILoginView` inherit `ILoginView` from `IView` (`public interface ILoginView : IView`). – Stephen Ross Nov 18 '15 at 15:38
  • The `frmLoginView.xaml.cs` didn't inherit `IView`, i have just made the change and it worked. The conversion error is still there though as the constructor in `EpsiodeViewModel` are `IEpisodeModel` and `IViewFinder` and we are creating new instances of `EpisodeModel` and `Viewfinder`? – Ben Clarke Nov 18 '15 at 15:47
  • And the `EpisodeModel` and `ViewFinder` should inherit `IEpisodeModel` and `IViewFinder` respectively. Otherwise you can change the constructor on the `ViewModel` to reference the implementations and not the interfaces if required to speed up the process. – Stephen Ross Nov 18 '15 at 15:56
  • Thanks for all your help today you have been really helpful! The code runs. I am going to research a bit about the flow in MVVM tomorrow to clarify what we have done - again, thank you! – Ben Clarke Nov 18 '15 at 16:01
0

Yes, this is a typical problem when doing MVVM. Many of these problems can be generalized as: How do I do this or that concrete "thing" in the concrete user interface as I should stick 100% inside my view model?

This kind and many other related problems have excellent answers in https://msdn.microsoft.com/en-us/library/gg406140.aspx (Developer's Guide to Microsoft Prism Library 5.0 for WPF) which I would highly recommend to you.

Personally I found it a great pleasure to read the book and the authors obviously know what they express themselves about.

Best regards,

Henrik Dahl

Henrik Dahl
  • 121
  • 1
  • 1
  • 7
-1

You could always put this view code in the App.xaml.cs constructor:

public App()
{
    if (Registry.GetValue("HKEY_CURRENT_USER", "URL", "") == null)
    {        
        //OPEN FORM HERE FRMLOGINVIEW.XAML
        StartupUri = new Uri("View/frmLoginView.xaml", UriKind.Relative);

        ServerURL = Registry.GetValue("HKEY_CURRENT_USER", "URL", "").ToString();
    }
}
Mike Eason
  • 9,525
  • 2
  • 38
  • 63