Search code examples
c#wpfuser-controls

UserControl with Collection Binding always returning Null


Problem: When attempting to bind an Observable Collection to my user control, it is always showing null at runtime.

Description: I have a user control as described below. The goal is to create a button to cycles through an array of images with every click. However, when I run this, ImageCollection is always null, regardless of how I setup the binding on the implementation side. I'm at a loss for why this is. The code is as follows:

XAML:

<UserControl x:Class="kTrack.ToggleImage"
             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:kTrack"
             mc:Ignorable="d" 
             d:DesignHeight="{Binding ImageHeight}" d:DesignWidth="{Binding ImageWidth}">
    <Grid x:Name="LayoutRoot">
        <Button Click="ToggleImage_Click" Height="{Binding ImageHeight}" Width="{Binding ImageWidth}">
            <Image Source="{Binding ActiveImage}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ToolTip="{Binding ToolTip}" />
        </Button>
    </Grid>
</UserControl>

Code-Behind (Important Bits)

public partial class ToggleImage : UserControl, INotifyPropertyChanged
{      
    public static readonly DependencyProperty ImageCollectionProperty = DependencyProperty.Register("ImageCollection", typeof(ObservableCollection<string>), typeof(ToggleImage), new PropertyMetadata(null));

    ...

    public ObservableCollection<String> ImageCollection
    {
        get => (ObservableCollection<String>)GetValue(ImageCollectionProperty);
        set => SetValue(ImageCollectionProperty, value);
    }

    private ImageSource _activeImage;
    public ImageSource ActiveImage
    {
        get => _activeImage;
        set
        {
            _activeImage = value;
            OnPropertyChanged();
        }
    }

    private int _currentImageIndex;
    public int CurrentImageIndex
    {
        get => _currentImageIndex;
        set
        {
            _currentImageIndex = value;
            OnPropertyChanged();
        }
    }

    // Constructor
    public ToggleImage()
    {
        ImageCollection = new ObservableCollection<String>();
        InitializeComponent();
        LayoutRoot.DataContext = this;
        
        CurrentImageIndex = 0;

        if (ImageCollection.Count > 0)
        {
            ActiveImage = SetCurrentImage(CurrentImageIndex);
        }
    }

    ...

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

XAML Implementation:

<local:ToggleImage ImageHeight="32" ImageWidth="32"
    ImageCollection="{Binding Images, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

Code-Behind of Implementation:

public partial class MainWindow : kWindow
{
    public ObservableCollection<String> Images = new ObservableCollection<String>()
    {
        "pack://application:,,,/Resources/Icons/Close.png",
        "pack://application:,,,/Resources/Icons/Minimize.png",
        "pack://application:,,,/Resources/Icons/WindowOne.png"
    };

    public MainWindow()
    {
        InitializeComponent();
    }
}

Solution

  • for one, you should set DataContext in Window.

    for two, Binding work with properties, not fields: change Images field to property.

    public partial class MainWindow : kWindow
    {
        public ObservableCollection<String> Images { get; } = new ObservableCollection<String>()
        {
            "pack://application:,,,/Resources/Icons/Close.png",
            "pack://application:,,,/Resources/Icons/Minimize.png",
            "pack://application:,,,/Resources/Icons/WindowOne.png"
        };
    
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }
    }