-1

I'm trying to improve my WPF skills via some side projects - the current one being creating a small clone of the Windows File Explorer.

I'm currently doing the 'This PC' section - making a user control designed to mimic the way Explorer displays disks, i.e. Explorer has this:

enter image description here

And my clone has this:

enter image description here

In this user control (Drive.xaml & Drive.xaml.cs), I have created several dependency properties that I want to be able to bind to in order to pass data in, namely being the volume label, disk name, percentage used etc...:

Drive.xaml.cs - shortened for brevity

public partial class Drive : UserControl
{
    /// <summary>
    /// Using a DependencyProperty as the backing store for DriveName.  This enables animation, styling, binding, etc...
    /// </summary>
    public static readonly DependencyProperty DriveNameProperty =
        DependencyProperty.Register("DriveName", typeof(string), typeof(Drive), new PropertyMetadata(string.Empty));

        /// <summary>
        /// Initializes a new instance of the <see cref="Drive" /> class
        /// </summary>
        public Drive()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Gets or sets the <see cref="DriveNameProperty" /> dependency property
        /// </summary>
        public string DriveName
        {
            get => (string)this.GetValue(DriveNameProperty);
            set => this.SetValue(DriveNameProperty, value);
        }
    }

Drive.xaml - also shortened

<UserControl x:Class="Explorer.View.Components.Hardware.Drive"
             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"
             mc:Ignorable="d" 
             DataContext="{Binding RelativeSource={RelativeSource Self}}"
             d:DesignHeight="60" 
             d:DesignWidth="260">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="15"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="200" />
        </Grid.ColumnDefinitions>

        <Image Width="50" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Source="{StaticResource DriveIcon}" />
        <TextBlock Grid.Row="0" Grid.Column="1">
            <TextBlock.Text>
                <MultiBinding StringFormat="{}{0} ({1})">
                    <Binding Path="VolumeLabel"/>
                    <Binding Path="DriveName"/>
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
        <ProgressBar Grid.Row="1" Grid.Column="1" Value="{Binding PercentageUsedBar}" Foreground="CornflowerBlue" />
        <TextBlock Grid.Row="2" Grid.Column="1" Text="[x TB free of y TB]"/>
    </Grid>
</UserControl>

The issue I'm having arises when I try to use this control as part of a data template. I can use these dependency properties without binding:

<!-- this works and renders correctly -->
<hardware:Drive PercentageUsedBar="25" DriveName="C:\" VolumeLabel="Reece"/>

But obviously that's not much use. So I have a ViewModel that provides the relevant real data from the filesystem, and I'm attempting to bind to details from that in order to render all the disks attached to my system:

<!-- this does not work for some reason -->
<ItemsControl ItemsSource="{Binding FixedDrives}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Style="{StaticResource TabButtonStyle}">
                <hardware:Drive Margin="5" 
                                DriveName="{Binding Drive.Name}"
                                VolumeLabel="{Binding Drive.VolumeLabel}"
                                PercentageUsedBar="{Binding PercentageSpaceUsed}"/>
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

But this just gives me binding failures for each disk:

enter image description here

Any help would be greatly appreciated, I'm starting to tear my hair out!

Reece
  • 549
  • 9
  • 23

1 Answers1

1

You are "overriding" the inherited DataContext.

Remove this from the UserControl, i.e. do not set its DataContext explicitly:

DataContext="{Binding RelativeSource={RelativeSource Self}}"

...and specify the source of each binding:

<TextBlock Grid.Row="0" Grid.Column="1">
    <TextBlock.Text>
        <MultiBinding StringFormat="{}{0} ({1})">
            <Binding Path="VolumeLabel" RelativeSource="{RelativeSource AncestorType=UserControl}"/>
            <Binding Path="DriveName" RelativeSource="{RelativeSource AncestorType=UserControl}"/>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>
mm8
  • 163,881
  • 10
  • 57
  • 88
  • That's it! No idea how I didn't pick that up even after combing it for a while. Thanks :) – Reece Sep 06 '21 at 15:10