Search code examples
c#wpfxamldata-binding.net-4.6

Binding named tuple to XAML DataGrid


In my WPF application, I have a lot of view models that, now, require a "property-value" like grid.

I'm trying to build a generic purpose view model that can be "injected" in existing ones so that I can use them with the new window with little no effort. To do this, I've written a simple interface with an observable collection:

public interface IPropertyGridVM
{
    ObservableCollection<(string Prop, object Val)> PropValueList
    {
        get;
        set;
    }
}

And, in one of the View Models that needs the new property-value grid:

public class ExistingVM : ViewModelBase<Model>, IPropertyGridVM
{
    public ObservableCollection<(string Prop, object Val)> PropValueList
    {
        get;
        set;
    }

    ExistingVM()
         : base(new Model())
    {
        // "old" vm initialization
        initPropValueList();
    }

    ExistingVM(Model model)
         : base(model)
    {
        // "old" vm initialization
        initPropValueList();
    }

    private void initPropValueList()
    {
         PropValueList = new ObservableCollection<(string Prop, object Val)>()
         {
             (nameof(Prop1), Prop1),
             // ...
         }
    }
}

Following the existing convention of the application:

// This piece of code is inside a Dialog Manager    
public void ShowPropertiesDialog<T>(T propValueLikeVm)
{
    if (propValueLikeVm is IPropertyGridVM)
    {
        // create the dialog
        PropertyGridDialog dialog = new PropertyGridDialog();
        // assign the datacontext
        dialog.DataContext = propValueLikeVm;
        // till here is all ok, VM is correctly initialized and contains what I expected
        dialog.ShowDialog();
    }
}

Now, in my general XAML dialog come the troubles:

<...
     xmlns:vm="clr-namespace:ViewModels;assembly=ViewModels"
     d:DataContext="{d:DesignData Type={x:Type vm:IPropertyGridVM},
                             IsDesignTimeCreatable=True}"
     mc:Ignorable="d">
<!-- dialog styling and definition -->
<!-- Intellisense warns me of some problems in binding the PropValueList: "No DataContext value found for PropValueList"-->
<DataGrid ItemsSource="{Binding Path=PropValueList}">
     <DataGrid.Columns>
            <DataGridTextColumn Width="2*" Binding="{Binding Path=Prop}">
                   <!-- Text column styling -->
            </DataGridTextColumn>
            <DataGridTextColumn Width="2*" Binding="{Binding Path=Val}">
                   <!-- Text column styling -->
            </DataGridTextColumn>
     </DataGrid.Columns>
</DataGrid>

And it's right: at run-time I have binding errors that tell me that "The property Prop has not been found in the object of type ValueTuple'2" and "The property Val has not been found in the object of type ValueTuple'2", but I can't figure why.

Any hints?


Solution

  • The names Prop and Val are only really present before compilation. They are really named Item1 and Item2. The compiler does some magic to let you use better names in the source. However, these are fields and not properties, and you might need properties to bind to in WPF. I would recommend just adding your own class:

    public class PropVal : INotifyPropertyChanged{
        public string Prop {get;}
        public object Val {get;}
        public PropVal(string prop, object val) => (Prop, Val) = (prop, val);
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    I would expect that to be more reliable. Add a implicit or explicit conversion from the corresponding valuetuple if you wish.