WPF 应用程序中的条件设置
Conditional settings in WPF application
我正在为我的媒体播放器设置用户可更改的设置,我正在努力寻找解决问题的优雅方法。
例如,我的一个设置 - 在最后一帧暂停视频,如果未选中,它将继续播放列表,或者如果只有一个文件,重置它并在开始时暂停。
我是这样实现的:
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()));
}
这包含在主要 window 的 ViewModel 中,其中媒体元素是并且 GeneralSettings.PauseOnLastFrame
是布尔值 属性。
此命令绑定如下:
<MediaElement ....>
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="MediaEnded">
<ia:InvokeCommandAction Command="{Binding MediaEndedCommand}"/>
</ia:EventTrigger>
</ia:Interaction.Triggers>
</MediaElement>
它有效但很糟糕,我应该如何以优雅的方式实现这样的设置系统?有些设置可能不是布尔值,它们可能有多个选项,有些可能仅在启动时应用,而其他设置,如上图所示,基于事件。
根据您提供的信息和示例代码,我建议
方法 - 1
ViewModel 与 System.Configuration.ApplicationSettingsBase
紧密结合,您可以在 ViewModel 中提及所有属性,并使用单独的应用程序设置 属性 映射其中的单个属性。您可以在之后直接使用您的设置进行出价,例如:{x:Static Settings.Default.Whatevs}
。其他 "Save" 按钮点击事件或主要 window 关闭事件,您可以保存所有设置,例如: Settings.Default.Save();
方法 - 2
我建议/更喜欢(如果我正在开发此应用程序)一个更好的方法是开发一个实现继承(例如:ISettingProvider
) 它将所有设置显示为单独的属性,并且还有一个保存所有设置值的保存方法。您可以在 ViewModel 中使用此包装器 class 以更好地处理所有命令和设置值。
这种方法的好处是,如果您决定将设置更改为数据库,则无需更改 ViewModel,因为所有工作都在 SettingProvider
class 中完成。
我不确定,但根据查看您的代码,我假设您使用了 Approach-1。请发表您的评论和对此答案的任何反馈。我想知道您的想法,也许您有更简单有趣的方法来实现这一目标。
UPDATE-1
例子
演示枚举
public enum MediaStatus
{
Playing = 0,
Stopped = 1,
Paused = 2
}
界面
public interface ISettingProvider
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
包装器Class
public class SettingProvider : ISettingProvider
{
private double volumne;
public double Volumne // read-write instance property
{
get
{
return volumne;
}
set
{
volumne = value;
Settings.Default.Volumne = volumne;
}
}
private string lastMediaUrl;
public string LastMediaUrl // read-write instance property
{
get
{
return lastMediaUrl;
}
set
{
lastMediaUrl = value;
Settings.Default.LastMediaUrl = lastMediaUrl;
}
}
private MediaStatus playingMediaStatus;
public MediaStatus PlayingMediaStatus // read-write instance property
{
get
{
return playingMediaStatus;
}
set
{
playingMediaStatus = value;
Settings.Default.PlayingMediaStatus = (int)playingMediaStatus;
}
}
public void SaveSettings()
{
Settings.Default.Save();
}
//Constructor
public SettingProvider()
{
this.Volumne = Settings.Default.Volumne;
this.LastMediaUrl = Settings.Default.LastMediaUrl;
this.PlayingMediaStatus = (MediaStatus)Settings.Default.PlayingMediaStatus;
}
}
ViewModelBase Class
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
CommandHandlerClass
public class CommandHandler : ICommand
{
public event EventHandler CanExecuteChanged { add { } remove { } }
private Action<object> action;
private bool canExecute;
public CommandHandler(Action<object> action, bool canExecute)
{
this.action = action;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute;
}
public void Execute(object parameter)
{
action(parameter);
}
}
ViewModel
public class SettingsViewModel : ViewModelBase
{
SettingProvider objSettingProvider = new SettingProvider();
public double Volumne
{
get
{
return objSettingProvider.Volumne;
}
set
{
objSettingProvider.Volumne = value;
OnPropertyChanged("Volumne");
}
}
// Implementaion of other properties of SettingProvider with your ViewModel properties;
private ICommand saveSettingButtonCommand;
public ICommand SaveSettingButtonCommand
{
get
{
return saveSettingButtonCommand ?? (saveSettingButtonCommand = new CommandHandler(param => saveSettings(param), true));
}
}
private void saveSettings()
{
objSettingProvider.SaveSettings();
}
}
UPDATE-2
public interface ISettingProvider
{
bool PauseOnLastFrame;
bool IsAutoPlay;
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
public class SettingProvider : ISettingProvider
{
private bool pauseOnLastFrame;
public bool PauseOnLastFrame // read-write instance property
{
get
{
return pauseOnLastFrame;
}
set
{
pauseOnLastFrame = value;
Settings.Default.PauseOnLastFrame = volumne;
}
}
private bool isAutoPlay;
public bool IsAutoPlay // read-write instance property
{
get
{
return isAutoPlay;
}
set
{
isAutoPlay = value;
Settings.Default.IsAutoPlay = volumne;
}
}
}
public class SettingsViewModel : ViewModelBase
{
SettingProvider objSettingProvider = new SettingProvider();
MediaStatus PlayingMediaStatus
{
get
{
return objSettingProvider.PlayingMediaStatus;
}
set
{
if(value == MediaStatus.Paused)
MediaPlayer.Pause();
if(value == MediaStatus.Playing)
MediaPlayer.Play();
if(value == MediaStatus.Stopped)
MediaPlayer.Stop();
objSettingProvider.PlayingMediaStatus = (int)value;
OnPropertyChanged("PlayingMediaStatus");
}
}
private string currentMediaFile;
public string CurrentMediaFile
{
get
{
return currentMediaFile;
}
set
{
currentMediaFile = value;
MediaPlayer.Stop();
MediaPlayer.Current = currentMediaFile;
if(objSettingProvider.IsAutoPlay)
MediaPlayer.Play();
OnPropertyChanged("CurrentMediaFile");
}
}
// Implementaion of other properties of SettingProvider with your ViewModel properties;
private ICommand onMediaEndedCommand;
public ICommand OnMediaEndedCommand
{
get
{
return onMediaEndedCommand ?? (onMediaEndedCommand = new CommandHandler(param => onMediaEnded(param), true));
}
}
private void onMediaEnded()
{
if(objSettingProvider.PauseOnLastFrame)
{
PlayingMediaStatus = MediaStatus.Paused;
}
else if(PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
PlayingMediaStatus = MediaStatus.Stopped;
}
else
{
CurrentMediaFile = PlayListViewModel.FilesCollection.MoveNext();
}
}
}
注意: 这是我放在这里的详细示例,如果我遗漏了某个地方,也可以避免一些语法错误或命名错误。请更正。
我不知道您使用的是哪种媒体播放器设置。我拿了一些样本属性。这只是您可以为您的应用程序实现的结构示例。您可能需要更改更多代码来实现此结构。
我想也许您正在寻找一种接口方法?
public interface IMediaEndedHandler
{
bool AlternateHandling(MediaPlayer player);
}
public class NullMediaEndedHandler : IMediaEndedHandler
{
public bool AlternateHandling(MediaPlayer player)
{
return false;
}
}
public class PauseOnLastFrameHandler : IMediaEndedHandler
{
public bool AlternateHandling(MediaPlayer player)
{
player.SetMediaState(MediaPlayerStates.Pause);
return true;
}
}
public class GeneralSettings
{
private bool pauseOnLastFrame;
private bool PauseOnLastFrame
{
get
{
return pauseOnLastFrame;
}
set
{
pauseOnLastFrame = value;
MediaEndedHandler = value
? new PauseOnLastFrameHandler()
: new NullMediaEndedHandler();
}
}
public IMediaEndedHandler MediaEndedHandler = new NullMediaEndedHandler();
}
然后:
private void OnMediaEndedCommand()
{
if (GeneralSettings.MediaEndedHandler.AlternateHandling(MediaPlayer))
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()));
}
这种方式,例如,如果您的设置是。枚举而不是布尔值,您可以为每个可能的值指定不同的接口实现。
实现此恕我直言的一种优雅方法是使用依赖项注入容器,这将提供极大的灵活性,同时允许您完全分离关注点(即设置实现与视图模型和自定义控件)。
有很多 DI 框架,对于我的示例,我将使用 simple injector,因为它是免费的(开源)、简单且快速,但您可以将相同的原则应用于其他框架(Unity、Ninject, 等等..).
首先为您的设置服务创建一个界面,例如:
public interface ISettingsService
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
然后为服务添加您的实现,使用 DI 的美妙之处在于您可以随时更改实现或完全替换它,您的应用程序将继续照常工作。
假设您想使用应用程序设置,这是您的服务:
public class SettingsServiceFromApplication : ISettingsService
{
public double Volume
{
get
{
return Properties.Settings.Volume;
}
}
[...]
}
或者假设您想使用数据库来存储您的设置:
public class SettingsServiceFromDb : ISettingsService
{
public double Volume
{
get
{
return MyDb.Volumen;
}
}
[...]
}
然后你可以使用DI容器来指定使用哪个实现:
首先使用 NuGet 安装库:
Install-Package SimpleInjector -Version 4.0.12
您需要一种在整个应用程序中共享您的容器的方法,我通常只使用在启动应用程序时初始化的静态 class:
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
}
}
App启动时需要调用Setup,最好放在App构造函数中:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper.Setup();
}
}
所以现在你有一个应用范围的依赖注入容器,你可以用它来请求"services"(接口的特定实现)。
要在视图模型中实现设置,您只需按如下方式调用容器:
// This will return an instance of SettingsServiceFromApplication
ISettingsService settingsService = Bootstrapper.Container.GetInstance<ISettingsService>();
double volumen = settingsService.Volume;
为了更容易使用,我通常会创建一个基础视图模型,以便更轻松地获取服务,例如:
public abstract BaseViewModel
{
private ISettingsService _settings;
protected ISettingsService GeneralSettings
{
get
{
if (_settings == null)
_settings = Bootstrapper.Container.GetInstance<ISettingsService>();
return _settings;
}
}
}
从这个 class 继承的每个视图模型都可以访问设置:
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()));
}
}
如您所见,代码与您的代码相同!但现在设置来自您的容器。优雅在哪里?好吧,假设一年后有人决定将您的设置存储在数据库中,您需要更改代码中的哪些内容?
Container.Register<ISettingsService, SettingsServiceFromDb>();
一行。其他一切都应该照常工作。
除了视图模型,您还可以在自己的控件中使用此机制:
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
[...]
}
}
然后只需在您的视图/视图模型中使用您的控件:
<local:MyMediaElement />
是的,这就是您所需要的,因为您处理用户/自定义控件中的所有内容,您的视图模型不需要关心您如何处理控件中的设置。
有很多选项可以用来注册容器,我建议你看看文档:
https://simpleinjector.org/index.html
https://simpleinjector.readthedocs.io/en/latest/index.html
我正在为我的媒体播放器设置用户可更改的设置,我正在努力寻找解决问题的优雅方法。
例如,我的一个设置 - 在最后一帧暂停视频,如果未选中,它将继续播放列表,或者如果只有一个文件,重置它并在开始时暂停。
我是这样实现的:
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()));
}
这包含在主要 window 的 ViewModel 中,其中媒体元素是并且 GeneralSettings.PauseOnLastFrame
是布尔值 属性。
此命令绑定如下:
<MediaElement ....>
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="MediaEnded">
<ia:InvokeCommandAction Command="{Binding MediaEndedCommand}"/>
</ia:EventTrigger>
</ia:Interaction.Triggers>
</MediaElement>
它有效但很糟糕,我应该如何以优雅的方式实现这样的设置系统?有些设置可能不是布尔值,它们可能有多个选项,有些可能仅在启动时应用,而其他设置,如上图所示,基于事件。
根据您提供的信息和示例代码,我建议
方法 - 1
ViewModel 与 System.Configuration.ApplicationSettingsBase
紧密结合,您可以在 ViewModel 中提及所有属性,并使用单独的应用程序设置 属性 映射其中的单个属性。您可以在之后直接使用您的设置进行出价,例如:{x:Static Settings.Default.Whatevs}
。其他 "Save" 按钮点击事件或主要 window 关闭事件,您可以保存所有设置,例如: Settings.Default.Save();
方法 - 2
我建议/更喜欢(如果我正在开发此应用程序)一个更好的方法是开发一个实现继承(例如:ISettingProvider
) 它将所有设置显示为单独的属性,并且还有一个保存所有设置值的保存方法。您可以在 ViewModel 中使用此包装器 class 以更好地处理所有命令和设置值。
这种方法的好处是,如果您决定将设置更改为数据库,则无需更改 ViewModel,因为所有工作都在 SettingProvider
class 中完成。
我不确定,但根据查看您的代码,我假设您使用了 Approach-1。请发表您的评论和对此答案的任何反馈。我想知道您的想法,也许您有更简单有趣的方法来实现这一目标。
UPDATE-1
例子
演示枚举
public enum MediaStatus
{
Playing = 0,
Stopped = 1,
Paused = 2
}
界面
public interface ISettingProvider
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
包装器Class
public class SettingProvider : ISettingProvider
{
private double volumne;
public double Volumne // read-write instance property
{
get
{
return volumne;
}
set
{
volumne = value;
Settings.Default.Volumne = volumne;
}
}
private string lastMediaUrl;
public string LastMediaUrl // read-write instance property
{
get
{
return lastMediaUrl;
}
set
{
lastMediaUrl = value;
Settings.Default.LastMediaUrl = lastMediaUrl;
}
}
private MediaStatus playingMediaStatus;
public MediaStatus PlayingMediaStatus // read-write instance property
{
get
{
return playingMediaStatus;
}
set
{
playingMediaStatus = value;
Settings.Default.PlayingMediaStatus = (int)playingMediaStatus;
}
}
public void SaveSettings()
{
Settings.Default.Save();
}
//Constructor
public SettingProvider()
{
this.Volumne = Settings.Default.Volumne;
this.LastMediaUrl = Settings.Default.LastMediaUrl;
this.PlayingMediaStatus = (MediaStatus)Settings.Default.PlayingMediaStatus;
}
}
ViewModelBase Class
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
CommandHandlerClass
public class CommandHandler : ICommand
{
public event EventHandler CanExecuteChanged { add { } remove { } }
private Action<object> action;
private bool canExecute;
public CommandHandler(Action<object> action, bool canExecute)
{
this.action = action;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute;
}
public void Execute(object parameter)
{
action(parameter);
}
}
ViewModel
public class SettingsViewModel : ViewModelBase
{
SettingProvider objSettingProvider = new SettingProvider();
public double Volumne
{
get
{
return objSettingProvider.Volumne;
}
set
{
objSettingProvider.Volumne = value;
OnPropertyChanged("Volumne");
}
}
// Implementaion of other properties of SettingProvider with your ViewModel properties;
private ICommand saveSettingButtonCommand;
public ICommand SaveSettingButtonCommand
{
get
{
return saveSettingButtonCommand ?? (saveSettingButtonCommand = new CommandHandler(param => saveSettings(param), true));
}
}
private void saveSettings()
{
objSettingProvider.SaveSettings();
}
}
UPDATE-2
public interface ISettingProvider
{
bool PauseOnLastFrame;
bool IsAutoPlay;
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
public class SettingProvider : ISettingProvider
{
private bool pauseOnLastFrame;
public bool PauseOnLastFrame // read-write instance property
{
get
{
return pauseOnLastFrame;
}
set
{
pauseOnLastFrame = value;
Settings.Default.PauseOnLastFrame = volumne;
}
}
private bool isAutoPlay;
public bool IsAutoPlay // read-write instance property
{
get
{
return isAutoPlay;
}
set
{
isAutoPlay = value;
Settings.Default.IsAutoPlay = volumne;
}
}
}
public class SettingsViewModel : ViewModelBase
{
SettingProvider objSettingProvider = new SettingProvider();
MediaStatus PlayingMediaStatus
{
get
{
return objSettingProvider.PlayingMediaStatus;
}
set
{
if(value == MediaStatus.Paused)
MediaPlayer.Pause();
if(value == MediaStatus.Playing)
MediaPlayer.Play();
if(value == MediaStatus.Stopped)
MediaPlayer.Stop();
objSettingProvider.PlayingMediaStatus = (int)value;
OnPropertyChanged("PlayingMediaStatus");
}
}
private string currentMediaFile;
public string CurrentMediaFile
{
get
{
return currentMediaFile;
}
set
{
currentMediaFile = value;
MediaPlayer.Stop();
MediaPlayer.Current = currentMediaFile;
if(objSettingProvider.IsAutoPlay)
MediaPlayer.Play();
OnPropertyChanged("CurrentMediaFile");
}
}
// Implementaion of other properties of SettingProvider with your ViewModel properties;
private ICommand onMediaEndedCommand;
public ICommand OnMediaEndedCommand
{
get
{
return onMediaEndedCommand ?? (onMediaEndedCommand = new CommandHandler(param => onMediaEnded(param), true));
}
}
private void onMediaEnded()
{
if(objSettingProvider.PauseOnLastFrame)
{
PlayingMediaStatus = MediaStatus.Paused;
}
else if(PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
PlayingMediaStatus = MediaStatus.Stopped;
}
else
{
CurrentMediaFile = PlayListViewModel.FilesCollection.MoveNext();
}
}
}
注意: 这是我放在这里的详细示例,如果我遗漏了某个地方,也可以避免一些语法错误或命名错误。请更正。 我不知道您使用的是哪种媒体播放器设置。我拿了一些样本属性。这只是您可以为您的应用程序实现的结构示例。您可能需要更改更多代码来实现此结构。
我想也许您正在寻找一种接口方法?
public interface IMediaEndedHandler
{
bool AlternateHandling(MediaPlayer player);
}
public class NullMediaEndedHandler : IMediaEndedHandler
{
public bool AlternateHandling(MediaPlayer player)
{
return false;
}
}
public class PauseOnLastFrameHandler : IMediaEndedHandler
{
public bool AlternateHandling(MediaPlayer player)
{
player.SetMediaState(MediaPlayerStates.Pause);
return true;
}
}
public class GeneralSettings
{
private bool pauseOnLastFrame;
private bool PauseOnLastFrame
{
get
{
return pauseOnLastFrame;
}
set
{
pauseOnLastFrame = value;
MediaEndedHandler = value
? new PauseOnLastFrameHandler()
: new NullMediaEndedHandler();
}
}
public IMediaEndedHandler MediaEndedHandler = new NullMediaEndedHandler();
}
然后:
private void OnMediaEndedCommand()
{
if (GeneralSettings.MediaEndedHandler.AlternateHandling(MediaPlayer))
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()));
}
这种方式,例如,如果您的设置是。枚举而不是布尔值,您可以为每个可能的值指定不同的接口实现。
实现此恕我直言的一种优雅方法是使用依赖项注入容器,这将提供极大的灵活性,同时允许您完全分离关注点(即设置实现与视图模型和自定义控件)。
有很多 DI 框架,对于我的示例,我将使用 simple injector,因为它是免费的(开源)、简单且快速,但您可以将相同的原则应用于其他框架(Unity、Ninject, 等等..).
首先为您的设置服务创建一个界面,例如:
public interface ISettingsService
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
然后为服务添加您的实现,使用 DI 的美妙之处在于您可以随时更改实现或完全替换它,您的应用程序将继续照常工作。
假设您想使用应用程序设置,这是您的服务:
public class SettingsServiceFromApplication : ISettingsService
{
public double Volume
{
get
{
return Properties.Settings.Volume;
}
}
[...]
}
或者假设您想使用数据库来存储您的设置:
public class SettingsServiceFromDb : ISettingsService
{
public double Volume
{
get
{
return MyDb.Volumen;
}
}
[...]
}
然后你可以使用DI容器来指定使用哪个实现:
首先使用 NuGet 安装库:
Install-Package SimpleInjector -Version 4.0.12
您需要一种在整个应用程序中共享您的容器的方法,我通常只使用在启动应用程序时初始化的静态 class:
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
}
}
App启动时需要调用Setup,最好放在App构造函数中:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper.Setup();
}
}
所以现在你有一个应用范围的依赖注入容器,你可以用它来请求"services"(接口的特定实现)。
要在视图模型中实现设置,您只需按如下方式调用容器:
// This will return an instance of SettingsServiceFromApplication
ISettingsService settingsService = Bootstrapper.Container.GetInstance<ISettingsService>();
double volumen = settingsService.Volume;
为了更容易使用,我通常会创建一个基础视图模型,以便更轻松地获取服务,例如:
public abstract BaseViewModel
{
private ISettingsService _settings;
protected ISettingsService GeneralSettings
{
get
{
if (_settings == null)
_settings = Bootstrapper.Container.GetInstance<ISettingsService>();
return _settings;
}
}
}
从这个 class 继承的每个视图模型都可以访问设置:
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()));
}
}
如您所见,代码与您的代码相同!但现在设置来自您的容器。优雅在哪里?好吧,假设一年后有人决定将您的设置存储在数据库中,您需要更改代码中的哪些内容?
Container.Register<ISettingsService, SettingsServiceFromDb>();
一行。其他一切都应该照常工作。
除了视图模型,您还可以在自己的控件中使用此机制:
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
[...]
}
}
然后只需在您的视图/视图模型中使用您的控件:
<local:MyMediaElement />
是的,这就是您所需要的,因为您处理用户/自定义控件中的所有内容,您的视图模型不需要关心您如何处理控件中的设置。
有很多选项可以用来注册容器,我建议你看看文档:
https://simpleinjector.org/index.html
https://simpleinjector.readthedocs.io/en/latest/index.html