'WPF - MVVM binding in UserControl

I'm testing a sample binding in MVVM pattern. I'm using package GalaSoft.MvvmLight. Binding from MainViewModel to MainWindow is normal but I can't binding data from a ViewModel (ImageViewModel) to View (ImageView). All my code is below

in App.xaml

<Application x:Class="WpfApplication1.App"         
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr- 
namespace:WpfApplication1" StartupUri="MainWindow.xaml" 
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" 
xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
  <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr- 
  namespace:WpfApplication1.ViewModel" />
</ResourceDictionary>
</Application.Resources>
</Application>

in MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:v="clr-namespace:WpfApplication1.View"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApplication1"
    DataContext="{Binding Main, Source={StaticResource Locator}}"
    mc:Ignorable="d"
    Title="MainWindow" Height="800" Width="1000">
<Grid>
    <Grid Grid.Column="1">
        <v:ImageView DataContext="{Binding ImageVM}"/>
    </Grid>
       
</Grid>

in ImageView.xaml

<UserControl x:Class="WpfApplication1.View.ImageView"
         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:WpfApplication1.View"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         Name="ucImage">

<UserControl.DataContext>
    <Binding Path="Main.ImageVM" Source="{StaticResource Locator}"/>
</UserControl.DataContext>

<Grid>
    <TextBox x:Name="label" Text="{Binding TestText, ElementName=ucImage}" Width="100" 
     Height="50"/>
</Grid>
</UserControl>

in ViewModelLocator.cs

using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication1.ViewModel
{
public class ImageViewModel: ViewModelBase
{
    public string _TestText;
    public string TestText
    {
        get
        {
            return _TestText;
        }
        set
        {
            _TestText = value;
            RaisePropertyChanged(() => this.TestText);
        }
    }


    public ImageViewModel()
    {
        TestText = "asdasdasdasdas";
    }
}
}

in MainViewModel

public class MainViewModel : ViewModelBase
{
    private ImageViewModel _ImageVM;
    public ImageViewModel ImageVM
    {
        get { return _ImageVM; }
        set { Set(ref _ImageVM, value); }
    }


    public MainViewModel()
    {
        ImageVM = new ImageViewModel();
    }
}

in ImageViewModel.cs

using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication1.ViewModel
{
public class ImageViewModel: ViewModelBase
{
    public string _TestText;
    public string TestText
    {
        get
        {
            return _TestText;
        }
        set
        {
            _TestText = value;
            RaisePropertyChanged(() => this.TestText);
        }
    }


    public ImageViewModel()
    {
        TestText = "asdasdasdasdas";
    }
}

}

This error from output is

System.Windows.Data Error: 40 : BindingExpression path error: 'TestText' property not found on 'object' ''ImageView' (Name='ucImage')'. BindingExpression:Path=TestText; DataItem='ImageView' (Name='ucImage'); target element is 'TextBox' (Name='label'); target property is 'Text' (type 'String')

Anyone who comes up with a solution would be greatly appreciated!



Solution 1:[1]

The expression

Text="{Binding TestText, ElementName=ucImage}"

expects a TestText property in the ImageView control, which apperently does not exists - that is what the error message says.

You would simply write the following to make the element in the control's XAML bind directly to the view model object in its DataContext:

<TextBox Text="{Binding TestText}" .../>

In order to make the control independent of a specific view model, it should expose a bindable property, i.e. a dependency property like this:

public partial class ImageView : UserControl
{
    public ImageView()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register(
            nameof(Text), typeof(string), typeof(ImageView));

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }
}

which would work with

<TextBox Text="{Binding Text, ElementName=ucImage}"

in the control's XAML and without explictly setting the control's DataContext, i.e. without the <UserControl.DataContext> section in its XAML.

The property would be bound like

<v:ImageView DataContext="{Binding ImageVM}" Text="{Binding TestText}"/>

or just

<v:ImageView Text="{Binding ImageVM.TestText}"/>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1