Search code examples
c#wpfdata-bindinguser-controls

Custom UserControls + MVVM - WPF / Prism-Framework


Currently, I am refactoring an application, where we have lots of duplicated XAML code. I want to use custom UserControls instead. I did a tutorial, on how to create those, but now I am at a point, where I do not know how to solve it.

So, currently I am getting this error:

System.Windows.Markup.XamlParseException: '"Binding" cannot be set for the "PropertyMargin" property of type "TxtInput". "Binding" can only be set for a "DependencyProperty" of a "DependencyObject"

So, basically, I have a custom UserControl:

<UserControl ...>
    <UserControl.Resources>
       ...
    </UserControl.Resources>
    <DockPanel Margin="{Binding PropertyMargin}">
        <md:Card Style="{StaticResource ParamCard}">
            <TextBlock Text="{Binding PropertyName}"/>
        </md:Card>
    </DockPanel>
</UserControl>

With its Code-Behind:

public partial class TxtInput : UserControl, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    #region Properties

    private string _propertyName = string.Empty;
    public string PropertyName
    {
        get { return _propertyName; }
        set
        {
            _propertyName = value;
            OnPropertyChanged();
        }
    }

    private string _propertyMargin = "0, 0, 0, 0";
    public string PropertyMargin
    {
        get { return _propertyMargin; }
        set
        {
            _propertyMargin = value;
            OnPropertyChanged();
        }
    }
    #endregion

    public TxtInput()
    {
        DataContext = this;
        InitializeComponent();
    }

    private void OnPropertyChanged([CallerMemberName] string? property = null) 
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }
}

I use this UserControl within another one - and I want to inject the Properties via the "Parent one"

So - for statically set properties, e.g. the PropertyName - that's no problem at all - but for dynamically changing values, for example the ProprtyMargin, it does not work as expected:

"Parent-UserControl"

<uc:TxtInput PropertyName="Developer Name" PropertyMargin="{Binding NameMargin}"/>

The Margin changes, depending, if the validation within the parent-userControl is successful or not (just an example here)

Parent-UserControlViewModel

...
        private string _nameMargin;
        public string NameMargin
        {
            get { return _nameMargin; }
            set { SetProperty(ref _nameMargin, value); }
        }
...
        private void SetErrorTxts(List<ValidationFailure> errors)
        {
            RemoveErrorTxtsFromProperties();
            foreach (var _ in errors.Select(error => error.ErrorMessage).Where(error => error.Contains("Name")).Select(error => new { }))
            {
                // ------------------------------
                // Default values for properties
                // if set not correctly
                // ------------------------------
                NameMargin = "0 0 0 16";
            }
        }

        // ------------------------------
        // Default values for properties
        // if set correctly
        // ------------------------------
        private void RemoveErrorTxtsFromProperties()
        {
            NameMargin = "0 0 0 8";
        }

I know, it's possible if I set the UserControl's DataContext to the ViewModel of the Control, where I am using it - but I want to use my custom control in different other views as well, so I cannot set the DataContext of this UserControl statically.

Basically, I want the custom UserControl represent the "style" that it gets via the properties from the parent-ViewModel. I hope it's clear, what I mean.

Any tip would be helpful!


Solution

  • As the error message says, to allow binding, you need to implement your properties has dependency property. Here is the documentation about that.

    This look like this:

    public static readonly DependencyProperty PropertyNameProperty =
        DependencyProperty.Register(
            nameof(PropertyName),
            typeof(string),
            typeof(TxtInput),
            new PropertyMetadata(default(string)));
    
    public string PropertyName
    {
        get { return (string)GetValue(PropertyNameProperty); }
        set { SetValue(PropertyNameProperty, value); }
    }