0

I am designing a NumericUpDownControl UserControl and have successfully implemented it in my MainWindow.xaml as follows:

<UserControl x:Class="SettingsDialog.Controls.NumericUpDownControl"
             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:SettingsDialog.Controls"
             xmlns:viewModels="clr-namespace:SettingsDialog.ViewModels"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="18"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBox Text="{Binding Value}" Grid.Row="0" Grid.RowSpan="2" Height="20" VerticalContentAlignment="Center"/>
        <RepeatButton Content="5" Grid.Column="1" Grid.Row="0" FontSize="8" Height="10" FontFamily="Marlett" VerticalContentAlignment="Center"/>
        <RepeatButton Content="6" Grid.Column="1" Grid.Row="1" FontSize="8" Height="10" FontFamily="Marlett"  VerticalContentAlignment="Center"/>
    </Grid>
</UserControl>

In the code-behind of my UserControl, I have defined the following:

  public static readonly DependencyProperty ValueProperty =
     DependencyProperty.Register(
        "Value", typeof(int),
        typeof(NumericUpDownControl)
     );

  public int Value
  {
     get => (int)GetValue(ValueProperty);
     set => SetValue(ValueProperty, value);
  }

It works fine as I can use it in my MainWindow.xaml like this:

<controls:NumericUpDownControl Width="100" Value="10"/>

However, when I attempt to set a ViewModel for my UserControl, the TextBox within the UserControl no longer recognizes the Value dependency property. How can I properly implement a ViewModel for my UserControl while still allowing for the ability to set the Value property from outside the control? Is there an issue with my current implementation that is causing this issue?

Vahid
  • 5,144
  • 13
  • 70
  • 146
  • In the constructor of the UserControl add DataContext =this; – Nawed Nabi Zada Oct 09 '18 at 13:21
  • @NawedNabiZada I want the UserControl to have a ViewModel as well. – Vahid Oct 09 '18 at 13:22
  • Then set it to var myViewModel = new MyViewModel(); myViewModel.Value = this.Value; DataContext = myViewModel – Nawed Nabi Zada Oct 09 '18 at 13:23
  • @NawedNabiZada Thanks, that makes sense! I was curious if this is the best solution though! Since if pass it to ViewModel and change it later in ViewModel it will not reflect in the Dependency Property! – Vahid Oct 09 '18 at 13:27
  • You cannot bind it to two different places, so I would say best solution will be the one @mm8 came with. – Nawed Nabi Zada Oct 09 '18 at 13:29
  • @NawedNabiZada I know, I was just wondering what is the standard way to do this kind of stuff. – Vahid Oct 09 '18 at 13:31
  • When your UserControl exposes dependency properties, it should under no circumstances explicitly set its own DataContext (neither to `this` nor to a "private" view model). Doing so breaks any DataContext-based bindings of its dependency properties. What people on the internet (and sadly also here) are telling you about a UserControl's DataContext and private view models is plain wrong. Just don't do that. – Clemens Oct 09 '18 at 13:34
  • @Clemens That means all the inner logic of the control is done in code behind? Let's say I want to do some Validation for the entered Value here. Should I do that in the code-behind of the UserControl, right? – Vahid Oct 09 '18 at 13:37
  • Yes. The code-behind is part of the control class itself. You might also consider creating a custom control instead of using a UserControl. – mm8 Oct 09 '18 at 13:38
  • @Vahid Exactly. A control with bindable properties has its logic in its code behind, no private view model. There is not a single control in WPF that has a private view model. – Clemens Oct 09 '18 at 13:38
  • @Clemens and mm8 Thank you very much, Another aspect of WPF and MVVM was demystified for me today! – Vahid Oct 09 '18 at 13:40
  • 1
    @Vahid Think about `` as the general use case. Whenever you pass an instance of MyViewModel to a ContentControl or a ContentPresenter (also in an ItemsControl), the DataTemplate instantiates a MyUserControl and passes the MyViewModel instance to its DataContext. – Clemens Oct 09 '18 at 13:56

3 Answers3

2

This should bind the Text property of the TextBox to the Value property of the UserControl regardless of its DataContext:

<TextBox Text="{Binding Value, RelativeSource={RelativeSource AncestorType=UserControl}}" Grid.Row="0" Grid.RowSpan="2" Height="20" VerticalContentAlignment="Center"/>
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Thanks that worked. Am I going right about designing the UserControl or is there a better more standard way? – Vahid Oct 09 '18 at 13:30
  • It depends. Generally speaking a UserControl should inherit the DataContext and you shouldn't explicitly set its DataContext property. – mm8 Oct 09 '18 at 13:31
  • But, isn't a TextBox for example a Control itself? Are you suggesting that it does not have a ViewModel of its own in the code-behind although hidden from us? – Vahid Oct 09 '18 at 13:34
  • 1
    A TextBox is a control and it certainly has no view model. – mm8 Oct 09 '18 at 13:35
0

You cannot bind a UserControl to itself and to a ViewModel at the same time.

You would have to remove DataContext="{Binding RelativeSource={RelativeSource Self}}" from within the UserControl and make sure to pass the right DataContext to the UserControl when integrating it in your UI.

Joel Bourbonnais
  • 2,618
  • 3
  • 27
  • 44
0

Since you are making a sort of custom control another way to solve your problem is simply to use the propertychanged callback of your dependency property and every time it changes you simply update your Textbox's Text property:

    public partial class NumericUpDownControl : UserControl
    {
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(int),typeof(NumericUpDownControl),new PropertyMetadata(OnValueChanged));

        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((NumericUpDownControl) d).TextBox.Text = e.NewValue?.ToString() ?? string.Empty;
        }

        public int Value
        {
            get => (int)GetValue(ValueProperty);
            set => SetValue(ValueProperty, value);
        }

        public NumericUpDownControl()
        {
            InitializeComponent();
        }
    }
}
taquion
  • 2,667
  • 2
  • 18
  • 29