0
votes

I'm new to WPF and xaml and I'm making a video player. I'm currently trying to bind the movie time slider to the current elapsed time which I store in a TimeSpan variable which is updated every second thru a DispatcherTimer.

This is my xaml:

<customControls:ThumbDragSlider x:Name="sMovieSkipSlider" Height="25" Margin="65,0,65,71" VerticalAlignment="Bottom"
        Value="{Binding ElementName=_movieElapsedTime, Path = TotalSeconds, Mode=OneWay}"
        DragStarted="SMovieSkipSlider_OnDragStarted"
        DragCompleted="SMovieSkipSlider_OnDragCompleted"/>

This is how the variable is declared:

private TimeSpan _movieElapsedTime;

And this is the error I'm getting:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=_movieElapsedTime'. BindingExpression:Path=TotalSeconds; DataItem=null; target element is 'ThumbDragSlider' (Name='sMovieSkipSlider'); target property is 'Value' (type 'Double')

1

1 Answers

1
votes

ElementName is used to refer to an element in the XAML. If you had a control such as...

<TextBox x:Name="_movieElapsedTime" />

Then it would make sense the way you have it -- if it happened to have a property named TotalSeconds.

You also can't bind to a field; it has to be either a regular C# property with a get and maybe a set, or else a special kind of property called a dependency property.

But let's do this with a viewmodel. A viewmodel is any random class that implements INotifyPropertyChanged, and raises the PropertyChanged event when its property values change. It keeps track of the internals of your application. It isn't aware that a user interface exists. It exposes data properties like MovieElapsedTime, and may expose commands as well which allow buttons or menu items to send orders to the viewmodel. It may also have methods that its parent viewmodel may call.

We'll write a viewmodel base class that implements INotifyPropertyChanged, and derive a simple viewmodel from it that represents the things that a video player needs to know. Then we'll create a UI in XAML that lets the user interact with it.

You'll probably want the viewmodel to have commands to start and stop the video and so on. That's easy to find on Google. I'd recommend using a RelayCommand/DelegateCommand class; google those and you'll see what they do. There are a lot of examples out there you can steal the code for.

#region ViewModelBase Class
public class ViewModelBase : INotifyPropertyChanged
{
    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    #endregion INotifyPropertyChanged
}
#endregion ViewModelBase Class

#region VideopPlayerViewModel Class
public class VideopPlayerViewModel : ViewModelBase
{
    #region MovieElapsedTime Property
    private TimeSpan _movieElapsedTime = default(TimeSpan);
    public TimeSpan MovieElapsedTime
    {
        get { return _movieElapsedTime; }
        set
        {
            if (value != _movieElapsedTime)
            {
                _movieElapsedTime = value;
                OnPropertyChanged();
            }
        }
    }
    #endregion MovieElapsedTime Property
}
#endregion VideopPlayerViewModel Class

MainWindow constructor:

    public MainWindow()
    {
        InitializeComponent();

        DataContext = new VideoPlayerViewModel();
    }

XAML. Because A VideoPlayerViewModel is the DataContext for our window, that means that when you tell a binding to bind to MovieElapsedTime, with no further information about where to find it, it will go to the DataContext object it inherited from the window.

<customControls:ThumbDragSlider 
    Value="{Binding MovieElapsedTime.TotalSeconds, Mode=OneWay}"
    x:Name="sMovieSkipSlider" 
    Height="25" 
    Margin="65,0,65,71" 
    VerticalAlignment="Bottom"
    DragStarted="SMovieSkipSlider_OnDragStarted"
    DragCompleted="SMovieSkipSlider_OnDragCompleted"/>

Non-MVVM version

Here's the dependency property version. It's not "the right way to do it" but it's not totally awful.

Next question: What is MovieElapsedTime a member of? The Window? What is the DataContext? If you set DataContext = this and implemented INotifyPropertyChanged on your window, and raise PropertyChanged when MovieElapsedTime changes, that's a bad idea for other reasons, but your binding will work with MovieElapsedTime as a conventional property. If not, you need this:

<customControls:ThumbDragSlider 
    Value="{Binding MovieElapsedTime.TotalSeconds, Mode=OneWay, RelativeSource={RelativeSource AncestorType=Window}}"
    x:Name="sMovieSkipSlider" 
    Height="25" 
    Margin="65,0,65,71" 
    VerticalAlignment="Bottom"
    DragStarted="SMovieSkipSlider_OnDragStarted"
    DragCompleted="SMovieSkipSlider_OnDragCompleted"/>

Window codebehind:

    public TimeSpan MovieElapsedTime
    {
        get { return (TimeSpan)GetValue(MovieElapsedTimeProperty); }
        set { SetValue(MovieElapsedTimeProperty, value); }
    }

    public static readonly DependencyProperty MovieElapsedTimeProperty =
        DependencyProperty.Register(nameof(MovieElapsedTime), typeof(TimeSpan), typeof(MainWindow),
            new PropertyMetadata(null));

Define the property that way instead of what you have. With all that stuff, the control will receive notifications automatically when you set the property to a new value.


You should really write a viewmodel which implements INotifyPropertyChanged and make this a property of the viewmodel. We can go through that if you're interested.


I think you'll need to poll more than once a second, though, if you want the update to be smooth. More likely every 500 ms or even 250. Try 500 and see how it looks.