I have a UserControl who's code-behind has declared a dependency property. I also have another UserControl who is serving as the main UI.
The main UI usercontrol has set its DataContext to a viewmodel and that viewmodel has a property SelectedFile. The XAML for the main UI usercontrol declares another usercontrol and binds the dependency property "SelectedFilePath" to the property SelectedFile.
I would like to keep the UserControl FileSelectControl encapsulated from the UserControl MainView. Ideally FileSelectControl can contain all the code it needs to select a file and pass the required info on to MainWindow. So I need the child control to pass its data to the parent control.
MainView.xaml:
<UserControl x:Class="Whiteking_UnitTest_Updater.Views.MainView"
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:Whiteking_UnitTest_Updater.Views"
xmlns:viewmodel="clr-namespace:Whiteking_UnitTest_Updater.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Background="Black"
d:DataContext="{d:DesignInstance Type=viewmodel:MainViewModel}">
<Grid>
<Border BorderBrush="Lime" BorderThickness="1"/>
<local:FileSelectControl Height="30" Width="500" SelectedFilepath="{Binding SelectedFile, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding SelectedFile, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,100,0,0"/>
<Button x:Name="CloseButton"
Content="EXIT"
Margin="50,35"
Padding="15,5"
FontSize="18"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Style="{StaticResource Button_Custom}"
Command="{Binding ExitCommand}"/>
</Grid>
</UserControl>
MainView.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Whiteking_UnitTest_Updater.ViewModels;
namespace Whiteking_UnitTest_Updater.Views
{
/// <summary>
/// Interaction logic for MainView.xaml
/// </summary>
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
FileSelectControl.xaml:
<UserControl x:Class="Whiteking_UnitTest_Updater.Views.FileSelectControl"
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:Whiteking_UnitTest_Updater.Views"
d:DataContext="{d:DesignInstance Type=local:FileSelectControl}"
mc:Ignorable="d"
d:DesignHeight="30"
d:DesignWidth="300">
<Grid>
<Button x:Name="FileSelectButton"
Grid.Column="1"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:FileSelectControl}}, Path=SelectedFilepath, UpdateSourceTrigger=PropertyChanged}"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:FileSelectControl}}, Path=SelectFileCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="Lime"/>
<Setter Property="BorderBrush" Value="Lime"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="Container"
BorderThickness="1"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="Content"
Grid.Column="0"
Padding="5,5,20,5"
VerticalAlignment="Center"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Text="{TemplateBinding Content}"/>
<Border Grid.Column="1"
BorderThickness="1,0,0,0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding Foreground}"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Grid}}, Path=ActualHeight}">
<Path x:Name="Search_Icon"
Margin="5"
Stretch="Uniform"
Fill="{TemplateBinding Foreground}">
<Path.Data>
M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z
</Path.Data>
</Path>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Lime"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="BorderBrush" Value="Lime"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</UserControl>
FileSelectControl.xaml.cs:
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using Whiteking_UnitTest_Updater.Services;
namespace Whiteking_UnitTest_Updater.Views
{
/// <summary>
/// Interaction logic for FileSelectControl.xaml
/// </summary>
public partial class FileSelectControl : UserControl, INotifyPropertyChanged
{
public FileSelectControl()
{
InitializeComponent();
SelectFileCommand = new RelayCommand(SelectFile);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
Trace.WriteLine("PropertyChanged called, property Name: " + propertyName);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#region DependencyProperty_SelectedFilepath
public string SelectedFilepath
{
get => (string)GetValue(SelectedFilepathProperty);
set
{
Trace.WriteLine("SelectedFilepathProperty SET was called");
SetValue(SelectedFilepathProperty, value);
NotifyPropertyChanged();
}
}
public static readonly DependencyProperty SelectedFilepathProperty = DependencyProperty.Register(
"SelectedFilepath",
typeof(string),
typeof(FileSelectControl),
new PropertyMetadata(""));
#endregion
public RelayCommand SelectFileCommand { get; private set; }
private void SelectFile(object sender)
{
string filepath;
OpenFileDialog dialogue = new OpenFileDialog();
bool? results = dialogue.ShowDialog();
if (results == true)
{
//Get the path of specified file
filepath = dialogue.FileName;
SelectedFilepath = filepath;
}
}
}
}
My code builds without any error, The binding even works one-way, I can edit a textbox in the main UI usercontrol and it sets the dependancy property and the changes are visualized as I would expect.
The problem, is that when the UserControl with the dependency property sets its the property on its own through a RelayCommand, this doesn't bubble up to the parent USerControl and change the bound property.
I'm pretty stumped, and I've tried fudging the code around... to no avail.
I was hoping someone could help with this. I would like my UserControl "FileSelectControl" to be able to trigger the selectedFilePath property to change when a file is selected and have that affect bound parameters in the parent.
The binding in the MainView
should be TwoWay
:
<local:FileSelectControl Height="30" Width="500"
SelectedFilepath="{Binding SelectedFile, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Alternatively, you can define the dependency property to bind two-way by default in the FileSelectControl
:
public static readonly DependencyProperty SelectedFilepathProperty = DependencyProperty.Register(
"SelectedFilepath",
typeof(string),
typeof(FileSelectControl),
new FrameworkPropertyMetadata("") { BindsTwoWayByDefault = true });
Also note that there is no reason for the control to implement INotifyPropertyChanged
when it sets a dependency property. This implementation is cleaner and should work just fine:
public partial class FileSelectControl : UserControl
{
public FileSelectControl()
{
InitializeComponent();
SelectFileCommand = new RelayCommand(SelectFile);
}
#region DependencyProperty_SelectedFilepath
public string SelectedFilepath
{
get => (string)GetValue(SelectedFilepathProperty);
set => SetValue(SelectedFilepathProperty, value);
}
public static readonly DependencyProperty SelectedFilepathProperty = DependencyProperty.Register(
"SelectedFilepath",
typeof(string),
typeof(FileSelectControl),
new FrameworkPropertyMetadata("") { BindsTwoWayByDefault = true });
#endregion
public RelayCommand SelectFileCommand { get; private set; }
private void SelectFile(object sender)
{
string filepath;
OpenFileDialog dialogue = new OpenFileDialog();
bool? results = dialogue.ShowDialog();
if (results == true)
{
//Get the path of specified file
filepath = dialogue.FileName;
SelectedFilepath = filepath;
}
}
}