I created a small File Browser Control:

<UserControl x:Class="Test.UserControls.FileBrowserControl"
             d:DesignHeight="44" d:DesignWidth="461" Name="Control">
    <Grid Margin="3">
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        <TextBox  Margin="3" Text="{Binding SelectedFile}" IsReadOnly="True" TextWrapping="Wrap" />
        <Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />

With the following code behind:

public partial class FileBrowserControl : UserControl
    public ICommand BrowseCommand { get; set; }
    //The dependency property
    public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
        typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
    public string SelectedFile { get{ return (string)GetValue(SelectedFileProperty);}  set{ SetValue(SelectedFileProperty, value);}}
    //For my first test, this is a static string
    public string Filter { get; set; }

    public FileBrowserControl()
        BrowseCommand = new RelayCommand(Browse);
        Control.DataContext = this;
    private void Browse()
        SaveFileDialog dialog = new SaveFileDialog();
        if (Filter != null)
            dialog.Filter = Filter;
        if (dialog.ShowDialog() == true)
            SelectedFile = dialog.FileName;

And I use it like this:

<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>

(SelectedFile is Property of the ViewModel of the usercontrol using this control)

Currently the issue is that when I click on Browse, the textbox in the usercontrol is correctly updating, but the SelectedFile property of the viewmodel parent control is not set(no call to the set property).

If I set the Mode of the binding to TwoWay, I got this exception:

An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.

So what did I do wrong?

Solution 1

The problem is that you set your UserControl's DataContext to itself in its constructor:

DataContext = this;

You should not do that, because it breaks any DataContext based Bindings, i.e. to a view model instance that is provided by property value inheritance of the DataContext property

Instead you would change the binding in the UserControl's XAML like this:

<TextBox Text="{Binding SelectedFile,
                RelativeSource={RelativeSource AncestorType=UserControl}}" />

Now, when you use your UserControl and write a binding like

<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />

the SelectedFile property gets bound to a SelectedFile property in your view model, which should be in the DataContext inherited from a parent control.

Solution 2

Do not ever set DataContext of UserControl inside usercontrol:


this.DataContext = someDataContext;

because if somebody will use your usercontrol, its common practice to set its datacontext and it is in conflict with what you have set previously

<my:SomeUserControls DataContext="{Binding SomeDataContext}" />

Which one will be used? Well, it depends...

The same applies to Name property. you should not set name to UserControl like this:

<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />

because it is in conflict with

<my:SomeUserControls Name="SomeOtherName" />

In your control, just use RelativeSource Mode=FindAncestor:

<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />

To your question on how are all those third party controls done: They use TemplateBinding. But TemplateBinding can be used only in ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate

In usercontrol the xaml represents Content of UserControl, not ControlTemplate/

Solution 3

Using this:

<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...

The FileBrowserControl's DataContext has already been set to itself, therefore you are effectively asking to bind to the SelectedFile where the DataContext is the FileBrowserControl, not the parent ViewModel.

Give your View a name and use an ElementName binding instead.

SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"