2

I created a small File Browser Control:

<UserControl x:Class="Test.UserControls.FileBrowserControl"
             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" 
             d:DesignHeight="44" d:DesignWidth="461" Name="Control">
    <Grid Margin="3">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <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}" />
    </Grid>
</UserControl>

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()
    {
        InitializeComponent();
        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?

J4N
  • 19,480
  • 39
  • 187
  • 340

3 Answers3

12

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.

Clemens
  • 123,504
  • 12
  • 155
  • 268
  • It seems to be much better, thank you. You seems to be very aware of the good practices. I've a small other related question: currently this userControl has all its logic in the code behind. is it common? Because it's not really respecting the MVVM architecture that I'm trying to follow. – J4N Mar 11 '15 at 11:50
  • A UserControl usually has all its logic in its code-behind, at least those parts of the logic that are view related. Its code behind should however not (heavily) modifiy its view model. – Clemens Mar 11 '15 at 12:07
  • Ok, but let's say I've a Filter, InitialDirectory, SelectedFile, ... should I not be able to bind this to a ViewModel which contains those fields? – J4N Mar 11 '15 at 12:14
  • 1
    As shown in the answer, you may bind these properties to view model properties where you use your control. The UserControl's XAML may however also directly access view model properties as binding sources, but then you would possibly not need to declare additional properties in your UserControl. If e.g. the XAML of your control would directly access a SelectedFile property of a view model, there should not be an extra SelectedFile property in the control itself. – Clemens Mar 11 '15 at 12:20
4

Do not ever set DataContext of UserControl inside usercontrol:

THIS IS WRONG:

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" />

SOLUTION:
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/

Liero
  • 25,216
  • 29
  • 151
  • 297
1

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}"
Mike Eason
  • 9,525
  • 2
  • 38
  • 63
  • And in my head I hear an Unreal sound: `God like` :) Thank you very much, I didn't think that the binding in the parent was still refering to the DataContext of the child. But I'm a little lost. How all those third party library makes to have their usercontrols not requiring this `ElementName`? – J4N Mar 11 '15 at 08:53
  • That's a good question. I think it's because they use CustomControls, rather than UserControls, but I'm not entirely sure. You should post that as a new question. – Mike Eason Mar 11 '15 at 09:01
  • No problem :-) You should also mark the question as answered if it has solved your problem ;-) Thanks. – Mike Eason Mar 11 '15 at 09:11