canton7 / Stylet

A very lightweight but powerful ViewModel-First MVVM framework for WPF for .NET Framework and .NET Core, inspired by Caliburn.Micro.
MIT License
988 stars 143 forks source link

Using Stylet in a class library #131

Closed sa-mustafa closed 4 years ago

sa-mustafa commented 4 years ago

I want to ship a library containing WPF views based on Stylet MVVM approach. How can I bootstrap the library? Suppose, the caller does not know and does not want to know, how to work with Stylet.

The default bootstrapper takes a ViewModel and shows it by default which is not desirable in a class library. I resorted to modifying (which is not preferable) the Stylet's source by adding a ViewModel-less bootstrapper. I hit other errors such as Bootstrapping twice (when the library is used in another project using the Stylet too) at this line. I believe there should be an elegant way to use Stylet in a class library, bootstrapping without showing a default ViewModel, calling the Bootsrapper's Start in a well-defined manner without adding it to the caller App's Resources. Another suggestion is to modify the WindowManager class and make a public CreateWindow function so that anyone can modify the window's properties such as positioning. The ShowDialog function may be modified to give back a Window object and let the caller show it. Thanks a lot for the library.

canton7 commented 4 years ago

How would the user of your class library actually interact with it?

Is the intention that they'd write their own views and viewmodels, which can make use of the ones in your class library? Or perhaps they have their windows, they can ask your library to show your windows, and those don't interact? Or perhaps they don't have any of their own views/models?

sa-mustafa commented 4 years ago

It's the second case. They have their own, and I would like to provide the library based on Stylet MVVM pattern. After a day of struggling with the library, I switched to the Stylet source code and saw the inter working. I figured a way to solve my problems. I've come up with the following ViewModel-free bootstrapper:

namespace Bps.Osk
{
    using System.Windows;

    public class Bootstrapper : Stylet.Bootstrapper<object>
    {
        public static Bootstrapper Instance
        {
            get
            {
                if (bootstrapper != null)
                    return bootstrapper;

                bootstrapper = new Bootstrapper();
                bootstrapper.Setup(App.Current);
                bootstrapper.Configure();
                return bootstrapper;
            }
        }
        private static Bootstrapper bootstrapper;

        protected static Stylet.IViewManager ViewManager
        {
            get
            {
                if (viewManager != null)
                    return viewManager;

                viewManager = Instance.GetInstance(typeof(Stylet.IViewManager)) as Stylet.IViewManager;
                return viewManager;
            }
        }
        private static Stylet.IViewManager viewManager;

        protected static Stylet.IWindowManager WindowManager
        {
            get
            {
                if (windowManager != null)
                    return windowManager;

                windowManager = Instance.GetInstance(typeof(Stylet.IWindowManager)) as Stylet.IWindowManager;
                return windowManager;
            }
        }
        private static Stylet.IWindowManager windowManager;

        public new void Configure()
        {
            ConfigureBootstrapper();
        }

        public Window CreateWindow(object viewModel)
        {
            var view = ViewManager.CreateAndBindViewForModelIfNecessary(viewModel);
            return view as Window;
        }
    }
}

The library user just needs to create his desired ViewModel by calling the Bootstrapper.Instance.CreateWindow function. The library & stylet is automatically boostrapped and the ViewModel is shown correctly. If you agree with the solution, please add some notes to the documentation. The need for a public CreateWindow function still remains.

canton7 commented 4 years ago

I'm not sure how that can work? You're passing in App.Current, which is the App you've defined in your class library, not the actual running App.

I'd probably attempt something like the following (untested):

public class MyBootstrapper : Bootstrapper<object>
{
    // Stop it showing a window on startup
    protected override void Launch() { }
    // Expose the IContainer
    public new IContainer Container => base.Container;
}

public class LibraryManager
{
    private readonly MyBoostrapper bootstrapper;

    public LibraryManager
    {
        this.boostrapper = new MyBootstrapper();
        this.bootstrapper.Setup(Application.Current);
        // Might need to call OnStart as well, if the application has already started?
    }

    public void ShowWindow(MyViewModelBaseClass viewModel)
    {
        var windowManager = this.bootstrapper.Container.Get<IWindowManager>();
        windowManager.ShowWindow(viewModel);
    }
}
sa-mustafa commented 4 years ago

Yeah, you're right about App.Current; it should be changed to Application.Current. However, either code does the job. Do you provide a public CreateWindow function? Sometimes, the caller need the Windowobject prior to display.

canton7 commented 4 years ago

Stylet is a VM-first framework: you shouldn't really be interacting directly with views or windows.

That said, WindowManager has a protected CreateWindow method: if you want to customise how windows are created, subclass WindowManager to create your own customised version.