Moving methods from view to viewmodel - WPF MVVM

My code has the following code:

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

I would like to follow the MVVM pattern and move this code to my ViewModel, which currently has only a few properties. I read a lot of answers here and outside of StackOverflow on this topic, I downloaded some github projects to check how experienced programmers handle specific situations, but none of this seems to fix the confusion for me. I would like to see how my case can be reorganized to follow the MVVM pattern.

These are additional extension methods, as well as the ViewModel itself:

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

I tried to copy / paste the code as requested in the comments, all controls in the view code behind are created in XAML if you want to fully reproduce it.

+5
source share
5 answers

I recommend using Caliburn Micro. If you use this library, you can bind events as follows:

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

or the like

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

Visit your site for more advanced features:

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

+1
source

The idea is to have a property and a command in your virtual machine for each area of ​​the user interface that you want to update, or events that need to be processed, respectively.

Just by looking at your current code, I think it will be much easier for you (you can remove several event handlers) if you connect directly to the β€œSlider” property and attach it (two-way) to the property on your virtual machine. Whenever the user drags, you can see when the value is updated, and you can handle accordingly.

As for the β€œhidden” effect of your scrub strip, it can be much easier for you to simply connect to the visual state of your slider. Here are the styles and visual states.

EDIT:

 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 end time in whatever unit you're using (ie if I have a 120 second clip, this value would be 120 and my elapsed time would be hooked into an underlying TimeSpan.TotalSeconds) } } 

and on your slider:

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

I have some simple rules that I follow in XAML applications:

  • The ViewModel doesn't need to know about the view, so no UI related code will ever be found in the ViewModel
  • All code associated with the UI is in the code (xaml.cs)
  • User controls and dependency properties are your best friends, so use them. The view should consist of custom controls, each of which has its own ViewModel.
  • Add your dependencies through constructor injection so that you can make fun of them when writing unit tests.
+1
source

You should not have mouse handlers in your viewmodel. These events are related to the user interface and therefore to the view. Instead, move the bloated view code to the attached behavior . From the behavior that you can optionally invoke in your viewing model through interfaces. For instance:.

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

you cannot use events in viewmodel. So you will need to create a command template class and just create a viewmodel class. After that, you can use the viewmodel namespace in the xml file or view the file using the "xmlns tag" and create a resource for the class and specify the value of the full key name. And set the datacontext to <Grid datacontext="nameofresource">. Now do the key binding.

Note. If you need to clean more, answer

-1
source

Source: https://habr.com/ru/post/1272974/


All Articles