Why is the binding on my custom control not working? - c#

I have a MainView where I'm using a component I created called VideoPlayer. VideoPlayer is a Grid that has a MediaElement as well as some buttons and a slider to control video playback.
I'm trying to set the path of the video to be played in MainView by binding a property in VideoPlayer to a property in MainView.
The relevant parts of the code are below:
Snippet from VideoPlayer.xaml:
<MediaElement
Name="MediaElement"
Grid.Row="0"
LoadedBehavior="Manual"
Stretch="Uniform"
Source="{Binding VideoLocation}" />
Snippet from VideoPlayer.xaml.cs:
public string VideoLocation
{
get { return (string)GetValue(VideoLocationProperty); }
set { SetValue(VideoLocationProperty, value); }
}
public static readonly DependencyProperty VideoLocationProperty
= DependencyProperty.Register("VideoLocation", typeof(string), typeof(VideoPlayer), new PropertyMetadata(null));
VideoPlayer is used in MainView.xaml, which is following MVVM:
<ReuseableComponents:VideoPlayer VideoLocation="{Binding VideoPath}"/>
This is the bound property, VideoPath, in MainViewModel.cs:
private string _videoPath;
public string VideoPath
{
get => _videoPath;
set => SetProperty(ref _videoPath, value);
}
If I remove the VideoLocation binding and hard code a path in MainView.xaml, the video will play just fine:
<ReuseableComponents:VideoPlayer VideoLocation="C:\Movies\FightClub.mp4"/>
So, I think the issue is with the MainView binding and not the VideoPlayer binding.
All of my other property bindings in MainView work, and they all follow this pattern:
<ComponentName PropertyName="{Binding ViewModelPropertyName}">
where ViewModelPropertyName is defined in MainView with a backing field and the setter calls SetProperty()
EDIT
The error I made was: in the constructor for VideoPlayer I had a line DataContext = this; I think I saw it in some tutorial somewhere. Anyway as per #Magnus advice I removed the line and changed VideoPlayer.xaml to
<UserControl x:Name="TheControl" ...>
<Grid DataContext={Binding ElementName=TheControl} ...>
...
</Grid>
</UserControl>
Where previously the UserControl didn't have the name property set and the Grid didn't have the DataContext property set.

The problem might be that you are setting the DataContext of your VideoPlayer UserControl to itself on the top level. That means you override the MainView DataContext that the control should inherit, i.e., this binding:
<ReuseableComponents:VideoPlayer VideoLocation="{Binding VideoPath}"/>
tries to bind to the VideoPlayer control instead of the MainView DataContext.
You could instead set the internal DataContext of your VideoPlayer on a contained element, something like this:
<UserControl x:Name="TheControl" ...>
<Grid DataContext={Binding ElementName=TheControl} ...>
...
</Grid>
</UserControl>

The Binding is being done relative to your control's DataContext, which you probably haven't set. You need to set it relative to the control instead. Give your control a name like this:
<UserControl x:Class="YourApp.YourUserControl"
... etc ...
x:Name="_this">
And then bind to that directly:
Source="{Binding ElementName=_this, Path=VideoLocation}"
Alternatively you can also use a RelativeSource binding with AncestorType set to your user control type.

Related

Dependency Property Datacontext

I have a usercontrol, and there is a Datacontext set for it. This usercontrol contains also a Dependency-Property. Now, i want simply bind to this property.
I think the problem has something to do with the wrong datacontext.
The dependency-Property in my usercontrol (called TimePicker) looks like this:
public TimeSpan Time
{
get { return (TimeSpan)GetValue(TimeProperty); }
set
{
SetValue(TimeProperty, value);
OnPropertyChanged();
}
}
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register("Time", typeof (TimeSpan), typeof (TimePicker));
I try to use it like this:
<upDownControlDevelopement:TimePicker Grid.Row="1" Time="{Binding Path=TimeValue}" />
When i do this i get the following binding error:
System.Windows.Data Error: 40 : BindingExpression path error: 'TimeValue' property not found on 'object' ''TimePicker' (Name='TimePickerControl')'. BindingExpression:Path=TimeValue; DataItem='TimePicker' (Name='TimePickerControl'); target element is 'TimePicker' (Name='TimePickerControl'); target property is 'Time' (type 'TimeSpan')
Any help would be highly appreciated
Greetings Michael
PS: you can download the code at here
Although this has now been solved there seems to be some, in my opinion, inappropriate use of the DataContext.
When developing a custom reusable control, you should not set DataContext at all. What the DataContext will be, that is for the user of the control to decide, not for the developer. Consider the following common pattern of code:
<Grid DataContext="{Binding Data}">
<TextBox Text="{Binding TextValue1}" />
<!-- Some more controls -->
</Grid>
Notice that here, you are using the Grid control. The developer of the control (in this case, the WPF team), didn't touch the DataContext at all - that is up to you. What does it mean for you as a control developer? Your DependencyProperty definition is fine, but you shouldn't touch the DataContext. How will you then bind something inside your control to the DependencyProperty value? A good way is using a template (namespaces omitted):
<MyTimePicker>
<MyTimePicker.Template>
<ControlTemplate TargetType="MyTimePicker">
<!-- Stuff in your control -->
<TextBlock Text="{TemplateBinding Time}" />
<TextBox Text="{Binding Time, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
<MyTimePicker.Template>
</MyTimePicker>
Note that TemplateBinding is always one-way only, so if you need any editing at all, you need to use normal binding (as you can see on the TextBox in the example).
This only means that the TextBlock/Box inside your control will get its Time value from your custom control itself, ignoring any DataContext you might have set.
Then, when you use the control, you do it like this (added to my first example):
<Grid DataContext="{Binding Data}">
<TextBox Text="{Binding TextValue1}" />
<!-- Some more controls -->
<MyTimePicker Time="{Binding TimeValue}" />
</Grid>
What just happened here is that the MyTimePicker does not have DataContext set anywhere at all - it gets it from the parent control (the Grid). So the value goes like this: Data-->(binding)-->MyTimePicker.Time-->(template binding)-->TextBlock.Text.
And above all, avoid doing this in the constructor of your custom control:
public MyTimePicker()
{
InitializeComponent();
DataContext = this;
}
This will override any DataContext set in XAML, which will make binding a huge pain (because you'll have to always set Source manually). The previous example would not work, and this wouldn't work either:
<MyTimePicker DataContext="{Binding Data}" Time="{Binding TimeValue}" />
You would think this is OK, but the DataContext will be resolved in the InitializeComponent() call, so the value will be immediately overwritten. So the binding to TimeValue will look for it in the control instead (which will, of course, fail).
Just don't touch the DataContext when developing a control and you'll be fine.
You don't need to override the data context of user control. You can use RelativeSource to point your binding source property i.e. TimeValue to any other source you like. E.g. If you have the source property in your window's class. You could simply point your binding target to the source in window's data context as follows:
{Binding Path=DataContext.TimeValue, RelativeSource={ RelativeSource AncestorType={x:Type Window}}}
Your error states that 'TimeValue' property not found on 'object' 'TimePicker', which means that the WPF Framework is looking at the 'TimePicker' object to resolve the 'TimeValue' property value. You must have somehow set the DataContext of the Window or UserControl that contains the 'TimePicker' object to an instance of the 'TimePicker' object.
Instead, it should be set to an instance of the class that declares the 'TimeValue' property. If you're using a view model, then you should set it to an instance of that:
DataContext = new YourViewModel();
If the 'TimeValue' property is declared in the Window or UserControl then you can set the DataContext to itself (although generally not recommended):
DataContext = this;
Please note that when data binding to the 'Time' property from inside your TimePicker control, you should use a RelativeSource Binding:
<TextBlock Text="{Binding Time, RelativeSource={RelativeSource
AncestorType={x:Type YourLocalPrefix:TimePicker}}}" ... />
Normally we are not setting datacontext directly.If u want to set datacontext create an instance of your usercontrol and set datacontext individually to each one.

How do you bind to a property in a xaml code-behind if the usercontrol's datacontext is a view-model?

I have a UserControl who's DataContext is being set to an instance of a ViewModel (using MVVM). But, I have controls within the UserControl which need to be bound to properties that only pertain to the view (which is why I placed them in code behind). I'm not sure how to bind this in xaml appropriately:
Note: SelectedOrderType is a property on the View-Model, and OrderTypes is a property on the UserControl itself.
<UserControl x:Class="MyNamespace.OrderControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="OrderUserControl">
<Grid>
...
<ComboBox ItemsSource="{Binding Path=OrderTypes, ElementName=OrderUserControl}"
SelectedValue="{Binding Path=SelectedOrderType}"
SelectedValuePath="OrderTypeCode"
DisplayMemberPath="OrderTypeName" />
</Grid>
</UserControl>
public partial class OrderControl : UserControl
{
public OrderControl()
{
InitializeComponent();
OrderTypes = ...;
}
public IReadOnlyCollection<OrderTypeInfo> OrderTypes { get; private set; }
}
Also, I know I can simply create a property on the View-Model, and I get that some people would suggest that that would be the correct place to put it... but I really would like to know how I could do what I'm attempting to do if not for this scenario, maybe for other scenarios in the future?
I may be wrong but would you not need to make a dependency property on your user control for "SelectedOrderType" and bind the the View Model to that property not bind directly to the view model from the user control.
That way your UserControl is not dependent on the view model?
Edit:
I think you could set it up the way you have it, but the binding for SelectedOrderType would need to be something like {Binding Path=DataContext.SelectedOrderType, ElementName=OrderUserControl}

Setting Image Source in a UserControl Button

i got an userControl, and i want to set the image button from the page in which userControl is included.
If i set my button image in the usercontrol in this way it works but i can't change.
<Button blablabla>
<Image Source="../../../Assets/truck-512.png"/>
</Button>
So i define a property from the codebehind
public ImageSource ButtonSource
{
get { return (ImageSource)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
// Using a DependencyProperty as the backing store for Source. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(ImageSource), typeof(TruckFormUC),null);
and in my userContol xaml i set my image as following
<Image x:Name="imgIcon" Source="{Binding Path=ButtonSource}"/>
than i use my userControl in my page and i try to set the image source, but it doesn't work
<UC:TruckFormUC x:Name="truckForm"
ButtonSource="../../../Assets/truck-512.png"/>
Thanks a lot
The userControl and the page are in the same folder.
Your binding is looking for ButtonSource property on the DataContext object. If you want to bind to your UserControl you should use relative binding.
<Image x:Name="imgIcon" Source="{Binding
RelativeSource={RelativeSource FindAncestor, AncestorType=UC:TruckFormUC},
Path=ButtonSource}"/>
See RelativeSource markup extension MSDN article for more info
You should also be able to use element binding.
<Image x:Name="imgIcon" Source="{Binding ElementName=truckForm, Path=ButtonSource}"/>

How to add variable Image on User Control

I'm trying to have a user control where an image is passed in from its containing element. The purpose is so that I can reuse a common set of visual elements while only changing the image. For example:
The control usage:
<DataTemplate DataType={x:Type myType}>
<local:MyControl PlotIconSource="..\Images\Scatter.png"/>
</DataTemplate>
The Image inside the control
<UserControl x:Class="MyControl">
<Image Source="{Binding PlotIconSource}"/>
</UserControl>
Finally the dependency property for PlotIconSource in the code-behind for MyControl.xaml.cs.
public ImageSource PlotIconSource
{
get { return (ImageSource)GetValue(PlotIconSourceProperty); }
set { SetValue(PlotIconSourceProperty, value); }
}
public static readonly DependencyProperty PlotIconSourceProperty =
DependencyProperty.Register(
"PlotIconSource",
typeof(ImageSource),
typeof(PlotHeader),
new UIPropertyMetadata());
I'm sure I've missed something along the way so any help would be appreciated.
You might want to bind via RelativeSource or with ElementName:
<UserControl x:Class="MyControl" Name="control">
<Image Source="{Binding PlotIconSource, ElementName=control}"/>
</UserControl>
(Do not set the DataContext, it will be invisible from the outside and mess with bindings meant for an inherited DataContext)
Looks right to me, are you getting an error message or something?

Usercontrol databinding property changed MVVM

I am working with WPF and using data binding.
I would like to make a UserControl which has a property that could be used for data binding.
Also, I want to update some other property in the UserControl if the property changed.
For example,
public class MyControl : UserControl
{
....
....
....
....
public ViewStyles CurrentView
{
get { return (ViewStyles)GetValue(CurrentViewProperty); }
set
{
SetValue(CurrentViewProperty, value);
UpdateView();
}
}
public static readonly DependencyProperty CurrentViewProperty = DependencyProperty.Register("CurrentView", typeof(ViewStyles), typeof(ComboView));
....
.....
.....
.....
}
Problems comes:
A ViewModel is used and in which, there is a property ViewStyle which binded to the CurrentView in the above.
Another control combobox is also data-binded with ViewStyle in the ViewModel.
Actually, I want to use a combobox to choose the different view of my control. How to make it possible in MVVM?
I tried the above method. However, the UI (the different ViewStyles of MyControl) didn't change. It only change when I click on it using the mouse.
Thank you.
XAML: (MyControl)
<Views:MyControl Grid.Column="1" Grid.Row="1" Height="505" HorizontalAlignment="Left" Margin="2,0,0,0" Name="comboView1" VerticalAlignment="Top" Width="983"
ViewStyle="{Binding Path=CurrentView}" BorderThickness="5" BorderBrush="Black" ItemsSource="{Binding Path=Images}"
SelectedIndex="{Binding Path=CurrentIndex}" Foreground="White"
</Views:MyControl>
XAML: (ComboBox)
<ComboBox Margin="0,3,1,0" Width="178" HorizontalAlignment="Right" Name="ViewDDBox" FontSize="13" Foreground="#FFF6F3F3" Background="#FF444444"
BorderThickness="2" Height="23" VerticalAlignment="Top" Grid.Column="1"
ItemsSource="{Binding Path=ViewTypes}" IsEnabled="True" SelectedValue="{Binding Path=CurrentView, Mode=TwoWay}">
</ComboBox>
It is supposed that the view (some UI effect) will be changed of MyControl after choosing in the Combobox. But now, it only change when I click on MyControl using mouse.
The UpdateView() in your CurrentView property setter raises a HUGE red flag! You should never have any content other than SetValue in a dependency property setter, as certain aspects of xaml call the SetValue directly instead of going through the property. Always use the coerce property callback (if you want to validate the data before it's set) or the property changed callback (if you want to act after the property is changed, as I show in the example below).
You should do this instead:
public static DependencyProperty CurrentViewProperty =
DependencyProperty.Register("CurrentView", typeof(ViewStyles), typeof(ComboView),
new FrameworkPropertyMetadata(CurrentViewPropertyChanged));
private static void CurrentViewPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyControl mc = (MyControl)d;
mc.UpdateView();
}
Instead of binding the view, why not create a templated control and then bind the control's view to the property on your viewmodel?
You may also have to use data template triggers on your template to get the desired functionality.
Check out this article for help on template basics and this one for a more in depth discussion.

Categories