Problema con el enlace DependencyProperty
Creé un pequeño control del explorador de archivos:
<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>
Con el siguiente código detrás:
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;
}
}
}
Y lo uso así:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>
(SelectedFile es propiedad del ViewModel del control de usuario que usa este control)
Actualmente, el problema es que cuando hago clic en Examinar, el cuadro de texto en el control de usuario se actualiza correctamente, pero la propiedad SelectedFile del control principal del modelo de vista no está configurada (no se llama a la propiedad establecida).
Si configuro el Modo de enlace en TwoWay, obtengo esta excepción:
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
Entonces, ¿qué hice mal?
El problema es que configuraste el DataContext de tu UserControl en su constructor:
DataContext = this;
No debe hacer eso, porque rompe cualquier enlace basado en DataContext, es decir, a una instancia de modelo de vista proporcionada por la herencia del valor de propiedad de la propiedad DataContext.
En su lugar, cambiaría el enlace en el XAML del UserControl de esta manera:
<TextBox Text="{Binding SelectedFile,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Ahora, cuando usas tu UserControl y escribes un enlace como
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />
la propiedad SelectedFile se vincula a una propiedad SelectedFile en su modelo de vista, que debe estar en el DataContext heredado de un control principal.
Nunca configures DataContext de UserControl dentro del control de usuario:
ESTO ESTÁ MAL:
this.DataContext = someDataContext;
porque si alguien va a usar su control de usuario, es una práctica común establecer su contexto de datos y está en conflicto con lo que usted configuró anteriormente.
<my:SomeUserControls DataContext="{Binding SomeDataContext}" />
¿Cuál se utilizará? Bueno, eso depende...
Lo mismo se aplica a la propiedad Nombre. no deberías establecer el nombre en UserControl de esta manera:
<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />
porque está en conflicto con
<my:SomeUserControls Name="SomeOtherName" />
SOLUCIÓN:
Bajo su control, simplemente use RelativeSource Mode=FindAncestor:
<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />
A su pregunta sobre cómo se realizan todos esos controles de terceros: utilizan TemplateBinding. Pero TemplateBinding sólo se puede utilizar en ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
En el control de usuario, el xaml representa el contenido de UserControl, no ControlTemplate/
Usando esto:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...
El DataContext de FileBrowserControl ya se ha configurado a sí mismo, por lo tanto, efectivamente está solicitando vincularse al SelectedFile donde el DataContext es FileBrowserControl, no el ViewModel principal.
Asigne un nombre a su Vista y utilice un enlace ElementName en su lugar.
SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"