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')
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.
Related
Can someone explain me why need to use implementation of INotifyPropertyChanged when using binding in wpf?
I can bind properties without implementation of this interface?
For example i have code
public class StudentData : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _firstName = null;
public string StudentFirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
OnPropertyChanged("StudentFirstName");
}
}
}
And binding in .xaml
<TextBox Text="{Binding Path=StudentFirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Center" />
this code from .xaml.cs
StudentData _studentData = new StudentData { StudentFirstName = "John", StudentGradePointAverage = 3.5};
public MainWindow()
{
InitializeComponent();
this.DataContext = _studentData;
}
why we need to use INotifyPropertyChanged in this case?
It is not my code.
You need INotifyPropertyChanged if you want a wpf form to be automatically updated when a property changes through code. Also some controllers might want to know if edits have been made in order to enable/disable a save-button, for instance. You also might be displaying the same property on different views; in this case INotifyPropertyChanged helps to immediately update the other view when you edit a property.
If you think that your form behaves well without INotifyPropertyChanged, then you can drop it.
Note that binding works even without INotifyPropertyChanged. See: Why does the binding update without implementing INotifyPropertyChanged?
I would implement the properties like this. In some rare cases it can help to avoid endless circular updates. And it is more efficient by the way.
private string _firstName;
public string StudentFirstName
{
get { return _firstName; }
set
{
if (value != _firstName) {
_firstName = value;
OnPropertyChanged("StudentFirstName");
}
}
}
Starting with C#6.0 (VS 2015), you can implement OnPropertyChanged like this:
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When you bind to a property of StudentData such as the StudentFirstName then the binding class tests to see if the StudentData instance provides the INotifyPropertyChanged interface. If so then it will hook into the PropertyChanged event. When the event fires and it fires because of the StudentFirstName property then it knows it needs to recover the source value again because it has changed. This is how the binding is able to monitor changes in the source and reflect them in the user interface.
If you do not provide the INotifyPropertyChanged interface then the binding has no idea when the source value changes. In which case the user interface will not update when the property is changed. You will only see the initial value that was defined when the binding was first used.
It does need to be implemented in order for binding to work but that doesn't mean you always have to do it yourself. There are other options like Castle Dynamic Proxy (which wraps your classes in a proxy and injects INPC into all virtual properties) and Fody (which adds it to the IL in a post-processing step). It's also possible to implement yourself while at the same time reducing code bloat, as demonstrated in my answer to this question.
I have a usercontrol with a single Textbox inside a grid like this:
<TextBox Text="{Binding Username}"></TextBox>
The code-behind of the usercontrol implements INotifyDataErrorInfo and INotifyPropertyChanged. This is how my code-behind looks like (Apart from the above mentioned interface implementations)
public TestControl()
{
InitializeComponent();
this.DataContext = this;
}
private string _username;
public string Username
{
get { return _username; }
set
{
_username = value;
if (_username.Length < 3)
SetErrors("Username", new List<string> { "Usernames should be at least 3 characters long" });
OnPropertyChanged("Username");
}
}
Where SetErrors is just a function which adds an error to the IEnumerable which the INotifyDataErrorInfo.GetErrors will return. This works pretty well. When I write text less than 3 characters, the textbox turns red. That is exactly what I expect.
Now I want the MainWindow's viewmodel to set this textbox. To do that, the Username field should be a dependency property so I can bind to it. But the problem is that I can't validate it now. I made a dependency property and tried validating it at the ValidateValueCallback but INotifyDataErrorInfo members are not static. So I can't reach them. What should I do?
Place Username within MainViewModel and inside of UserControl bind to it using RelativeSource binding like
"{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.Username}"
You can replace explicit name indication with CallerMemberName attribute.
EDIT
When you define dependency property you can determine event which will be raised whenever value's change occurs. As a parameter it has reference to class, in which it is defined, as far as your project is concerned it would be your UserControl. You can then invoke any method on this object, no need to be static at all. Following code depicts the concept, not precise solution, adjust it to your requirement:
public static readonly DependencyProperty PropertyTypeProperty = DependencyProperty.Register(
"PropertyType", typeof (propertyType), typeof (PasswordBoxDP), new PropertyMetadata((x, y) =>
{
var userControlClass = x as UserControlClass;
userControlClass.Validate();
}));
private void Validate()
{
}
By the way, binding in your case will not be working. You defined DataContext refering to itself so when you set a binding on your dependency property it will kick off seeking within UserControl.
I need to make list of items. I binded collection of users to listbox. Everything works quite well, but items in listbox aren't updated in real time. They aren't updated at all by this binding. So when I remove any user from the list, listbox isn't updated even if its source is correctly changed.
Source is located in data view model at path DataViewModel.Instance.AllUsers; Whenever I add to this list new item or remove one, layout does not update. Other bindings work well. I tried to update listbox layout, to raise event of source update, other way of adding/removing items, but nothing worked.
I tried to debug binding, but I have too many bindings to find the error.
Thanks in advance for any useful advice.
Listbox:
<ListBox x:Name="ListboxUsers" ItemsSource="{Binding Path=AllUsers, Mode=OneWay}" Grid.Column="1" Margin="0" Grid.Row="5" Background="DimGray" BorderThickness="0" Visibility="Hidden" SelectionChanged="ListboxUsers_SelectionChanged"/>
Code-behind:
CatalogueGrid.DataContext = DataViewModel.Instance; //whole view model added as datacontext
DataViewModel class:
public class DataViewModel : INotifyPropertyChanged
{
private static DataViewModel _dataViewModel;
private Collection<UserModel> allUsers;
public Collection<UserModel> AllUsers
{
get
{
return allUsers;
}
set
{
allUsers = value;
NotifyPropertyChanged("AllUsers");
}
}
private DataViewModel()
{
AllUsers = new Collection<UserModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
.
.
.
}
use ObservableColLection instead if Collection wich implements the INotifyCollectionChanged Interface :
private ObservableCollection<UserModel> allUsers;
public ObservableCollection<UserModel> AllUsers
{
get
{
return allUsers;
}
set
{
allUsers = value;
NotifyPropertyChanged("AllUsers");
}
}
For changes to the collection to propagate to the UI, the collection class needs to implement INotifyCollectionChanged.
A very useful class that already implements this is ObservableCollection<T> (MSDN). Use that instead of Collection<T>.
You have bound your listbox to a Collection<T> - that is just a list, which does not issue any notifications to bound properties that its contents has changed. Hence, your listbox cannot possibly know when the collection has changed.
Instead, you can use the ObservableCollection<T> class (or, more precisely, any collection that also implements INotifyCollectionChanged), and changes will be automatically propagated to the listbox.
Note that your property does not have to be typed as ObservableCollection<T>, you can also just declare your public property as IEnumerable<T> or IList<T>; the binding will find out on its own whether the returned class also implements INotifyCollectionChanged. Like this, you are free to replace your actual underlying collection class later on, for example with a ReadOnlyObservableCollection<T>, in case you want to disallow changes from the outside.
Speaking of this, a note on your code: You have provided your AllUsers property with a setter. This may lead to undesired consequences, as you open up possibilities for some other code to set the property to null, which (depending on the rest of your code) might lead to exceptions. Unless you actually want to allow assigning new values, for the ItemsSource property binding, a read-only property is fully sufficient, as long as the returned collection object implements INotifyCollectionChanged.
I made a little Project to learn a bit about MVVM. It is a calculator that calculates when you are allowed to go home from work.
I made a UserControl with two Textboxes and a single label as a simple "TimePicker". This Usercontrol has a ViewModel (the Mainwindow even has one) which manages the time of one Timepicker. It has three Properties: an int called TimeValue which is just the value of hours and minutes and two ints called Hours and Minutes. My two Textboxes are bound to them and display them. Setting one value via Textbox also resets Time, setting time (via Property) resets Hours and Minutes, both textboxes are updated after setting this value.
This works quit fine. Now I wanted to add a second Property called ReadOnly. ReadOnly is needed for the TimePicker which displayes the time to go. It makes no sense to manually set this time so I want to have a possibility to set both Textboxes IsReadOnly Property.
ReadOnly is now a second Property of the UserControl. Because I am lazy I wanted to directly bind the Property and both Textboxes via the UserControl and bind only the IsReadOnly-Property to the UserControl.
This was the Code of my Idea (Usercontrol):
public partial class TimeBox : UserControl, INotifyPropertyChanged
{
private SingleTimeViewModel viewModel;
//... other Properties
public static DependencyProperty ReadOnlyProperty = DependencyProperty.Register("ReadOnly", typeof(Boolean), typeof(TimeBox), new PropertyMetadata(false));
// Schnittstellen-Ereignis
public event PropertyChangedEventHandler PropertyChanged;
protected internal void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
public TimeBox()
{
InitializeComponent();
viewModel = new SingleTimeViewModel(SingleTime.CreateSingleTime());
this.DataContext = viewModel;
}
//... Code of other Properties
private bool _ReadOnly;
public bool ReadOnly
{
get
{
return _ReadOnly;
}
set
{
if (_ReadOnly == value)
return;
_ReadOnly = value;
OnPropertyChanged("ReadOnly");
}
}
//... Other Methods
}
This was bound to both Textboxes via XAML (Bindings for Text lead to ViewModel, IsReadOnly should bind to TimeBox):
<UserControl x:Name="TimeBoxControl" x:Class="TimeCalculator.TimeBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
... >
<WrapPanel Grid.Column="7" HorizontalAlignment="Stretch" Margin="0,0,0,0" Grid.Row="1" VerticalAlignment="Center" >
<TextBox x:Name="txtBxHours" ... Text="{Binding Hours}" ... IsReadOnly="{Binding ReadOnly, ElementName=TimeBoxControl}" />
<Label x:Name="lblSeparator" ... />
<TextBox x:Name="txtBxMinutes" ... Text="{Binding Minutes}" ... IsReadOnly="{Binding ReadOnly, ElementName=TimeBoxControl}" />
</WrapPanel>
</UserControl>
I made the values readonly at the Constructor of the Mainwindow of my Project after InitializeComponent. Therefore I used the following lines:
this.TmBxMayGo.ReadOnly = true;
this.TmBxMustGo.ReadOnly = true;
this.TmBxTimeUntilMayGo.ReadOnly = true;
this.TmBxTimeUntilMustGo.ReadOnly = true;
this.TmBxCurrentOvertime.ReadOnly = true;
This did not work, after some debugging I found out it did not because PropertyChangedEventHandler PropertyChanged was always null.
I searched a lot to find a solution for this problem, but I made none of the common mistakes (e. g. forgot : INotifyPropertyChanged, wrong names in Strings or else).
I finally gave up and made it via ViewModel. But then I realised that PropertyChangedEventHandler PropertyChanged was also null when I set it via ViewModel, but the textboxes were ReadOnly after calling.
Now the two questions I have:
Does it make sense to make an own ViewModel for an single Usercontrol?
Why is that so? How can it be that PropertyChanged is null twice but only works once?
Yes, it makes sense to have a single ViewModel for the single standlone logically seperated UI. It divides the responsibility from the mainviewmodel. So put all your properties in your ViewModel.
WPF only attaches the handler to the PropertyChanged event of the INotifyPropertyChanged object when that object is set as the DataContext of the view that is why your PropertyChanged is null before you set the DataContext of your usercontrol. And your textboxes are still disabled because while initializing the binding, getter of these properties are called and UI is updated with the default values you gave.
a cleaner approach for doing the mentioned would be something like this
public bool ReadOnly
{
get { return (bool)GetValue(ReadOnlyProperty); }
set { SetValue(ReadOnlyProperty, value); }
}
// Using a DependencyProperty as the backing store for ReadOnly. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ReadOnlyProperty =
DependencyProperty.Register("ReadOnly", typeof(bool), typeof(TimeBox), new PropertyMetadata(false, null, CoerceReadOnly));
private static object CoerceReadOnly(DependencyObject d, object baseValue)
{
if ((d as TimeBox)._Enabled == baseValue)
return DependencyProperty.UnsetValue;
return baseValue;
}
here I am using Coerce Value callback to check the condition, so by returning DependencyProperty.UnsetValue will cancel the property change otherwise it will proceed
more info on Coerce here
http://msdn.microsoft.com/en-us/library/ms745795(v=vs.110).aspx#Advanced
for other question, I would say you may probably not require to create a usercontrol unless you want to deploy a control library. Do try to leverage data templates for the same.
and secondly since you are binding to a usercontrol which work on dependency framework the regular notifications may not work as expected.
I'm fairly new to WPF and I have some problems getting databinding to work as I want. I've written a user control which contains a TextBox whose Text-Property I want to bind to a property of my UserControl, which I want to bind again to something else.
What am I missing?
XAML
<!-- User Control -->
<TextBox Text="{Binding Path=TheText}" />
<!-- Window -->
<WpfApplication1:SomeControl TheText="{Binding Path=MyStringProp}" />
C#
// User Control ----
public partial class SomeControl : UserControl
{
public DependencyProperty TheTextProperty = DependencyProperty
.Register("TheText", typeof (string), typeof (SomeControl));
public string TheText
{
get
{
return (string)GetValue(TheTextProperty);
}
set
{
SetValue(TheTextProperty, value);
}
}
public SomeControl()
{
InitializeComponent();
DataContext = this;
}
}
// Window ----
public partial class Window1 : Window
{
private readonly MyClass _myClass;
public Window1()
{
InitializeComponent();
_myClass = new MyClass();
_myClass.MyStringProp = "Hallo Welt";
DataContext = _myClass;
}
}
public class MyClass// : DependencyObject
{
// public static DependencyProperty MyStringPropProperty = DependencyProperty
// .Register("MyStringProp", typeof (string), typeof (MyClass));
public string MyStringProp { get; set; }
// {
// get { return (string)GetValue(MyStringPropProperty); }
// set { SetValue(MyStringPropProperty, value); }
// }
}
Best RegardsOliver Hanappi
PS: I've tried to implement the INotifyPropertyChanged interface on my user control, but it did not help.
You want to bind the Text property of your TextBox back to the TheText property of the UserControl it lives in, right? So you need to tell the binding where the property lives. There's a couple of ways to do this (you can do it with a RelativeSource using FindAncestor) but the easiest way is to give the UserControl a "name" in the XAML and bind using element binding:
<UserControl ...
x:Name="me" />
<TextBox Text="{Binding TheText,ElementName=me}" />
</UserControl>
Now your TextBox will reflect the value you've assigned (or bound) to your "SomeControl.TheText" property - you needn't change any of your other code, although you'll probably want to implement INotifyPropertyChanged on your underlying MyClass object so that the binding knows when the property has changed.
Matt has provided a solution to your problem. Here is a little more explanation and a hint to stop this problem in future.
As SomeControl.DataContext is set in the SomeControl constructor, the window's binding TheText="{Binding Path=MyStringProp}" has a Source of type SomeControl, not MyClass as you intended.
Any bindings that fail at runtime cause debug messages to be logged to the output panel of Visual Studio. In this case, you would have seen that no such property 'MyStringProp' exists on object of type 'SomeControl', which should have raised your suspicions.
I think everyone finds WPF data binding takes some time to learn and especially to debug, but persevere. Data binding in WPF is really fantastic, and I still get a kick out of knowing how easily it makes the data on my UIs stay up to date.