2

Bit of background: I'm trying to wrap my head around Windows Presentation Foundation's data binding. I understand most of it (or at least, I think I do), but trying to bind to data from a parent to a user control keeps leaving me baffled. It also feels like everyone on the internet has a different approach to this, which does not help.

I'm currently creating a small control to manage a file being processed, then uploaded to a service. The control is simple right now, only displaying the filename and path. It will grow with more complexity eventually, so I want to get the bindings down now.

The issue is that despite UploadFiles being bound correctly and FullName working as intended (showing the FileInfo.FullName prop), I can't bind to the controls from the list view. Ideally, I'd like to bind to a ViewModel's File object, and go from there.

Using the control, where UploadFiles is an observable FileInfo list:

<ScrollViewer VerticalScrollBarVisibility="Auto">
        <ListView ItemsSource="{Binding UploadFiles}" Margin="5">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <TextBlock Text="{Binding FullName}" />
                        <controls:ControlUploadDataItem File="{Binding}" />
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ScrollViewer>

The UploadControl XAML (Namespace obfuscated):

<UserControl x:Class="-----.Controls.ControlUploadDataItem"
         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:-----.Controls"
         mc:Ignorable="d" 
         d:DesignWidth="300"
         x:Name="UploadDataItem">    
<StackPanel Background="{StaticResource SecondaryColorBrush}" Margin="0, 0, 0, 10">
    <TextBlock FontSize="20" FontWeight="Bold" Foreground="{StaticResource PrimaryColorBrush}" Text="{Binding File.Name, ElementName=UploadDataItem, FallbackValue='File Name'}" />
    <TextBlock FontSize="10" FontStyle="Italic" Foreground="Gray" Text="{Binding File.FullName, ElementName=UploadDataItem, FallbackValue='x:\\file\\path\\here'}" />

    <!--<TextBlock FontWeight="Bold" Foreground="{StaticResource PrimaryColorBrush}" Text="{Binding File.Name, FallbackValue='File Name'}" />-->
</StackPanel>

Lastly, the code behind:

/// <summary>
/// Interaction logic for UploadItem.xaml
/// </summary>
public partial class ControlUploadDataItem : UserControl
{

    public ControlUploadDataItem()
    {
        InitializeComponent();
        this.DataContext = new UploadDataViewModel();            
    }
}

internal class UploadDataViewModel: ViewModelBase
{

    private FileInfo _file;

    public FileInfo File
    {
        get { return _file; }

        set
        {
            _file = value;
            // Refresh(); ?
            OnPropChanged(nameof(File));
        }
    }
}

internal class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropChanged(string prop)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
    }
}

I'd like this control to handle everything about an uploaded file; that is, showing the file as pending, an upload bar when it's being worked on, and a "final result" when it completes.

Question is.. what am I doing wrong, to bind the file data?

Ted S.
  • 77
  • 6
  • Why are you setting the `DataContext` property in the constructor of the `ControlUploadDataItem` class? Don't do this. And where is the File property that you bind to in the XAML? – mm8 Sep 25 '18 at 13:19
  • @mm8 That's part of what I mean. Some people give examples of this working, others mention using DependencyProperty. Ideally I just want to use `` in the list view. – Ted S. Sep 25 '18 at 13:22
  • You could let the `ControlUploadDataItem` inherit the DataContext and bind directly to Name and FullName in it. Then you don't need any File property. If you do need it, it should be defined as a dependendy property in ControlUploadDataItem.xaml.cs. – mm8 Sep 25 '18 at 13:23
  • @mm8 I need to create a context due to this control evolving into something more complex; inheriting context is definitely not going to be enough. If I add a dependency property with the same name (File) will that automatically work? – Ted S. Sep 25 '18 at 13:31
  • So add a dependency property and avoid setting the DataContext in the constructor then. Then your bindings should work. – mm8 Sep 25 '18 at 13:32

2 Answers2

0

The DataContext of the UserControl is inherited from it's parent. So in the UserControl you don't need to specify 'File'. The dataContext is already the selected file.

<UserControl x:Class="-----.Controls.ControlUploadDataItem"
     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:-----.Controls"
     mc:Ignorable="d" 
     d:DesignWidth="300"
     x:Name="UploadDataItem">    
<StackPanel Background="{StaticResource SecondaryColorBrush}" Margin="0, 0, 0, 10">
    <TextBlock FontSize="20" FontWeight="Bold" Foreground="{StaticResource PrimaryColorBrush}" Text="{Binding Name, FallbackValue='File Name'}" />
    <TextBlock FontSize="10" FontStyle="Italic" Foreground="Gray" Text="{Binding FullName, FallbackValue='x:\\file\\path\\here'}" />

    <!--<TextBlock FontWeight="Bold" Foreground="{StaticResource PrimaryColorBrush}" Text="{Binding Name, FallbackValue='File Name'}" />-->
</StackPanel>
Neil B
  • 2,096
  • 1
  • 12
  • 23
0

This markup requires you to add a dependency property called File to the ControlUploadDataItem control:

<controls:ControlUploadDataItem File="{Binding}" />

You should also avoid setting the DataContext property in the constructor of the control as this will prevent it from inherting the DataContext from the parent element in the object tree:

this.DataContext = new UploadDataViewModel();

You don't really need an UploadDataViewModel here. The File property belongs to the control and is bound to the FileInfo in the source collection of the ListView in the ItemTemplate of the same. The ControlUploadDataItem control can then bind properties of its own File dependency property.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • Will I be able to handle upload progress, control state (pending, uploading, completed), and similar information without the view model by doing this? That's what led me towards using one in the first place. – Ted S. Sep 25 '18 at 13:45
  • If you need a child view model, you could create one in the parent view model and then set the DataContext of the control in the ItemTemplate. You may of course also add several dependency properties to the control. You may want to read this: http://briannoyesblog.azurewebsites.net/2015/09/19/using-mvvm-in-a-reusable-usercontrol/ – mm8 Sep 25 '18 at 13:50
  • Changing it to use a DependencyProperty directly in the Control class works as intended. Will need to see about the context (due to scope) but this fixes the immediate issue. – Ted S. Sep 25 '18 at 14:06