0

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.

1 Answer 1

1

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;
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.