Dea*_*one 5 c# wpf settings binding
我正在为我的媒体播放器制作用户可更改的设置,而我正在努力找到解决问题的优雅方案.
例如,我的一个设置 - 暂停视频的最后一帧,如果没有选中,它将继续通过播放列表,或者只有1个文件,重置它并在开始时暂停.
这就是我实现它的方式:
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()));
}
Run Code Online (Sandbox Code Playgroud)
它包含在主窗口的ViewModel中,其中media元素是GeneralSettings.PauseOnLastFrame
布尔属性.
此命令绑定如下:
<MediaElement ....>
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="MediaEnded">
<ia:InvokeCommandAction Command="{Binding MediaEndedCommand}"/>
</ia:EventTrigger>
</ia:Interaction.Triggers>
</MediaElement>
Run Code Online (Sandbox Code Playgroud)
它有效,但很糟糕,我该如何以优雅的方式实现这样的设置系统呢?某些设置可能不是布尔值,它们可能有多个选项,有些可能仅在启动时应用,而其他设置如上图所示,基于事件.
根据您提供的信息和示例代码,我建议
方法 - 1
与ViewModel紧密结合System.Configuration.ApplicationSettingsBase
,您可以在ViewModel中提及所有属性,并使用单独的应用程序设置属性映射其中的一个属性.您可以在之后直接使用您的设置,例如:{x:Static Settings.Default.Whatevs}
.在"保存"按钮单击事件或主窗口关闭事件中,您可以保存所有设置,例如:Settings.Default.Save();
方法 - 2
一个更好的办法,我建议/希望(如果我开发这个程序)是开发一个包装类(如:SettingProvider
)实现继承(如:ISettingProvider
),它揭示了所有你的设置单独的属性,也有一个保存方法,保存所有设置值.您可以将此包装类用于ViewModel,以更好的方式处理所有命令和设置值.
这种方法的好处是,如果您决定将设置更改为数据库,则无需对ViewModel进行更改,因为所有作业都是在SettingProvider
课堂上完成的.
我不确定但基于查看您的代码,我假设您使用了Approach-1.请将您的评论和反馈意见发给您.我想知道你的想法,也许你有更简单有趣的方法来实现这一目标.
UPDATE-1
例
枚举为您展示演示
public enum MediaStatus
{
Playing = 0,
Stopped = 1,
Paused = 2
}
Run Code Online (Sandbox Code Playgroud)
接口
public interface ISettingProvider
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
Run Code Online (Sandbox Code Playgroud)
包装类
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;
}
}
Run Code Online (Sandbox Code Playgroud)
ViewModelBase类
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Run Code Online (Sandbox Code Playgroud)
CommandHandler类
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);
}
}
Run Code Online (Sandbox Code Playgroud)
视图模型
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();
}
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
注意:这是我放在这里的详细示例,如果我错过了某处,也可以避免一些语法错误或命名错误.请更正它.我不知道您正在使用哪种媒体播放器设置.我拿了一些样本属性.这只是您可以为您的应用程序实现的结构示例.您可能需要更改更多代码才能实现此结构.
实现此恕我直言的一种优雅方法是使用依赖项注入容器,这将提供极大的灵活性,同时允许您完全分离关注点(即设置实现与视图模型和自定义控件)。
有很多 DI 框架,对于我的示例,我将使用简单的注入器,因为它是免费(开源)、简单且快速的,但您可以将相同的原理应用于其他框架(Unity、Ninject 等)。
首先为您的设置服务创建一个界面,例如:
public interface ISettingsService
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
Run Code Online (Sandbox Code Playgroud)
然后添加您的服务实现,使用 DI 的好处在于您可以随时更改实现或完全替换它,并且您的应用程序将继续照常工作。
假设您想使用应用程序设置,这是您的服务:
public class SettingsServiceFromApplication : ISettingsService
{
public double Volume
{
get
{
return Properties.Settings.Volume;
}
}
[...]
}
Run Code Online (Sandbox Code Playgroud)
或者假设您想使用数据库来存储您的设置:
public class SettingsServiceFromDb : ISettingsService
{
public double Volume
{
get
{
return MyDb.Volumen;
}
}
[...]
}
Run Code Online (Sandbox Code Playgroud)
然后您可以使用 DI 容器来指定要使用的实现:
首先使用 NuGet 安装库:
Install-Package SimpleInjector -Version 4.0.12
Run Code Online (Sandbox Code Playgroud)
您需要一种在整个应用程序中共享容器的方法,我通常只使用在启动应用程序时初始化的静态类:
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
}
}
Run Code Online (Sandbox Code Playgroud)
启动App时需要调用Setup,最好的地方是在App构造函数中:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper.Setup();
}
}
Run Code Online (Sandbox Code Playgroud)
现在您有了一个应用程序范围的依赖注入容器,您可以使用它来请求“服务”(接口的特定实现)。
要在视图模型中获取设置实现,您可以简单地调用容器,如下所示:
// This will return an instance of SettingsServiceFromApplication
ISettingsService settingsService = Bootstrapper.Container.GetInstance<ISettingsService>();
double volumen = settingsService.Volume;
Run Code Online (Sandbox Code Playgroud)
为了使其更易于使用,我通常创建一个基本视图模型,以便更轻松地获取服务,例如:
public abstract BaseViewModel
{
private ISettingsService _settings;
protected ISettingsService GeneralSettings
{
get
{
if (_settings == null)
_settings = Bootstrapper.Container.GetInstance<ISettingsService>();
return _settings;
}
}
}
Run Code Online (Sandbox Code Playgroud)
从此类继承的每个视图模型都可以访问以下设置:
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()));
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,该代码与您的代码相同!但现在设置来自您的容器。优雅在哪里?好吧,假设一年后有人决定您将设置存储在数据库中,您需要在代码中更改哪些内容?
Container.Register<ISettingsService, SettingsServiceFromDb>();
Run Code Online (Sandbox Code Playgroud)
单行。其他一切都应该照常进行。
除了视图模型之外,您还可以在自己的控件中使用此机制:
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
[...]
}
}
Run Code Online (Sandbox Code Playgroud)
然后只需在视图/视图模型中使用您的控件:
<local:MyMediaElement />
Run Code Online (Sandbox Code Playgroud)
是的,这就是您所需要的,因为您处理用户/自定义控件中的所有内容,您的视图模型不需要关心您如何处理控件中的设置。
您可以使用许多选项来注册容器,我建议您查看文档:
https://simpleinjector.org/index.html
https://simpleinjector.readthedocs.io/en/latest/index.html
Run Code Online (Sandbox Code Playgroud)