0

I want to bind some properties (FooClass.FooString) of a custom class (FooClass) to my MainWindow. Now below (Working known behavior) is the default working solution if binding some data to a gui.

What I want to do is in the second code block (Not working, but desired behavior). Expose some properties of another class objectto the gui and update it. **Problem**: TheTestStringis not getting updated (on the gui, code behind works). ThePropertyChangedeventis alsonull` (not subscribed?!).

Is this the wrong way how to bind data?

If I bind the complete FooClass object to the gui and set Path (of TextBlock) to Foo.FooString, the gui and string is updated. But I don't want to do it this way.

Is this the way how to solve it?


Working known behavior

public partial class MainWindow : Window
{
    public FooClass Foo { get; } = new FooClass();

    public MainWindow()
    {
        DataContext = this;
        InitializeComponent();

        Loaded += _OnLoaded;
    }

    private async void _OnLoaded(object sender, RoutedEventArgs e)
    {
        await Task.Delay(1000);
        Foo.ChangeTheProperty();
    }
}

public class FooClass : INotifyPropertyChanged
{
    public string FooString
    {
        get => _FooString;
        set
        {
            if (_FooString == value) return;
            _FooString = value;
            OnPropertyChanged();
        }
    }
    private string _FooString = "empty";

    public void ChangeTheProperty()
    {
        FooString = "Loaded";
    }

    // ##############################################################################################################################
    // PropertyChanged
    // ##############################################################################################################################

    #region PropertyChanged

    /// <summary>
    /// The PropertyChanged Eventhandler
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raise/invoke the propertyChanged event!
    /// </summary>
    /// <param name="propertyName"></param>        
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:MainWindow}"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBlock Text="{Binding Path=Foo.FooString}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Grid>
</Window>

Not working, but desired behavior

public partial class MainWindow : Window
{
    public string TestString => _Foo.FooString;


    private readonly FooClass _Foo;

    public MainWindow()
    {
        _Foo = new FooClass();
        DataContext = this;
        InitializeComponent();

        Loaded += _OnLoaded;
    }

    private async void _OnLoaded(object sender, RoutedEventArgs e)
    {
        await Task.Delay(1000);
        _Foo.ChangeTheProperty();
    }
}

public class FooClass : INotifyPropertyChanged
{
    public string FooString
    {
        get => _FooString;
        set
        {
            if (_FooString == value) return;
            _FooString = value;
            OnPropertyChanged();
        }
    }
    private string _FooString = "empty";

    public void ChangeTheProperty()
    {
        FooString = "Loaded";
    }

    // ##############################################################################################################################
    // PropertyChanged
    // ##############################################################################################################################

    #region PropertyChanged

    /// <summary>
    /// The PropertyChanged Eventhandler
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raise/invoke the propertyChanged event!
    /// </summary>
    /// <param name="propertyName"></param>        
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:MainWindow}"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBlock Text="{Binding Path=TestString}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Grid>
</Window>

Solution 1

Subscribe to the Foo.PropertyChanged event and route it to MainWindow.PropertyChanged.

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public FooClass Foo { get; } = new FooClass();

    public MainWindow()
    {
        Foo.PropertyChanged += (sender, args) => OnPropertyChanged(args.PropertyName);
        DataContext = this;
        InitializeComponent();

        Loaded += _OnLoaded;
    }

    private async void _OnLoaded(object sender, RoutedEventArgs e)
    {
        await Task.Delay(1000);
        Foo.ChangeTheProperty();
    }


    // ##############################################################################################################################
    // PropertyChanged
    // ##############################################################################################################################

    #region PropertyChanged

    /// <summary>
    /// The PropertyChanged Eventhandler
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raise/invoke the propertyChanged event!
    /// </summary>
    /// <param name="propertyName"></param>
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion

}
Dominic Jonas
  • 4,717
  • 1
  • 34
  • 77
  • Yes you are correct you need to do the binding through your 'working known behavior' section. That or inside your MainWindow you need to subscribe to the INotifyPropertyChanged.PropertyChanged event and then raise the same event from inside your MainWindow class. – Bijington May 09 '18 at 06:59
  • 1
    Instead of binding `this` aka `MainWindow` class to `DataContext`, why don't you just bind `Foo` to `DataContext`? Then `TextBlock`'s `Text` can be set to bind to `FooString`. This makes life easier. – kurakura88 May 09 '18 at 07:12
  • @Bijington Have a look at **Solution 1**. Did you mean that? @ kurakura88 In my app I have more than one different class object and not only one. – Dominic Jonas May 09 '18 at 07:32
  • 1
    As a note, implementing INotifyPropertyChanged in the MainWindow is pointless. There is no property where a change notification should be fired. – Clemens May 09 '18 at 07:46
  • 1
    @DominicJonas Yes that is what I meant. I wouldn't recommend this approach though as it goes against recommended practises. You should really just stick with your original approach I was just pointing out that this is technically possible. – Bijington May 09 '18 at 08:12

1 Answers1

0

I might not have fully understood what you want, but here is a working example of data binding, that is somewhat close to your example.

The two main changes were:

  1. Set the datacontext to the VM and not the code behind
  2. Actually give OnPropertyChanged the argument it needs to correctly trigger the refresh, the name of the property.

Result: enter image description here

MainWindow.xaml

<Window x:Class="ListViewColor.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Grid>
        <TextBlock Text="{Binding Foo.FooString}" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Aqua"/>
    </Grid>
</Window>

MainWindow.xaml.cs

namespace ListViewColor
{
    public partial class MainWindow : Window
    {
        public FooClass Foo { get; } = new FooClass();

        public MainWindow()
        {
            DataContext = this;
            InitializeComponent();
            Loaded += _OnLoaded;
        }

        private async void _OnLoaded(object sender, RoutedEventArgs e)
        {
            await Task.Delay(1000);
            Foo.ChangeTheProperty();
        }
    }
}

FooClass.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class FooClass : INotifyPropertyChanged
{
    private string _FooString = "Empty";
    public string FooString
    {
        get
        {
            return _FooString;
        }
        set
        {
            if (_FooString == value) return;
            _FooString = value;
            OnPropertyChanged();
        }
    }

    public void ChangeTheProperty()
    {
        FooString = "Loaded";
    }

    #region PropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

I hope that helps!

Ruben Bohnet
  • 392
  • 1
  • 12
  • 1
    Thanks for reply. 1. Actually it is working with `DataContext = this`. Then u have to define your path like `Path=Foo.FooString`. 2. Have a look at https://stackoverflow.com/questions/22580623/is-callermembername-slow-compared-to-alternatives-when-implementing-inotifypro – Dominic Jonas May 09 '18 at 08:47
  • @DominicJonas Hey, you are right both times! I learned something. But then I don't get the problem, since it works flawless even with those changes! Do you need something beside the above edited code? – Ruben Bohnet May 09 '18 at 08:53