An elegant way to implement this IMHO would be to use a dependency injection container, this will provide great flexibility while allowing you to completely separate concerns (i.e. the settings implementation from your view models and custom controls).
There are many DI frameworks out there, for my example I will use simple injector because it is free (open source), simple and fast but you can apply the same principle to other frameworks (Unity, Ninject, etc..).
Start by creating an interface for your settings service, for example:
public interface ISettingsService
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
Then add your implementation for the service, the beauty of using DI is that you can change the implementation at anytime or completely replace it and your application will continue to work as usual.
Let's say you want to use application settings, here is your service:
public class SettingsServiceFromApplication : ISettingsService
{
public double Volume
{
get
{
return Properties.Settings.Volume;
}
}
[...]
}
Or let's say you want to use a database to store your settings:
public class SettingsServiceFromDb : ISettingsService
{
public double Volume
{
get
{
return MyDb.Volumen;
}
}
[...]
}
Then you can use a DI container to specify which implementation to use:
Start by installing the library using NuGet:
Install-Package SimpleInjector -Version 4.0.12
You need a way to share your container throughout the application, I usually just go with a static class that I initialize when starting the app:
using Container = SimpleInjector.Container;
namespace YourNamespace
{
public class Bootstrapper
{
internal static Container Container;
public static void Setup()
{
//Create container and register services
Container = new Container();
//Let's specify that we want to use SettingsServiceFromApplication
Container.Register<ISettingsService, SettingsServiceFromApplication>();
//You can use your bootstrapper class to initialize other stuff
}
}
You need to call Setup when starting the App, the best place is in the App constructor:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper.Setup();
}
}
So now you have an app wide depedency injection container that you can use to request "services" (specific implementations of an interface).
To get the settings implementation in your view models you could simply call the container as follows:
// This will return an instance of SettingsServiceFromApplication
ISettingsService settingsService = Bootstrapper.Container.GetInstance<ISettingsService>();
double volumen = settingsService.Volume;
To make it easier to work with, I usually create a base view model that will allow to get services more easyly, for example:
public abstract BaseViewModel
{
private ISettingsService _settings;
protected ISettingsService GeneralSettings
{
get
{
if (_settings == null)
_settings = Bootstrapper.Container.GetInstance<ISettingsService>();
return _settings;
}
}
}
Every view model inheriting from this class will have access to the settings:
public class YourViewModel : BaseViewModel
{
private void OnMediaEndedCommand()
{
if (GeneralSettings.PauseOnLastFrame)
{
MediaPlayer.SetMediaState(MediaPlayerStates.Pause);
return;
}
if (PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
MediaPlayer.SetMediaState(MediaPlayerStates.Stop);
return;
}
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
}
}
As you can see the code is the same as your code! But now the settings are coming from your container. Where is the elegance? Well, let's say that one year from now someone decides that you will store your settings in a database, what do you need to change in your code?
Container.Register<ISettingsService, SettingsServiceFromDb>();
A single line. Everything else should work as usual.
As well as view models, you could use this mechanism in your own controls:
public class MyMediaElement : UserControl //Or MediaElement and instead of commands you can override real events in the control code behind, this does not break the MVVM pattern at all, just make sure you use depedency properties if you need to exchange data with your view models
{
private void OnMediaEndedCommand()
{
//Get your settings from your container, do whatever you want to do depending on the settings
[...]
}
}
Then just use your control in your Views / ViewModels:
<local:MyMediaElement />
Yep, that's all you need because you handle everything in your User / Custom control, your view models doesn't need to care about how you handle settings in the control.
There are many options you can use to register containers, I recommend you take a look at the docs:
https://simpleinjector.org/index.html
https://simpleinjector.readthedocs.io/en/latest/index.html