1

First of all, I am a WPF beginner! My approach is potentially not the right way to do what I want so do not hesitate to tell me if that is the case. What I want to do is a composite user control in WPF, using MVVM.

Some classes will do a better presentation than I, here are my view models:

interface IParameter : INotifyPropertyChanged
{
    string Name { get; set;}
    string Value { get; set;}
}

class TextParameter : ViewModelBase, IParameter 
{ 
    private string _value;

    public string Name { get; private set; }

    public string Value
    {
        get { return _value; }
        set
        {
            _value = value;
            RaisePropertyChanged();
        }
    }

    public TextParameter (string name)
    {
        this.Name = name;
    }
}

class ParameterList : ViewModelBase, IParameter
{
    private string _value;

    public string Name { get; private set; }

    public string Value
    {
        get { return _value; }
        set
        {
            _value = value;
            RaisePropertyChanged();
        }
    }  

    ObservableCollection<IParameter> Parameters { get; set; }

    public ParameterList (string name, IEnumerable<IParameter> parameters = null)
    {
        this.Name = name;
        this.Parameters = new ObservableCollection<IParameter>(parameters ?? new List<IParameter>());
    }
}

I am using MVVM Light, so all the PropertyChanged stuff is managed into ViewModelBase. Also, this is not an exhaustive list of all the parameters, there is some others, more complex but the issue is about these ones.

Here are my custom user controls:

TextParameterControl.xaml:

<UserControl x:Class="Stuff.TextParameterControl" [..] x:Name="parent">
    <StackPanel DataContext="{Binding ElementName=parent}" Orientation="Horizontal">
        <TextBlock Text="{Binding Path=ParamName, StringFormat='{}{0}:'}" Width="100"></TextBlock>
        <TextBox Text="{Binding Path=Value}" Width="100"></TextBox>
    </StackPanel>
</UserControl>

TextParameterControl.xaml.cs :

public class TextParameterControl : UserControl
{
    #region param name

    public string ParamName
    {
        get { return (string)GetValue(ParamNameProperty); }
        set { SetValue(ParamNameProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ParamName.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ParamNameProperty =
        DependencyProperty.Register("ParamName", typeof(string), typeof(TextParameterControl), new PropertyMetadata(String.Empty));

    #endregion

    #region value

    public string Value
    {
        get { return (string)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(string), typeof(TextParameterControl), new PropertyMetadata(String.Empty));

    #endregion

    public TextParameterControl()
    {
        InitializeComponent();
    }
}

ParameterListControl.xaml:

<UserControl x:Class="Stuff.ParameterListControl" [..] x:Name="parent">
    <UserControl.Resources>
        <DataTemplate x:Key="TextParameterTemplate">
            <c:TextParameterControl ParamName="{Binding Name}" Value="{Binding Value}"/>
        </DataTemplate>
        <DataTemplate x:Key="ParameterListTemplate">
            <c:ParameterListControl ParamName="{Binding Name}" Value="{Binding Value}" Items="{Binding Parameters}" />
        </DataTemplate>
        <s:ParameterTemplateSelector x:Key="ParameterSelector"
            TextParameterTemplate="{StaticResource TextParameterTemplate}"
            ParameterListTemplate="{StaticResource ParameterListTemplate}"/>
    </UserControl.Resources>
    <Expander DataContext="{Binding ElementName=parent}" Header="{Binding Path=ParamName}" IsExpanded="True" ExpandDirection="Down">
        <StackPanel>
            <ItemsControl ItemsSource="{Binding Path=Items}" ItemTemplateSelector="{StaticResource ParameterSelector}"></ItemsControl>
        </StackPanel>
    </Expander>
</UserControl>

ParameterListControl.xaml.cs:

public partial class ParameterListControl: UserControl
{
    #region param name

    public string ParamName
    {
        get { return (string)GetValue(ParamNameProperty); }
        set { SetValue(ParamNameProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ParamName.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ParamNameProperty =
        DependencyProperty.Register("ParamName", typeof(string), typeof(ParameterListControl), new PropertyMetadata(String.Empty));

    #endregion

    #region value

    public string Value
    {
        get { return (string)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(string), typeof(ParameterListControl), new PropertyMetadata(String.Empty));

    #endregion

    #region items

    public IList<string> Items
    {
        get { return (List<string>)GetValue(ItemsProperty); }
        set { SetValue(ItemsProperty, value); }
    }

    public static readonly DependencyProperty ItemsProperty =
        DependencyProperty.Register("Items", typeof(IList<string>), typeof(ParameterListControl), new PropertyMetadata(new List<string>()));

    #endregion

    public ParameterListControl()
    {
        InitializeComponent();
    }
}

Here is my custom template selector:

class ParameterTemplateSelector : DataTemplateSelector
{
    public DataTemplate ParameterListTemplate { get; set; }
    public DataTemplate TextParameterTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is TextParameter)
        {
            return this.TextParameterTemplate;
        }
        else if (item is ParameterList)
        {
            return this.ParameterListTemplate;
        }

        throw new Exception(String.Format("This parameter ({0}) is not handled in the application", item.GetType().Name));
    }
}

And here is the calling View and ViewModel:

ViewModel:

public class MainViewModel : ViewModelBase
{
    public ObservableCollection<IParameter> Parameters { get; set; }

    public MainViewModel()
    {
        this.Parameters = new ObservableCollection<IParameter>();
        this.Parameters.Add(new TextParameter("Customer"));
        // here I am building my complex composite parameter list
}

View:

<UserControl.Resources>
    <DataTemplate x:Key="TextParameterTemplate">
        <c:TextParameterControl ParamName="{Binding Name}" Value="{Binding Value}"/>
    </DataTemplate>
    <DataTemplate x:Key="ParameterListTemplate">
        <c:ParameterListControl ParamName="{Binding Name}" Value="{Binding Value}" Items="{Binding Parameters}" />
    </DataTemplate>

    <s:ParameterTemplateSelector x:Key="ParameterSelector"
        TextParameterTemplate="{StaticResource TextParameterTemplate}"
        ParameterListTemplate="{StaticResource ParameterListTemplate}"/>
</UserControl.Resources>

<ItemsControl ItemsSource="{Binding Parameters}" ItemTemplateSelector="{StaticResource ParameterSelector}"></ItemsControl>

When I run the application, the TextParameter in the MainViewModel.Parameters are well loaded (VM.Name and VM.Value properties are well binded to UC.ParamName and UC.Value. Contrariwise, the ParameterList in MainViewModel.Parameters are partially loaded. UC.Name is well binded to the UC.ParamName but the VM.Parameters is not binded to the UC.Items (the UC.DataContext is the VM, the VM.Parameters is well defined, but the UC.Items is desperately null).

Do you have any idea of what I am missing ? (I am not a native speaker, excuse me if my english hurts you)

fharreau
  • 2,105
  • 1
  • 23
  • 46
  • you have binding issues and exlude the inotifypropertychanged code? very bad idea. also Parameters shows no propertychanged code/initializer. also bad idea – Dbl Nov 10 '16 at 10:49
  • I have added all the "plumbing" code. – fharreau Nov 10 '16 at 11:41
  • to me it looks like the problem is still, that your view tries to bind to a property, (Parameters) before it is set, and since you don't place INotifyPropertyChanged calls on your Parameters property, WPF won't notice and your binding can't be resolved – Dbl Nov 10 '16 at 14:19
  • it isn't plumbing code if the functionality is essential to the concept of the platform you are working with. #1 issue with WPF is improper property declaration/initialization which results in binding errors. – Dbl Nov 10 '16 at 14:20
  • if this does not solve your problem i may look into it later, unless i forget. – Dbl Nov 10 '16 at 14:24
  • My ObservableCollections are not intended to change (the setter is private) and the instanciations are done within the view model constructor. When my view binds to the property, it is necessarily already defined, am I wrong ? Anyway, I tried to had some RaisePropertyChanged but it does not work neither... – fharreau Nov 10 '16 at 15:10
  • if the code within your MainViewModel is the way it is (MainViewModel - ScopeOfWorkViewModel ?) - it is not properly initialized where databindings are guaranteed to work. I suppose uploading a stripped project to github is not an option? – Dbl Nov 10 '16 at 15:58
  • ScopeOfWorkViewModel is a copy/paste error. I fixed it in the post. I will try to upload something quickly. I agree, it is not easy to debug like this. – fharreau Nov 10 '16 at 17:28

2 Answers2

0

I see you have a binding MainViewModel.Parameters -> ParameterListControl.Items but you might be missing the binding from ParameterListControl.Items -> ParameterList.Parameters. (That's assuming ParameterList is the ViewModel for the ParameterListControl - you provide the code for DataContext bindings.)

See the accepted answer on this question. (Ignore the comment on Caliburn.Micro - same solution worked for me in MVVM Light.)

Essentially, in the constructor of ParameterListControl you create an extra binding between the dependency property of the view and the viewmodel's property.

(Also, Dbl is right in the comments that when debugging binding problems, the "unimportant" "plumbing" code that you omitted is very important.)

Community
  • 1
  • 1
Misza
  • 635
  • 4
  • 15
  • I am not sure to understand your first sentence. I have added now all the plumbing code. Can you see if your hypothesis is still valid ? What I don't understand is why is VM.Name binded to UC.ParamName correctly when VM.Paramters is not binded to UC.Items. I cannot see where the two bindings are differents. I cannot believe I have to manually register the binding in the code behind only for some properties and not for others ... – fharreau Nov 10 '16 at 12:48
0

I finally find out:

The Items dependency property of ParameterListControl was a IList<string>. It was a copy/paste mistake from another UC. I changed it to IEnumerable and everything works fine now:

public IEnumerable Items
{
    get { return (IEnumerable)GetValue(ItemsProperty); }
    set { SetValue(ItemsProperty, value); }
}

public static readonly DependencyProperty ItemsProperty =
        DependencyProperty.Register("Items", typeof(IEnumerable), typeof(ParameterListControl), new PropertyMetadata(new List<object>()));

I continued to work on the code and it is now finished and truly composite compared to the sample I posted earlier. If someone is interested in seeing/using this code, you can find it on github.

fharreau
  • 2,105
  • 1
  • 23
  • 46