WPF应用程序中的条件设置

时间:2018-02-07 13:21:08

标签: c# wpf binding settings

我正在为我的媒体播放器制作用户可更改的设置,而我正在努力找到解决此问题的优雅解决方案。

我的一个设置例如 - 暂停视频的最后一帧,如果没有选中,它将继续通过播放列表,或者如果只有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()));
}

它包含在主窗口的ViewModel中,其中media元素是,GeneralSettings.PauseOnLastFrame是布尔属性。

此命令绑定如下:

<MediaElement ....>
    <ia:Interaction.Triggers>
        <ia:EventTrigger EventName="MediaEnded">
            <ia:InvokeCommandAction Command="{Binding MediaEndedCommand}"/>
        </ia:EventTrigger>
    </ia:Interaction.Triggers>
</MediaElement>

它有效,但很糟糕,我应该如何以优雅的方式实施这样的设置系统?某些设置可能不是布尔值,它们可能有多个选项,有些可能仅在启动时应用,而其他设置如上图所示,基于事件。

3 个答案:

答案 0 :(得分:5)

根据您提供的信息和示例代码,我建议

方法 - 1

System.Configuration.ApplicationSettingsBase紧密结合的ViewModel,您可以在ViewModel中提及所有属性,并使用单独的应用程序设置属性映射其中的一个属性。您可以在之后直接使用您的设置,例如:{x:Static Settings.Default.Whatevs}。 Othe&#34; Save&#34;按钮点击事件或主窗口关闭事件,您可以保存所有设置,例如:Settings.Default.Save();

方法 - 2

更好的方法,我建议/更喜欢(如果我正在开发这个应用程序)是开发一个实现继承的包装类(例如:SettingProvider)(例如:ISettingProvider)您将所有设置设置为单独的属性,并且还具有保存所有设置值的保存方法。您可以将此包装类用于ViewModel,以更好的方式处理所有命令和设置值。

这种方法的好处是,如果您决定将设置更改为数据库,则无需对ViewModel进行更改,因为所有作业都在SettingProvider类中完成。

我不确定但基于查看您的代码,我认为您使用了 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();
}

包装类

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类

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propName)
    {
        if (PropertyChanged != null)
        {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}

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);
    }
}

<强>视图模型

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();
        }
    }
}

注意:这是我放在这里的详细示例,如果我错过某处,也可以避免一些语法错误或命名错误。请更正它。 我不知道您正在使用哪种媒体播放器设置。我拿了一些样本属性。这只是您可以为您的应用程序实现的结构示例。您可能需要更改更多代码才能实现此结构。

答案 1 :(得分:4)

实现此IMHO的一种优雅方式是使用依赖注入容器,这将提供极大的灵活性,同时允许您完全分离关注点(即视图模型和自定义控件中的设置实现)。

有许多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 

您需要一种在整个应用程序中共享容器的方法,我通常只使用我在启动应用程序时初始化的静态类:

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();
    }
}

所以现在你有一个app wide dependdency注入容器,你可以用来请求&#34;服务&#34; (接口的具体实现)。

要在视图模型中获取设置实现,您只需按如下方式调用容器:

// 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;
         }
    }
}

从此类继承的每个视图模型都可以访问设置:

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
       [...]
     }
 }

然后在Views / ViewModels中使用你的控件:

<local:MyMediaElement />

是的,这就是您所需要的一切,因为您处理了用户/自定义控件中的所有内容,您的视图模型并不需要关心如何处理控件中的设置。

您可以使用许多选项来注册容器,我建议您查看文档:

https://simpleinjector.org/index.html
https://simpleinjector.readthedocs.io/en/latest/index.html

答案 2 :(得分:2)

我想也许您正在寻找一种界面方法?

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()));
}

这样,例如,如果你的设置是。如果是enum而不是bool,则可以为每个可能的值指定接口的不同实现。