将方法从视图移动到viewmodel - WPF MVVM

时间:2017-10-29 12:12:43

标签: c# wpf mvvm view viewmodel

我的代码背后有以下代码:

public partial class MainWindow
{
    private Track _movieSkipSliderTrack;
    private Slider sMovieSkipSlider = null;
    private Label lbTimeTooltip = null;
    private MediaElement Player = null;

    public VideoPlayerViewModel ViewModel
    {
        get { return DataContext as VideoPlayerViewModel; }
    }

    public MainWindow()
    {
        InitializeComponent();
    }

    private void SMovieSkipSlider_OnLoaded(object sender, RoutedEventArgs e)
    {
        _movieSkipSliderTrack = (Track)sMovieSkipSlider.Template.FindName("PART_Track", sMovieSkipSlider);
        _movieSkipSliderTrack.Thumb.DragDelta += Thumb_DragDelta;
        _movieSkipSliderTrack.Thumb.MouseEnter += Thumb_MouseEnter;
    }

    private void Thumb_MouseEnter(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed && e.MouseDevice.Captured == null)
        {
            var args = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, MouseButton.Left)
            {
                RoutedEvent = MouseLeftButtonDownEvent
            };
            SetPlayerPositionToCursor();
            _movieSkipSliderTrack.Thumb.RaiseEvent(args);
        }
    }

    private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
    {
        SetPlayerPositionToCursor();
    }

    private void SMovieSkipSlider_OnMouseEnter(object sender, MouseEventArgs e)
    {
        lbTimeTooltip.Visibility = Visibility.Visible;
        lbTimeTooltip.SetLeftMargin(Mouse.GetPosition(sMovieSkipSlider).X);
    }

    private void SMovieSkipSlider_OnPreviewMouseMove(object sender, MouseEventArgs e)
    {
        double simulatedPosition = SimulateTrackPosition(e.GetPosition(sMovieSkipSlider), _movieSkipSliderTrack);
        lbTimeTooltip.AddToLeftMargin(Mouse.GetPosition(sMovieSkipSlider).X - lbTimeTooltip.Margin.Left + 35);
        lbTimeTooltip.Content = TimeSpan.FromSeconds(simulatedPosition);
    }

    private void SMovieSkipSlider_OnMouseLeave(object sender, MouseEventArgs e)
    {
        lbTimeTooltip.Visibility = Visibility.Hidden;
    }

    private void SetPlayerPositionToCursor()
    {
        Point mousePosition = new Point(Mouse.GetPosition(sMovieSkipSlider).X, 0);
        double simulatedValue = SimulateTrackPosition(mousePosition, _movieSkipSliderTrack);
        SetNewPlayerPosition(TimeSpan.FromSeconds(simulatedValue));
    }

    private double CalculateTrackDensity(Track track)
    {
        double effectivePoints = Math.Max(0, track.Maximum - track.Minimum);
        double effectiveLength = track.Orientation == Orientation.Horizontal
            ? track.ActualWidth - track.Thumb.DesiredSize.Width
            : track.ActualHeight - track.Thumb.DesiredSize.Height;
        return effectivePoints / effectiveLength;
    }

    private double SimulateTrackPosition(Point point, Track track)
    {
        var simulatedPosition = (point.X - track.Thumb.DesiredSize.Width / 2) * CalculateTrackDensity(track);
        return Math.Min(Math.Max(simulatedPosition, 0), sMovieSkipSlider.Maximum);
    }

    private void SetNewPlayerPosition(TimeSpan newPosition)
    {
        Player.Position = newPosition;
        ViewModel.AlignTimersWithSource(Player.Position, Player);
    }
}

我想遵循MVVM模式并将此代码移至我的ViewModel,目前只有少数属性。我已经在这里和StackOverflow之外阅读了很多关于这个主题的答案,我已经下载了一些github项目,以查看经验丰富的程序员如何处理特定的情况,但这些似乎都没有清除我的困惑。我想看看如何重构我的案例以遵循MVVM模式。

这些是额外的扩展方法,也是ViewModel本身:

static class Extensions
{
    public static void SetLeftMargin(this FrameworkElement target, double value)
    {
        target.Margin = new Thickness(value, target.Margin.Top, target.Margin.Right, target.Margin.Bottom);
    }

    public static void AddToLeftMargin(this FrameworkElement target, double valueToAdd)
    {
        SetLeftMargin(target, target.Margin.Left + valueToAdd);
    }
}

public class VideoPlayerViewModel : ViewModelBase
{
    private TimeSpan _movieElapsedTime = default(TimeSpan);
    public TimeSpan MovieElapsedTime
    {
        get { return _movieElapsedTime; }
        set
        {
            if (value != _movieElapsedTime)
            {
                _movieElapsedTime = value;
                OnPropertyChanged();
            }
        }
    }

    private TimeSpan _movieLeftTime = default(TimeSpan);
    public TimeSpan MovieLeftTime
    {
        get { return _movieLeftTime; }
        set
        {
            if (value != _movieLeftTime)
            {
                _movieLeftTime = value;
                OnPropertyChanged();
            }
        }
    }

    public void AlignTimersWithSource(TimeSpan currentPosition, MediaElement media)
    {
        MovieLeftTime = media.NaturalDuration.TimeSpan - currentPosition;
        MovieElapsedTime = currentPosition;
    }
}

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}

我已尝试按照注释中的要求准备好代码复制/粘贴,如果要完全复制它,则在XAML中创建View后面代码中的所有控件。

5 个答案:

答案 0 :(得分:1)

我们的想法是在您的VM中为您想要更新的UI的每个区域或需要处理的事件分别提供属性和命令。

如果你直接挂钩滑块的Value属性,只需看一下你当前的代码,我认为你将有一个更容易的时间(你将能够删除一些事件处理程序)将它(双向)绑定到VM上的属性。每当用户拖动时,您将能够看到值何时更新并且您可以相应地处理。

至于"隐藏"擦洗条的效果是,您可以更轻松地进入滑块的视觉状态。 Here are the styles and visual states.

编辑:

public class VideoPlayerViewModel : ViewModelBase
{
    // your existing properties here, if you decide that you still need them

    // this could also be long/double, if you'd like to use it with your underlying type (DateTime.TotalTicks, TimeSpan.TotalSeconds, etc.)
    private uint _elapsedTime = 0; //or default(uint), whichever you prefer
    public uint ElapsedTime
    {
        get { return _elapsedTime; }
        set
        {
            if (_elapsedTime != value)
            {
                _elapsedTime = value;
                //additional "time changed" logic here, if needed
                //if you want to skip programmatically, all you need to do is set this property!
                OnPropertyChanged();
            }
        }
    }

    private double _maxTime = 0;
    public double MaxTime
    {
        // you get the idea, you'll be binding to the media's end time in whatever unit you're using (i.e. if I have a 120 second clip, this value would be 120 and my elapsed time would be hooked into an underlying TimeSpan.TotalSeconds)
    }
}

并在滑块上:

Value={Binding ElapsedTime, Mode=TwoWay}
Maximum={Binding MaxTime, Mode=OneWay} //could also be OneTime, depending on the lifecycle of the control

答案 1 :(得分:1)

我建议使用Caliburn Micro。 如果您使用该库,则可以绑定以下事件:

<Button cal:Message.Attach="Save">

或者像那样

<Button cal:Message.Attach="[Event MouseEnter] = [Action Save]">

查看他们的网站以获得更多高级可能性:

https://caliburnmicro.codeplex.com/wikipage?title=Cheat%20Sheet

答案 2 :(得分:1)

我在XAML应用程序中遵循一些简单的规则:

  1. ViewModel不应该知道View,因此在ViewModel中找不到与UI相关的代码
  2. 所有与UI相关的代码都在后面的代码中(xaml.cs)
  3. 用户控件和依赖项属性是您最好的朋友,因此请使用它们。视图应由用户控件组成,每个控件都有自己的ViewModel。
  4. 通过构造函数注入注入依赖项,以便在编写单元测试时可以模拟它们

答案 3 :(得分:0)

您的viewmodel中不应该有鼠标处理程序。这些事件属于UI,因此属于视图。而是将膨胀的视图代码移动到attached behavior。根据行为,您可以选择通过接口调用viewmodel。 E.g:

var vm = AssociatedObject.DataContext as IPlayerViewModel;
vm?.AlignTimersWithSource(...);

答案 4 :(得分:-1)

您无法在viewmodel中使用事件。因此,您必须创建命令模式类,并创建viewmodel类。之后可以在xml文件中使用viewmodel的名称空间或使用“xmlns标签查看文件。并为类创建资源并提供完整的密钥名称。并设置datacontext in <Grid datacontext="nameofresource">.现在进行键绑定。

注意:如果您需要更多清除,请回复