wpfbindinguser-controlscustomproperty

How to setup databinding with custom property


I created a simple a spinbox(numericUpDown) control in WPF (since there is none).

I have I created a custom Value property to which I would like to create a databinding with Model.

<UserControl x:Class="PmFrameGrabber.Views.SpinBox"
         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:PmFrameGrabber.Views"
         mc:Ignorable="d" 
         d:DesignHeight="25" d:DesignWidth="100">
<UserControl.Resources>
    <local:IntToStringConv x:Key="IntToStringConverter" />
</UserControl.Resources>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition Width="25" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <TextBox Name="TbValue" Grid.RowSpan="2" HorizontalContentAlignment="Right" 
             VerticalContentAlignment="Center" HorizontalAlignment="Stretch" 
             VerticalAlignment="Stretch" Text="{Binding Value, Converter={StaticResource IntToStringConverter}}"/>
    <Button Name="BtPlus" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" Margin="3,0,0,0" 
            VerticalAlignment="Center" FontSize="8" Content="+" Click="BtPlus_Click" />
    <Button Name="BtMinus" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch" Margin="3,0,0,0"
            VerticalAlignment="Center" FontSize="8" Content="-" Click="BtMinus_Click" />
</Grid>
</UserControl>

Here is the code behind:

public partial class SpinBox : UserControl
{
    public static DependencyProperty ValueDP =
        DependencyProperty.Register("Value", typeof(int), typeof(SpinBox), new UIPropertyMetadata(0));

    // Public bindable properties
    public int Value
    {
        get => (int)GetValue(ValueDP);
        set => SetValue(ValueDP, value);
    }

    public SpinBox()
    {

        InitializeComponent();
        DataContext = this;
    }
    private void BtPlus_Click(object sender, RoutedEventArgs e) => Value++;

    private void BtMinus_Click(object sender, RoutedEventArgs e) => Value--;
}

In another view I am trying to use the control like this:

<local:SpinBox Width="80" Height="25" Value="{Binding Cam.ExposureTime, Mode=TwoWay}" />

Here I get an error: Wpf binding can only be set on a dependencyproperty of an dependencyobject

Model property is in C++/CLI written like this:

property int ExposureTime
{
     void set(int value)
     {
        m_settings->exposureTime = value;
        OnPropertyChanged(GetPropName(Camera, ExposureTime));
     }
     int get()
     {
         return m_settings->exposureTime;
     }
}

Binding with this property works for other controls (textbox, labels).

I guess issue is with my custom SpinBox and with the way I created Value property. After day of digging the web I haven't found what else to do.


Solution

  • You're ignoring the dependency property naming convention, which requires that the DependencyProperty field must be named <PropertyName>Property:

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(int), typeof(SpinBox));
    
    public int Value
    {
        get => (int)GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }
    

    Besides that, setting

    DataContext = this;
    

    in the UserControl constructor prevents that you can bind to an inherited DataContext.

    A Binding like

    <local:SpinBox Value="{Binding Cam.ExposureTime, Mode=TwoWay}" />
    

    will not work because there is no Cam property in the current DataContext.

    So do not ever set a UserControl's DataContext explicitly.

    Instead, write the "internal" bindings like this:

    <TextBox Text="{Binding Value,
                    RelativeSource={RelativeSource AncestorType=UserControl}, ...}" />