I have a simple UserControl that displays an icon and text:
<UserControl x:Class="IconLabel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="26" d:DesignWidth="200" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Image x:Name="imgIcon" Source="{Binding Path=IconPath}" Stretch="UniformToFill" Width="26" Height="26" Margin="3,0" />
<Label Content="{Binding Path=LabelText}" Margin="5,0" Grid.Column="1" />
</Grid>
</UserControl>
The code-behind defines two DependencyProperties that are meant to be bound from the outside:
Public Class IconLabel
Public Property IconPath As String
Get
Return GetValue(IconPathProperty)
End Get
Set(ByVal value As String)
SetValue(IconPathProperty, value)
End Set
End Property
Public Shared ReadOnly IconPathProperty As DependencyProperty = DependencyProperty.Register("IconPath", GetType(String), GetType(IconLabel), New PropertyMetadata(""))
Public Property LabelText As String
Get
Return GetValue(LabelTextProperty)
End Get
Set(ByVal value As String)
SetValue(LabelTextProperty, value)
End Set
End Property
Public Shared ReadOnly LabelTextProperty As DependencyProperty = DependencyProperty.Register("LabelText", GetType(String), GetType(IconLabel), New PropertyMetadata("LabelText"))
End Class
That's working fine so far. I can set its properties in XAML and they are getting used properly:
<local:IconLabel LabelText="Test"/>
However, I'd now like to re-use this control in another UserControl that slightly expands its functionality by showing a progress bar next to it (I've kept this short for the sake of the example):
<UserControl x:Class="IconLabelProgress"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:myApp"
mc:Ignorable="d"
d:DesignHeight="26" d:DesignWidth="600" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*" MaxWidth="300"/>
<ColumnDefinition Width="6*"/>
</Grid.ColumnDefinitions>
<local:IconLabel IconPath="{Binding Path=IconPath}" LabelText="{Binding Path=PropName}" />
<ProgressBar Value="{Binding Path=ActualValue}" Minimum="0" Maximum="10" Margin="5" Height="16" VerticalAlignment="Top" Grid.Column="1" />
</Grid>
</UserControl>
with the following code-behind:
Public Class IconLabelProgress
'These are just meant to be passed along to the IconLabel
Public Property IconPath As String
Get
Return GetValue(IconPathProperty)
End Get
Set(ByVal value As String)
SetValue(IconPathProperty, value)
End Set
End Property
Public Shared ReadOnly IconPathProperty As DependencyProperty = DependencyProperty.Register("IconPath", GetType(String), GetType(IconLabelProgress), New PropertyMetadata(""))
Public Property PropName As String
Get
Return GetValue(PropNameProperty)
End Get
Set(ByVal value As String)
SetValue(PropNameProperty, value)
End Set
End Property
Public Shared ReadOnly PropNameProperty As DependencyProperty = DependencyProperty.Register("PropName", GetType(String), GetType(IconLabelProgress), New PropertyMetadata("PropName"))
'This one is new
Public Property ActualValue As Double
Get
Return GetValue(ActualValueProperty)
End Get
Set(ByVal value As Double)
SetValue(ActualValueProperty, value)
End Set
End Property
Public Shared ReadOnly ActualValueProperty As DependencyProperty = DependencyProperty.Register("ActualValue", GetType(Double), GetType(IconLabelProgress), New PropertyMetadata(0.0))
End Class
If I now try to instantiate this control and pass in a value for the label of the inner IconLabel control, like this:
<local:IconLabelProgress x:Name="ilp1" PropName="Test" ActualValue="5.0" />
then it won't show "Test" on its label and instead fall back to its default that was specified via PropertyMetadata("LabelText"). The ActualValue is used correctly, though.
How can I make the outer control pass the value to the nested one?
As a general rule, never explicitly set the DataContext property of a UserControl as you do with
<UserControl x:Class="IconLabel" ...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
Doing so effectively prevents inheriting a DataContext from the UserControl's parent, e.g. here
<local:IconLabel LabelText="{Binding Path=PropName}" ... />
where PropName is expected to be a property in the parent DataContext.
Instead of explicitly setting a UserControl's DataContext, write its "internal" Bindings with a RelativeSource like
<Label Content="{Binding Path=LabelText,
RelativeSource={RelativeSource AncestorType=UserControl}}" ... />
By default (and you didn't specify anything else) the binding is resolved from the objects DataContext. So your IconLabel searches a property with the name IconPath on its DataContext.
To specify that the place to search for the property is the outer control, you can add ElementName to the binding and set a name property on the IconLabelProgress or you specify a RelativeSource like in the second example of the accepted answer in How do I use WPF bindings with RelativeSource.
Hope it helps.
Related
I created a small File Browser Control:
<UserControl x:Class="Test.UserControls.FileBrowserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="44" d:DesignWidth="461" Name="Control">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Margin="3" Text="{Binding SelectedFile}" IsReadOnly="True" TextWrapping="Wrap" />
<Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />
</Grid>
</UserControl>
With the following code behind:
public partial class FileBrowserControl : UserControl
{
public ICommand BrowseCommand { get; set; }
//The dependency property
public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
public string SelectedFile { get{ return (string)GetValue(SelectedFileProperty);} set{ SetValue(SelectedFileProperty, value);}}
//For my first test, this is a static string
public string Filter { get; set; }
public FileBrowserControl()
{
InitializeComponent();
BrowseCommand = new RelayCommand(Browse);
Control.DataContext = this;
}
private void Browse()
{
SaveFileDialog dialog = new SaveFileDialog();
if (Filter != null)
{
dialog.Filter = Filter;
}
if (dialog.ShowDialog() == true)
{
SelectedFile = dialog.FileName;
}
}
}
And I use it like this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>
(SelectedFile is Property of the ViewModel of the usercontrol using this control)
Currently the issue is that when I click on Browse, the textbox in the usercontrol is correctly updating, but the SelectedFile property of the viewmodel parent control is not set(no call to the set property).
If I set the Mode of the binding to TwoWay, I got this exception:
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
So what did I do wrong?
The problem is that you set your UserControl's DataContext to itself in its constructor:
DataContext = this;
You should not do that, because it breaks any DataContext based Bindings, i.e. to a view model instance that is provided by property value inheritance of the DataContext property
Instead you would change the binding in the UserControl's XAML like this:
<TextBox Text="{Binding SelectedFile,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Now, when you use your UserControl and write a binding like
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />
the SelectedFile property gets bound to a SelectedFile property in your view model, which should be in the DataContext inherited from a parent control.
Do not ever set DataContext of UserControl inside usercontrol:
THIS IS WRONG:
this.DataContext = someDataContext;
because if somebody will use your usercontrol, its common practice to set its datacontext and it is in conflict with what you have set previously
<my:SomeUserControls DataContext="{Binding SomeDataContext}" />
Which one will be used? Well, it depends...
The same applies to Name property. you should not set name to UserControl like this:
<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />
because it is in conflict with
<my:SomeUserControls Name="SomeOtherName" />
SOLUTION:
In your control, just use RelativeSource Mode=FindAncestor:
<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />
To your question on how are all those third party controls done: They use TemplateBinding. But TemplateBinding can be used only in ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
In usercontrol the xaml represents Content of UserControl, not ControlTemplate/
Using this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...
The FileBrowserControl's DataContext has already been set to itself, therefore you are effectively asking to bind to the SelectedFile where the DataContext is the FileBrowserControl, not the parent ViewModel.
Give your View a name and use an ElementName binding instead.
SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"
I created a small File Browser Control:
<UserControl x:Class="Test.UserControls.FileBrowserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="44" d:DesignWidth="461" Name="Control">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Margin="3" Text="{Binding SelectedFile}" IsReadOnly="True" TextWrapping="Wrap" />
<Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />
</Grid>
</UserControl>
With the following code behind:
public partial class FileBrowserControl : UserControl
{
public ICommand BrowseCommand { get; set; }
//The dependency property
public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
public string SelectedFile { get{ return (string)GetValue(SelectedFileProperty);} set{ SetValue(SelectedFileProperty, value);}}
//For my first test, this is a static string
public string Filter { get; set; }
public FileBrowserControl()
{
InitializeComponent();
BrowseCommand = new RelayCommand(Browse);
Control.DataContext = this;
}
private void Browse()
{
SaveFileDialog dialog = new SaveFileDialog();
if (Filter != null)
{
dialog.Filter = Filter;
}
if (dialog.ShowDialog() == true)
{
SelectedFile = dialog.FileName;
}
}
}
And I use it like this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>
(SelectedFile is Property of the ViewModel of the usercontrol using this control)
Currently the issue is that when I click on Browse, the textbox in the usercontrol is correctly updating, but the SelectedFile property of the viewmodel parent control is not set(no call to the set property).
If I set the Mode of the binding to TwoWay, I got this exception:
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
So what did I do wrong?
The problem is that you set your UserControl's DataContext to itself in its constructor:
DataContext = this;
You should not do that, because it breaks any DataContext based Bindings, i.e. to a view model instance that is provided by property value inheritance of the DataContext property
Instead you would change the binding in the UserControl's XAML like this:
<TextBox Text="{Binding SelectedFile,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Now, when you use your UserControl and write a binding like
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />
the SelectedFile property gets bound to a SelectedFile property in your view model, which should be in the DataContext inherited from a parent control.
Do not ever set DataContext of UserControl inside usercontrol:
THIS IS WRONG:
this.DataContext = someDataContext;
because if somebody will use your usercontrol, its common practice to set its datacontext and it is in conflict with what you have set previously
<my:SomeUserControls DataContext="{Binding SomeDataContext}" />
Which one will be used? Well, it depends...
The same applies to Name property. you should not set name to UserControl like this:
<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />
because it is in conflict with
<my:SomeUserControls Name="SomeOtherName" />
SOLUTION:
In your control, just use RelativeSource Mode=FindAncestor:
<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />
To your question on how are all those third party controls done: They use TemplateBinding. But TemplateBinding can be used only in ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
In usercontrol the xaml represents Content of UserControl, not ControlTemplate/
Using this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...
The FileBrowserControl's DataContext has already been set to itself, therefore you are effectively asking to bind to the SelectedFile where the DataContext is the FileBrowserControl, not the parent ViewModel.
Give your View a name and use an ElementName binding instead.
SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"
I created a small File Browser Control:
<UserControl x:Class="Test.UserControls.FileBrowserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="44" d:DesignWidth="461" Name="Control">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Margin="3" Text="{Binding SelectedFile}" IsReadOnly="True" TextWrapping="Wrap" />
<Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />
</Grid>
</UserControl>
With the following code behind:
public partial class FileBrowserControl : UserControl
{
public ICommand BrowseCommand { get; set; }
//The dependency property
public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
public string SelectedFile { get{ return (string)GetValue(SelectedFileProperty);} set{ SetValue(SelectedFileProperty, value);}}
//For my first test, this is a static string
public string Filter { get; set; }
public FileBrowserControl()
{
InitializeComponent();
BrowseCommand = new RelayCommand(Browse);
Control.DataContext = this;
}
private void Browse()
{
SaveFileDialog dialog = new SaveFileDialog();
if (Filter != null)
{
dialog.Filter = Filter;
}
if (dialog.ShowDialog() == true)
{
SelectedFile = dialog.FileName;
}
}
}
And I use it like this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>
(SelectedFile is Property of the ViewModel of the usercontrol using this control)
Currently the issue is that when I click on Browse, the textbox in the usercontrol is correctly updating, but the SelectedFile property of the viewmodel parent control is not set(no call to the set property).
If I set the Mode of the binding to TwoWay, I got this exception:
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
So what did I do wrong?
The problem is that you set your UserControl's DataContext to itself in its constructor:
DataContext = this;
You should not do that, because it breaks any DataContext based Bindings, i.e. to a view model instance that is provided by property value inheritance of the DataContext property
Instead you would change the binding in the UserControl's XAML like this:
<TextBox Text="{Binding SelectedFile,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Now, when you use your UserControl and write a binding like
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />
the SelectedFile property gets bound to a SelectedFile property in your view model, which should be in the DataContext inherited from a parent control.
Do not ever set DataContext of UserControl inside usercontrol:
THIS IS WRONG:
this.DataContext = someDataContext;
because if somebody will use your usercontrol, its common practice to set its datacontext and it is in conflict with what you have set previously
<my:SomeUserControls DataContext="{Binding SomeDataContext}" />
Which one will be used? Well, it depends...
The same applies to Name property. you should not set name to UserControl like this:
<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />
because it is in conflict with
<my:SomeUserControls Name="SomeOtherName" />
SOLUTION:
In your control, just use RelativeSource Mode=FindAncestor:
<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />
To your question on how are all those third party controls done: They use TemplateBinding. But TemplateBinding can be used only in ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
In usercontrol the xaml represents Content of UserControl, not ControlTemplate/
Using this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...
The FileBrowserControl's DataContext has already been set to itself, therefore you are effectively asking to bind to the SelectedFile where the DataContext is the FileBrowserControl, not the parent ViewModel.
Give your View a name and use an ElementName binding instead.
SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"
I built a simple WPF UserControl that I want to implement several of in another UserControl while maintaining the data binding. I have a simple Debug1 window that implements (1) a bound textblock (to verify data binding only) & button; (2) a single instance of the NumericUpDownBox usercontrol with binding; and (3) an instance of the parent user control containing an instance of the child user control NumericUpDownBox.
Items (1) and (2) are working. Item (3) does not. I feel like I am missing something in the implementation in the parent user control code? I also get a couple of errors that concern me (see below).
Debug1 Window xaml:
<Window x:Class="Debug1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CFIT"
mc:Ignorable="d"
Title="Debug1" Height="225" Width="600"
Closing="ClosingDebug1">
<Grid>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button Name="NextButton" Content="Next" Click="ClickNext" Width="50" />
<TextBox Text="{Binding Path=Years, Mode=TwoWay}" />
</StackPanel>
<local:NumericUpDownBox Grid.Row="1" x:Name="SingleBox" Label="TBD" Increment="2" Value="{Binding Path=Years}"/>
<local:YMDH_TimeControl Grid.Row="2" x:Name="TimeControl" YearsValue="{Binding Path=Years}" />
</Grid>
</Window>
Debug Window VB Code:
Public Class Debug1
Public Mat As New clsMaterial
Sub New() ' Removed for brevity
End Sub
Private Sub ClickNext(sender As Object, e As RoutedEventArgs)
Mat.Lives(0).MSDateOffset.Years = Mat.Lives(0).MSDateOffset.Years + 5
End Sub
End Class
Parent UserControl xaml:
<UserControl x:Class="YMDH_TimeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CFIT"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="200" MinHeight="30" MinWidth="160"
Name="YMDH_TimeControl">
<Grid DataContext="{Binding ElementName=YMDH_TimeControl}">
<local:NumericUpDownBox Grid.Column="0" x:Name="Years"/>
</Grid>
</UserControl>
Parent UserControl VB Code:
Public Class YMDH_TimeControl
Public Shared ReadOnly YearsValueProperty As DependencyProperty = DependencyProperty.Register("YearsValue", GetType(Integer), GetType(YMDH_TimeControl), New FrameworkPropertyMetadata(0, (FrameworkPropertyMetadataOptions.BindsTwoWayByDefault), Nothing, New CoerceValueCallback(AddressOf OnValueChanged)))
Private Shared Function OnValueChanged(d As DependencyObject, value As Object) As Object
If IsNumeric(value) Then
Return CInt(value)
Else
Return Nothing
End If
End Function
Public Property YearsValue As Integer
Get
Return Years.Value
End Get
Set(value As Integer)
Years.Value = value
End Set
End Property
End Class
Child UserControl xaml:
<UserControl x:Class="NumericUpDownBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CFIT"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="50" MinHeight="30" MinWidth="40"
Name="NumericUpDownBox">
<Grid DataContext="{Binding ElementName=NumericUpDownBox}">
<Grid.Resources>
... removed for brevity...
</Grid.Resources>
<!--Box Definition-->
<Border Style="{StaticResource SectionNameBorder}" Grid.Column="0">
<Grid Background="Gray" >
<Viewbox>
<TextBlock Name="LabelTextBlock" Text="{Binding Path=Label, ElementName=NumericUpDownBox}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Viewbox>
<Grid Grid.Row="2" Background="White" >
<Grid Grid.Column="0" HorizontalAlignment="Right">
<Viewbox>
<TextBox Name="ValueTextBox" BorderBrush="White" Text="{Binding Path=Value, Mode=TwoWay, ElementName=NumericUpDownBox, UpdateSourceTrigger=PropertyChanged}" />
</Viewbox>
</Grid>
<Grid Grid.Column="1">
<RepeatButton Name="UpArrow" Style="{StaticResource UpArrowStyle}" Tag="+" Click="TickValues" />
<RepeatButton Name="DownArrow" Style="{StaticResource DownArrowStyle}" Tag="-" Click="TickValues" />
</Grid>
</Grid>
</Grid>
</Border>
</Grid>
</UserControl>
Child UserControl VB Code:
Public Class NumericUpDownBox
Private Const DefaultInc As Integer = 1
Public Sub New()
InitializeComponent()
If Increment = 0 Then Increment = DefaultInc
End Sub
Private Sub TickValues(sender As Object, e As RoutedEventArgs)
Dim Sign As Char
Dim Tag As String
Tag = sender.tag.ToString
If Len(Tag) = 0 Then Exit Sub
Sign = Left(Tag, 1)
If Sign = "+" Then
Value = Value + Increment
ElseIf Sign = "-" Then
Value = Value - Increment
End If
End Sub
Public Property Value As Integer
Get
Return CInt(GetValue(ValueProperty))
End Get
Set(value As Integer)
If value < 0 Then value = 0
SetValue(ValueProperty, value)
End Set
End Property
Public Property Label As String
Get
Return GetValue(LabelProperty)
End Get
Set(value As String)
SetValue(LabelProperty, value)
End Set
End Property
Public Property Increment As Integer
Get
Return CInt(GetValue(IncProperty))
End Get
Set(value As Integer)
If value < 0 Then value = 0
SetValue(IncProperty, value)
End Set
End Property
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Integer), GetType(NumericUpDownBox), New FrameworkPropertyMetadata(0, (FrameworkPropertyMetadataOptions.BindsTwoWayByDefault), Nothing, New CoerceValueCallback(AddressOf OnValueChanged)))
Public Shared ReadOnly LabelProperty As DependencyProperty = DependencyProperty.Register("Label", GetType(String), GetType(NumericUpDownBox), New FrameworkPropertyMetadata(""))
Public Shared ReadOnly IncProperty As DependencyProperty = DependencyProperty.Register("Increment", GetType(Integer), GetType(NumericUpDownBox), New FrameworkPropertyMetadata(0, Nothing, New CoerceValueCallback(AddressOf OnValueChanged)))
Private Shared Function OnValueChanged(d As DependencyObject, value As Object) As Object
If IsNumeric(value) Then
Return CInt(value)
Else
Return Nothing
End If
End Function
End Class
Example Information Error in the Output Window:
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Years; DataItem=null; target element is 'YMDH_TimeControl' (Name='TimeControl'); target property is 'YearsValue' (type 'Int32')
How do I get my data binding to pass through my parent user control?
I'm creating a simple User Control combining a popup with a text view, nothing crazy. When I set it up in a window at first to style it all out, it worked perfectly, but when I moved it into a User Control to actually finish it up, it won't work correctly any more.
I pass a min and max value into the control and then it automatically creates a list of numbers to pick from in that range. In the User Control, the list of numbers doesn't get bound correctly, who knows why. Maybe someone can take a look at my code.
I've read a bunch of other questions about this, but don't really know what's happening. I'm not getting any errors in my output window, so no clues there. Anyway, here's the code -
UserControl.xaml
<UserControl x:Class="UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Me"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<TextBox Name="myTextbox"
Height="30"
Margin="0"
FontSize="14"
IsReadOnly="True"
Padding="5,2"
Text="{Binding Value}" />
<Popup x:Name="myPopup"
PlacementTarget="{Binding ElementName=myTextbox}"
StaysOpen="True">
<Popup.Style>
<Style TargetType="{x:Type Popup}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myTextbox, Path=IsFocused}" Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
<StackPanel>
<ListView Name="myListView"
Height="100"
MaxHeight="300"
ItemsSource="{Binding List,
UpdateSourceTrigger=PropertyChanged}"
SelectionChanged="ListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Label Width="100"
Height="30"
Margin="0"
Content="{Binding}"
FontFamily="Segoe UI"
FontSize="14"
Padding="5,2" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Width="200" Height="30" />
</StackPanel>
</Popup>
</StackPanel>
UserControl.xaml.vb
Imports System.ComponentModel
Imports System.Linq.Expressions
Imports System.Collections.ObjectModel
Class UserControl1
' Dependency Properties
Public Shared ReadOnly ListProperty As DependencyProperty = DependencyProperty.Register("List", GetType(ObservableCollection(Of Integer)), GetType(MainWindow))
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Integer), GetType(MainWindow))
Public Shared ReadOnly MaxValueProperty As DependencyProperty = DependencyProperty.Register("MaxValue", GetType(Integer), GetType(MainWindow))
Public Shared ReadOnly MinValueProperty As DependencyProperty = DependencyProperty.Register("MinValue", GetType(Integer), GetType(MainWindow))
' Properties
Public Property List As ObservableCollection(Of Integer)
Get
Return DirectCast(GetValue(ListProperty), ObservableCollection(Of Integer))
End Get
Set(value As ObservableCollection(Of Integer))
SetValue(ListProperty, value)
End Set
End Property
Public Property Value As Integer
Get
Return DirectCast(GetValue(ValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(ValueProperty, value)
End Set
End Property
Public Property MaxValue As Integer
Get
Return DirectCast(GetValue(MaxValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(MaxValueProperty, value)
End Set
End Property
Public Property MinValue As Integer
Get
Return DirectCast(GetValue(MinValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(MinValueProperty, value)
End Set
End Property
Private Sub ListView_SelectionChanged(sender As System.Object, e As System.Windows.Controls.SelectionChangedEventArgs)
Value = List(myListView.SelectedIndex)
End Sub
Private Sub UserControl1_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
List = New ObservableCollection(Of Integer)
' Add all available numbers into the list
For iCounter As Integer = MinValue To MaxValue
List.Add(iCounter)
Next
' Set the selected index on the list for the value
myListView.SelectedIndex = Value - MinValue
End Sub
End Class
Just for reference, when I tested this out, I set the Min and Max values myself in both the window and usercontrol setup.
I think that you have made the same mistake that I used to when I first started learning WPF. I can't guarantee that this is the cause of your problem, but as it's the only one that I can see, I'll address it. Unfortunately, there are so many poor tutorials and quick examples that show the connecting of a UserControl.DataContext to itself:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Or:
DataContext = this;
Now this is perfectly acceptable if you don't want to Bind to the UserControl externally because it's a quick and easy way to connect with properties defined in the code behind. However, when you want to Bind to the properties from outside the control, you'll find problems. In these instances (if not on all occasions), you should use a RelativeSource Binding to Bind to your code behind properties:
<TextBox Name="myTextbox" Height="30" Margin="0" FontSize="14" IsReadOnly="True"
Padding="5,2" Text="{Binding Value, RelativeSource={RelativeSource AncestorType={
x:Type UserControl1}}}" />
In this Binding, UserControl1 is the name of the UserControl that declared the properties. This should be done on all of the internal Bindings. This way, the DataContext is not set to the UserControl, but the Bindings still find the properties.
You can find out more about RelativeSource from the RelativeSource MarkupExtension page on MSDN.
As I cant provide a comment to Sheridan good answer I have to provide a new answer, sorry for this.
While I love this solution
DataContext="{Binding RelativeSource={RelativeSource Self}}"
it fails (as Sheridan pointed out already) fast.
What you can do is just set the DataContext of the content of your User Control
<UserControl x:Class="Example.View.Controls.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:Example.View.Controls"
mc:Ignorable="d">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:MyUserControl}}}">
</Grid>
In this way all following Bindings have less Boilerplate code as you can just bind directly to your DP from the code-behind like:
<Label Content="{Binding MyLabel}"/>
As for Windows Apps (Windows 8 and Windows 10 UWP) the way to go is to give your control a name and reference it within your XAML file using Path and ElementName:
<UserControl
x:Class="MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Control"
mc:Ignorable="d" >
<Grid Height="240" VerticalAlignment="Top">
<Rectangle Fill="{Binding ElementName=Control, Path=Background}" />
</Grid>
</UserControl>
``