0

I am trying to send objects to an UserControl, but I cannot figure what's wrong?

Here is my UserControl :

namespace VSTEEL.UserControls
{
    /// <summary>
    /// Interaction logic for UserControlListOperations.xaml
    /// </summary>
    public partial class UserControlListOperations : UserControl,INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(string nomPropriete)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(nomPropriete));
        }
        public string MyString
        {
            get
            {
                return (string)GetValue(MyStringProperty);
            }
            set
            {
                SetValue(MyStringProperty, value); this.NotifyPropertyChanged("MyString");
            }
        }
        public static DependencyProperty MyStringProperty =
        DependencyProperty.Register(
        nameof(MyString),
        typeof(string),
        typeof(UserControlListOperations),
        new PropertyMetadata(MyStringPropertyChanged));
        private static void MyStringPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            //var userControl = (UserControlMyString)obj;

            // handle the new property value here
        }

        public static DependencyProperty ListOp1Property =
        DependencyProperty.Register(
        nameof(ListOp1),
        typeof(IList<Operation>),
        typeof(UserControlListOperations),
        new PropertyMetadata(ListOp1PropertyChanged));

        public IList<Operation> ListOp1
        {
            get
            {
                return (IList<Operation>)GetValue(ListOp1Property);
            }
            set
            {
                SetValue(ListOp1Property, value);this.NotifyPropertyChanged("ListOp1");
            }
        }
        private static void ListOp1PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var userControl = (UserControlListOperations)obj;

            // handle the new property value here
        }
        public Contract Contract
        {
            get
            {
                return (Contract)GetValue(ContractProperty);
            }
            set
            {
                SetValue(ContractProperty, value); this.NotifyPropertyChanged("Contract");
            }
        }
        public static DependencyProperty ContractProperty =
        DependencyProperty.Register("Contract", typeof(Contract), typeof(UserControlListOperations));


        private List<Operation> listOp2 { get; set; } = new List<Operation>( Global.GetListOperations());
        public List<Operation> ListOp2 { get { return this.listOp2; } set { this.listOp2 = value; } }

        public UserControlListOperations()
        {
            InitializeComponent();
            this.DataContext = this;
        }

        static FrameworkPropertyMetadata propertyMetaData = new FrameworkPropertyMetadata
        (
            "UserControlListOperations",
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            new PropertyChangedCallback(TextProperty_PropertyChanged)
        );

        private static void TextProperty_PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            
        }

        
    }
}

And here is my main window, from which I am trying to open the UserControl :

<GridViewColumn Header="{x:Static p:Resources.Advancement}" Width="{Binding WidthColumnContractAdv, Mode=TwoWay}">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <StackPanel Tag="{Binding}" MouseMove="mouseOverProgressionContractAss">
                <Grid>
                    <views:UserControlListOperations 
                    ListOp1 = "{Binding Path = ListOpAss}"
                    MyString="toto"/>
                    <!--SetListOp2 = "{Binding DataContext.ListOpAssTot}"/>-->
                    </Grid>
                </StackPanel>
            </DataTemplate>
        </GridViewColumn.CellTemplate>
    </GridViewColumn>
    <GridViewColumn Header="{x:Static p:Resources.Advancement}" Width="{Binding WidthColumnContractAdv, Mode=TwoWay}">
        <GridViewColumn.CellTemplate>
            <DataTemplate>
                <StackPanel Tag="{Binding}" MouseMove="mouseOverProgressionContractAss">
                    <Rectangle Name="AFF_Track"  Height="12" Stroke="black" StrokeThickness="1"  Tag="{Binding ID}">
                        <Rectangle.Fill>
                            <MultiBinding Converter="{StaticResource :listOpToLinearGradientBrush}">
                                <Binding Path="ListOpAss" />
                                <Binding RelativeSource="{RelativeSource AncestorType={x:Type Fluent:RibbonWindow}}" Path="DataContext.ListOpAssTot" />
                                <Binding Source="0"/>
                            </MultiBinding>
                        </Rectangle.Fill>
                        <Rectangle.ToolTip>
                            <ContentControl  Template="{StaticResource ToolTipOperations}"/>
                        </Rectangle.ToolTip>
                    </Rectangle>

                    <Rectangle Name="AFF_Track2"  Height="12" Stroke="black" StrokeThickness="1"  Tag="{Binding ID}">
                        <Rectangle.Fill>
                            <MultiBinding Converter="{StaticResource :listOpToLinearGradientBrush}">
                                <Binding Path="ListOpAss" />
                                <Binding RelativeSource="{RelativeSource AncestorType={x:Type Fluent:RibbonWindow}}" Path="DataContext.ListOpAssTot" />
                                <Binding Source="1"/>
                            </MultiBinding>
                        </Rectangle.Fill>
                        <Rectangle.ToolTip>
                            <ContentControl  Template="{StaticResource ToolTipOperations}"/>
                        </Rectangle.ToolTip>
                    </Rectangle>
                </StackPanel>
            </DataTemplate>
        </GridViewColumn.CellTemplate>
    </GridViewColumn>

My first GridViewColumn is the one opening the UserControl, but in debugging, I can se that SetValue(ListOp1Property, value); is never launched, and that's what I cannot understand? (the UserControl is corrctly initialized)

I left the second column to check, that all is working fine without using UseControl (and it is working).

Here is my UserControl XAML : (I added a string MyString, and it is corrctly set, not objects.

<UserControl x:Class="VSTEEL.UserControls.UserControlListOperations"
             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:converters="clr-namespace:VSTEEL.Converters;assembly=MainLibrary"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:VSTEEL.UserControls"
             mc:Ignorable="d" 
             >
    <UserControl.Resources>
        <converters:ListOpToLinearGradientBrush x:Key=":listOpToLinearGradientBrush" />
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Label Content="{Binding MyString}"/>
        <Rectangle Grid.Row="1" Name="AFF_Track"  Height="12" Stroke="black" StrokeThickness="1"  Tag="{Binding ID}">
            <Rectangle.Fill>
                <MultiBinding Converter="{StaticResource :listOpToLinearGradientBrush}">
                    <Binding Path="ListOp1" />
                    <Binding Path="Contract" />
                    <Binding Source="0"/>
                </MultiBinding>
            </Rectangle.Fill>
        </Rectangle>

        <Rectangle Grid.Row="2" Name="AFF_Track2"  Height="12" Stroke="black" StrokeThickness="1"  Tag="{Binding ID}">
            <Rectangle.Fill>
                <MultiBinding Converter="{StaticResource :listOpToLinearGradientBrush}">
                    <Binding Path="ListOp1" />
                    <Binding Path="Contract" />
                    <Binding Source="0"/>
                </MultiBinding>
            </Rectangle.Fill>
        </Rectangle>
    </Grid>
</UserControl>
Siegfried.V
  • 1,508
  • 1
  • 16
  • 34
  • wpf invokes SetValue for ListOp1Property behind the scenes, without using setter of ListOp1 property. you can notice that by adding PropertyChangedCallback: https://stackoverflow.com/questions/5498517/how-to-use-propertychangedcallback – ASh Jun 18 '22 at 20:41
  • @Ash is there a way to debug that, so I could understand where is the problem? – Siegfried.V Jun 18 '22 at 20:43
  • @Ash not sure if I did it correctly, I add the function PropertyChangedCallBack (as in edit), but it is never launched. – Siegfried.V Jun 18 '22 at 20:55
  • you did it wrong. PropertyChangedCallback is not function name. it is delegate type. it should be added to DP metadata. also `DependencyObject o` will have UserControlListOperations type, not contract – ASh Jun 18 '22 at 21:01
  • @Ash I changed the code as in the link you gave me. But the `TextProperty_PropertyChanged` function is never executed. I also tried change binding mode = TwoWay, but nothing changed. – Siegfried.V Jun 18 '22 at 21:04

1 Answers1

1

Yould have to register the PropertyChangedCallback with PropertyMetadata that you pass as a fourth argument to the Register method. You should also use a more generic type for the property.

A correct dependency property implementation would look like this:

public static readonly DependencyProperty ListOp1Property =
    DependencyProperty.Register(
        nameof(ListOp1),
        typeof(IList<Operation>), // or ICollection or IEnumerable
        typeof(UserControlListOperations),
        new PropertyMetadata(ListOp1PropertyChanged)); // here

public IList<Operation> ListOp1
{
    get { return (IList<Operation>)GetValue(ListOp1Property); }
    set { SetValue(ListOp1Property, value); }
}

private static void ListOp1PropertyChanged(
    DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    var userControl = (UserControlListOperations)obj;

    // handle the new property value here
}

Besides that, in order to bind to its own properties, the Bindings in the UserControl's XAML must have their source object to the control instance, e.g. by RelativeSource={RelativeSource AncestorType=UserControl}.

When there ary many such Bindings, you can alternatively set the DataContext of the top level element in the control's XAML:

<Grid DataContext="{Binding
    RelativeSource={RelativeSource AncestorType=UserControl}}">
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • it's not working too, `ListOp1PropertyChanged` is never called. But I noticed you set `ListOp1` as `IList` instead of `List`. When I call my UserControl, I send `List`, can it be something with it? – Siegfried.V Jun 19 '22 at 05:29
  • No, that is just what I said, use a more genric type. When the callback is not called, the source property of the Binding did not provide a value. Are you sure that `"{Binding Path=ListOpAss}"` is actually working? Are there any data binding error messages in the Output Window in Visual Studio when you debug the application? – Clemens Jun 19 '22 at 05:34
  • I don't have any error message (is there a specific windows that says about Binding errors?), and I believe that Binding is working (is there a way to be sure?) because in my second column, Bnding is working. Then I also tried a third column using a Template, and it is also working. – Siegfried.V Jun 19 '22 at 05:40
  • ... the Output Window in Visual Studio when you debug the application – Clemens Jun 19 '22 at 05:41
  • Besides that, it seems unclear why you need the PropertyChangedCallback at all. There should be elements in the UserControl's XAML that bind to the control's properties. You may show us that XAML too. – Clemens Jun 19 '22 at 05:44
  • I don't need PropertyChangedCallback, but Ash advised me to check with it. I just made a try : Add just a string, and the string is corectly passed to the UserControl. ok, will now put xaml – Siegfried.V Jun 19 '22 at 05:48
  • Please see the edited answer. – Clemens Jun 19 '22 at 05:57
  • In fact, I now see some errors on my binding : `BindingExpression path error: 'ListOpAss' property not found on 'object' ''UserControlListOperations' (Name='')'. BindingExpression:Path=ListOpAss; DataItem='UserControlListOperations' (Name=''); target element is 'UserControlListOperations' (Name=''); target property is 'ListOp1' (type 'IList`1')` Trying to check it, thanks – Siegfried.V Jun 19 '22 at 06:09
  • 1
    Apparently you explicitly set the DataContext of the UserControl somewhere. In a part of code or XAML that you have not shown. – Clemens Jun 19 '22 at 06:10
  • Only in UserControl constructor `this.DataContext=this;`. I try to understand the error I get (BindngExpression path error), seems that the problem is in my main window on `ListOp1 = "{Binding Path = ListOpAss}"` ? How is it possible that it is not found, and on second column I use the same, and it works? (will now put the full code of UserControl) – Siegfried.V Jun 19 '22 at 06:14
  • 1
    Setting `DataContext = this;` is a programming error, although many idiots on the internet show it in their blog posts. As you just learned, it breaks the standard data binding behaviour, because it replaces the actual DataContext, i.e. the object that owns the ListOpAss property. – Clemens Jun 19 '22 at 06:15
  • In fact, removing it solved all the problem. Thanks for your help, I could search still a long time... And thanks about the `output window`, even if it is obvious for you, didn't even know it exists... – Siegfried.V Jun 19 '22 at 06:21
  • 1
    For completeness, setting `DataContext = this` in a Window or Page is of course ok. Just not for controls. – Clemens Jun 19 '22 at 06:32
  • I will rember that ^^. In fact it's my first control (also could do that with template, but I wanted to understand that UserControl, thanks again). – Siegfried.V Jun 19 '22 at 06:42
  • Yes, you could move the UserControl's XAML to a ControlTemplate in the default Style in Generic.xaml of a custom Control. – Clemens Jun 19 '22 at 07:07
  • One question, I see that `PropertyChanged` returns the Control. How can I get the binded element (ListOp1)? Cause I need to make operation regarding the element that is binded. Could do `IList listOp2 = (IList)obj.GetValue(ListOp2Property);`, is it a correct way? – Siegfried.V Jun 19 '22 at 07:20
  • Just access `userControl.ListOp1` or `e.NewValue`. – Clemens Jun 19 '22 at 07:27
  • cannot access to non static elements? – Siegfried.V Jun 19 '22 at 07:40
  • Concretely, I try not to send a list, but the main object (in that way I could send different objects, then load their attribute ListOperations). Maybe I just will create a separate post. – Siegfried.V Jun 19 '22 at 07:43
  • 1
    Take a look at the answer. I am referring to the userControl variable. – Clemens Jun 19 '22 at 07:44