powernemo
powernemo

Reputation: 99

UserControl Binding with Model - "property not found"

I made a super easy user control to browse files

<UserControl x:Class="DrumMapConverter.FileBrowserTextBox"
             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" 
             DataContext="{Binding RelativeSource={RelativeSource Self}}"
             mc:Ignorable="d" Height="24" d:DesignWidth="500">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Name="lblLabel" Text="{Binding Label, Mode=TwoWay}" MinWidth="150"/>
        <Button Grid.Column="1" Content=" ... " Click="BrowseButton_Click"/>
        <TextBox Grid.Column="2" Name="txtFilepath" Text="{Binding FilePath, Mode=TwoWay}"/>
    </Grid>

</UserControl>

With 2 Dependency properties :

Label and Filepath :

// FilePath
public static readonly DependencyProperty FilePathProperty =
    DependencyProperty.Register("FilePath", typeof(string), typeof(FileBrowserTextBox), new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(OnFilePathPropertyChanged)));

public string FilePath
{
    get { return (string)GetValue(FilePathProperty); }
    set { SetValue(FilePathProperty, value); }
}
static void OnFilePathPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    var obj = o as FileBrowserTextBox;
    if (obj == null)
        return;
    FileBrowserTextBox fileBrowserTextBox = (FileBrowserTextBox)o;
    fileBrowserTextBox.txtFilepath.Text = (string)e.NewValue;
}
// Label
public static readonly DependencyProperty LabelProperty =
    DependencyProperty.Register("Label", typeof(string), typeof(FileBrowserTextBox), new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(OnLabelPropertyChanged)));
public string Label
{
    get { return (string)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}
static void OnLabelPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    var obj = o as FileBrowserTextBox;
    if (obj == null)
        return;
    FileBrowserTextBox fileBrowserTextBox = (FileBrowserTextBox)o;
    fileBrowserTextBox.lblLabel.Text = (string)e.NewValue;
}

Then in my MainWindow ctor I have this

private DrumMapConverterDataModel model;
public MainWindow()
{
    InitializeComponent();
    model = new DrumMapConverterDataModel();
    DataContext = model;
}

The Model has 2 Properties :

private string inputFile = "";
public string InputFile
{
    get { return inputFile; }
    set { 
        inputFile = value;
        OnPropertyChanged("InputFile");
         }
}

private string outputFile = "";
public string OutputFile
{
    get { return outputFile; }
    set
    {
        outputFile = value;
        OnPropertyChanged("OutputFile");
    }
}

That i bind in MainWindow.XAML like this

<cust:FileBrowserTextBox  Label="Input File" FilePath="{Binding InputFile}"/>
<cust:FileBrowserTextBox  Label="Output File" FilePath="{Binding OutputFile}"/>

run it and get this errors

System.Windows.Data Error: 40 : BindingExpression path error: 'InputFile' property not found on 'object' ''FileBrowserTextBox' (Name='')'. BindingExpression:Path=InputFile; DataItem='FileBrowserTextBox' (Name=''); target element is 'FileBrowserTextBox' (Name=''); target property is 'FilePath' (type 'String') System.Windows.Data Error: 40 : BindingExpression path error: 'OutputFile' property not found on 'object' ''FileBrowserTextBox' (Name='')'. BindingExpression:Path=OutputFile; DataItem='FileBrowserTextBox' (Name=''); target element is 'FileBrowserTextBox' (Name=''); target property is 'FilePath' (type 'String')

Which basically means there is no InputFile and OutputFile in the UserControl, but I'm trying to bind the FilePath property of the control with InputFile and OutputFile of my model, why isn't working ?

Thanks in advance.

Upvotes: 3

Views: 1992

Answers (2)

dkozl
dkozl

Reputation: 33364

When you do

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

in FileBrowserTextBox you overwrite inherited DataContext changing binding context. Which means it will try to find InputFile and OutputFile properties inside FileBrowserTextBox control. Remove that line and change binding within FileBrowserTextBox so they don't affect DataContext, for example using RelativeSource, to something like:

<TextBlock Grid.Column="0" Name="lblLabel" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=Label}" MinWidth="150"/>
<TextBox Grid.Column="2" Name="txtFilepath" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=FilePath}"/>

also in PropertyChangedCallback for Label and FilePath you do:

fileBrowserTextBox.txtFilepath.Text = (string)e.NewValue;
fileBrowserTextBox.lblLabel.Text = (string)e.NewValue;

If you only want to change UI you don't need to handle property changed callback at all. You've used bindings in XAML which should do that for you, taking binding context is fine, and lines above will only overwrite these bindings with fixed value

Upvotes: 3

Rohit Vats
Rohit Vats

Reputation: 81253

You have set DataContext to itself on FileBrowserTextBox which makes all default bindings to search for property in itself.

Avoid setting DataContext for UserControl and bind using ElementName:

<UserControl x:Name="fileBrowserControl">
  ...
  <TextBlock Text="{Binding Label, ElementName=fileBrowserControl}"/>
  <Button Grid.Column="1" Content=" ... " Click="BrowseButton_Click"/>
  <TextBox Text="{Binding FilePath, ElementName=fileBrowserControl}"/>
  ...
</UserControl>

OR

If you want to keep DataContext for UserControl, you have to change code in Window to point to Window's DataContext explicitly like this:

<cust:FileBrowserTextBox Label="Input File"
                         FilePath="{Binding DataContext.InputFile, 
                                   RelativeSource={RelativeSource FindAncestor, 
                                       AncestorType=Window}}"/>

In place of RelativeSource, you can also use ElementName in binding by giving x:Name to your window and bind using ElementName like explained in case of UserControl.

Upvotes: 3

Related Questions