Reputation: 17037
I am creating an UserControl with 2 RepeatButtons and one TextBox. So problems begin when i want to Bind some properties....
FYI i am using Caliburn.micro as Framework..
i need to have the value of property Interval from the parent to define the time lapse of both repeat button
i need to have the MaxValue defined in the parent and use it in the event MouseClick of customcontrol
i need to initialize the value of TextBox of custom control from parent, use it in the event mouseclick of custom control to obtain the new value update the TextBox and use the new value changed in the parent...
what i have tested:
CustomRepeatButton.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace ChromiumWPF.UserControls
{
public partial class CustomRepeatButton : UserControl
{
public CustomRepeatButton()
{
InitializeComponent();
}
public string TextValue
{
get { return (string)GetValue(TextValueProperty); }
set { SetValue(TextValueProperty, value); }
}
public static DependencyProperty TextValueProperty =
DependencyProperty.Register("TextValue", typeof(string), typeof(CustomRepeatButton));
public int IntervalValue
{
get { return (int)GetValue(IntervalValueProperty); }
set { SetValue(IntervalValueProperty, value); }
}
public static DependencyProperty IntervalValueProperty =
DependencyProperty.Register("IntervalValue",
typeof(int),
typeof(CustomRepeatButton),
new FrameworkPropertyMetadata(0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
public int MaxValue
{
get { return (int)GetValue(MaxValueProperty); }
set { SetValue(MaxValueProperty, value); }
}
public static DependencyProperty MaxValueProperty =
DependencyProperty.Register("MaxValue", typeof(int), typeof(CustomRepeatButton));
:
:
//same definition for ResetValue, MinValue and IncrementValue
private void RepeatButton_Click(object sender, RoutedEventArgs e)
{
// click on button + or - and change the value of TextValue i trap in usercontrol Parent
var RB = sender as RepeatButton;
}
}
}
CustomRepeatButton.xaml (see Bindings TextValue and IntervalValue in Style)
<UserControl x:Class="ChromiumWPF.UserControls.CustomRepeatButton"
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:ChromiumWPF.UserControls"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="TextBox">
<Style.Setters>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="FontWeight" Value="Medium"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="IsEnabled" Value="True"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Background" Value="Aquamarine"/>
<Setter Property="Text" Value="{Binding TextValue, Mode=TwoWay}"/>
</Style.Setters>
</Style>
<Style TargetType="RepeatButton">
<Style.Setters>
<Setter Property="Interval" Value="{Binding IntervalValue, Mode=TwoWay}"/>
<Setter Property="Background" Value="Gray"/>
<EventSetter Event="Click" Handler="RepeatButton_Click"/>
</Style.Setters>
</Style>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<RepeatButton Content="-" />
<TextBox />
<RepeatButton Content="+" />
</StackPanel>
</Grid>
</UserControl>
ChromeViewModel.cs
private string adress;
public string Adress
{
get { return adress; }
set
{
adress = value;
NotifyOfPropertyChange(() => Adress);
}
}
private int _test;
public int Test
{
get { return _test; }
set
{
_test = value;
NotifyOfPropertyChange(() => Test);
}
}
private int _maxV;
public int MaxV
{
get { return _maxV; }
set
{
_maxV = value;
NotifyOfPropertyChange(() => MaxV);
}
}
ChromeView.xaml
<UserControl x:Class="ChromiumWPF.Views.ChromeView"
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:cal="http://www.caliburnproject.org"
xmlns:uc="clr-namespace:ChromiumWPF.UserControls"
xmlns:local="clr-namespace:ChromiumWPF.Views"
xmlns:vm="clr-namespace:ChromiumWPF.ViewModels"
mc:Ignorable="d" d:DataContext="{d:DesignInstance Type=vm:ChromeViewModel}"
d:DesignHeight="800" d:DesignWidth="1200">
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Vertical" VerticalAlignment="Top"
HorizontalAlignment="Center">
<ComboBox x:Name="Months" Background="Aqua" Height="30" Width="100"
VerticalContentAlignment="Center"/>
<uc:CustomRepeatButton TextValue="{Binding Adress}"
IncrementValue="10"
IntervalValue="{Binding Test}"
MaxValue="{Binding MaxV}"
MinValue="900"
ResetValue="1080" />
</StackPanel>
</Grid>
</UserControl>
So i have problem with Test, Adress and MaxV (only Bindings in relation with the customControl
).
I dont see why the bindings is in error, the definition of DP and properties seems ok for me?? is it a problem of DataContext? Maybe i have to set the DataContext to ChromeViewModel? if yes how i could do that?... Thanks for help..
errors displayed:
System.Windows.Data Error: 40 : BindingExpression path error: 'Adress' property not found on 'object' ''CustomRepeatButton' (Name='')'. BindingExpression:Path=Adress; DataItem='CustomRepeatButton' (Name=''); target element is 'CustomRepeatButton' (Name=''); target property is 'TextValue' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'Test' property not found on 'object' ''CustomRepeatButton' (Name='')'. BindingExpression:Path=Test; DataItem='CustomRepeatButton' (Name=''); target element is 'CustomRepeatButton' (Name=''); target property is 'IntervalValue' (type 'Int32')
System.Windows.Data Error: 40 : BindingExpression path error: 'MaxV' property not found on 'object' ''CustomRepeatButton' (Name='')'. BindingExpression:Path=MaxV; DataItem='CustomRepeatButton' (Name=''); target element is 'CustomRepeatButton' (Name=''); target property is 'MaxValue' (type 'Int32')
Upvotes: 0
Views: 94
Reputation: 6816
The problem is simple. You are trying to make the control's XAML bind to its own dependency properties but you are doing it the wrong way. You need to set the DataContext on the first visual element within the control, not on the control itself.
So do this:
First, go to your CustomRepeatButton.xaml file. Go to line 8 (shown below) where you set the DataContext
to itself. Delete that line
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Next go to the first visual element. The Grid
. Give it an x:Name
value so you can refer to it in code-behind. Like this:
<Grid x:Name="MainGrid" ShowGridLines="True">
Finally go into the control's code behind constructor. Manually set the DataContext
right after the call to IntializeComponent
. So make it look like this
public CustomRepeatButton()
{
InitializeComponent();
MainGrid.DataContext = this;
}
That should get you what you want.
Upvotes: 1
Reputation: 29028
In WPF the dependency property system uses value precedence. For example, the DataContext
value of a FrameworkElement
is implicitly inherited from the parent. If you set the value explicitly you will override the inherited value (the parent's DataContext
will be ignored by the property system).
In general you want a custom control to inherit the parents DataContext
so that external data bindings assigned to the dependency properties.
The external Binding
uses the current DataContext
as source (implicitly):
<UserControl>
<!-- Binding expects the DataContext of CustomRepeatButton
to be of type ChromeViewModel -->
<uc:CustomRepeatButton TextValue="{Binding Adress}" />
</UserControl>
The following local Binding
overrides the inherited DataContext
value (taken from your code):
<UserControl x:Class="ChromiumWPF.UserControls.CustomRepeatButton"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
</UserControl>
Because of this Binding
, the DataContext
of CustomRepeatButton
is the CustomRepeatButton
control itself. Every Binding
that uses the implicit binding syntax ({Binding path}
) will now bind to the CustomRepeatButton
. This explains the error message that states that the source object is of type CustomRepeatButton
: "BindingExpression path error: 'Adress' property not found on 'object' ''CustomRepeatButton'".
This also exemplifies why you should never set the DataContext
of a custom control explicitly - unless you want to surprise the user of your control.
To fix this don't ever set the DataContext
of custom control explicitly. In order to bind the internals to the control's properties you must use the proper binding source i.e explicit binding source.
If the internals are assigned to the Content
property of a ContentControl
, for example of a UserControl
, you must use Binding.RelativeSource
and set the RelativeSource.AncestorType
property of the RelativeSource
extension:
<UserControl>
<RepeatButton Interval="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=IntervalValue}" />
</UserControl>
Bindings are the same when defined in a Style
:
<Style TargetType="RepeatButton">
<Setter Property="Interval"
Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=IntervalValue}"/>
</Style>
If the internals are defined inside a ControlTemplate
, you would have to use the TemplateBinding
extension (instead of Binding
) or use the Binding
extension and set the Binding.RelativeSource
property using the RelativeSource
extension and set its RelativeSource.Mode
property to RelativeSourceMode.TemplatedParent
:
<UserControl>
<UserControl.Template>
<ControlTemplate TargetType="CustomRepeatButton">
<!-- Recommended using the TemplateBinding extension -->
<RepeatButton Interval="{TemplateBinding IntervalValue}" />
<!-- Using the Binding together with the RelativeSource extension -->
<RepeatButton Interval="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IntervalValue}" />
</ControlTemplate>
</UserControl.Template>
</UserControl>
Upvotes: 1