Alex Sappy Alex Sappy - 2 months ago 17
C# Question

WPF: two way data binding is not working

I found some silimar questions, but these are not exactly what i need.
I want to bound stackpanel "IsEnabled" value to bool "!IsIterrupted" value of my Items. Here is my XAML file:

<ListView ItemsSource="{Binding Path=Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel IsEnabled="{Binding !IsInterrupted, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<Button Command="{Binding Path=StopThreadCommand, Source={StaticResource viewModel}}" CommandParameter="{Binding Id}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>


This is how items looks like:

public class ThreadDecorator : BaseThread , INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged;

private bool _is_interrupted;
public bool IsInterrupted
{
get { return _is_interrupted; }
set
{
_is_interrupted = value;
OnPropertyChanged("IsInterrupted");
}
}

protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
...
}


And my ViewModel:

public class ThreadsViewModel : DependencyObject
{

private ThreadsModel _model;
public ThreadsModel Model
{
get { return _model; }
set
{
_model = value;
}
}

public ICollectionView Items
{
get { return (ICollectionView)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}

public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ICollectionView), typeof(ThreadsViewModel), new PropertyMetadata(null));

public StopThreadCommand StopThreadCommand { get; set; }

public ThreadsViewModel()
{
this.Model = new ThreadsModel();
Items = CollectionViewSource.GetDefaultView(Model.Threads);
this.StopThreadCommand = new StopThreadCommand(this);
}

public void InterruptThread(int id)
{
_model.InterruptThread(id);
}
}


StopThreadCommand:

public class StopThreadCommand : ICommand
{
public ThreadsViewModel ViewModel {get; set;}
public StopThreadCommand(ThreadsViewModel viewModel)
{
this.ViewModel = viewModel;
}

public bool CanExecute(object parameter)
{
return true;
}

public void Execute(object parameter)
{
this.ViewModel.InterruptThread((int)parameter);
}
}


When I am clicking on Stop button, IsInterrupted value is changing from false to true, and stackpanel have to become disabled, but UI does not update. Help please!

Answer

The default property of Binding is Path, which is a path to a property/sub-property of the DataContext. It's not an arbitrary C# expression. So you're setting Binding.Path to "!IsInterrupted". !IsInterrupted won't evaluate to the boolean inverse of IsInterrupted; it won't evaluate to anything. It'll get you this in the debug output stream:

System.Windows.Data Error: 40 : BindingExpression path error: '!IsInterrupted' property not found on 'object' 'ThreadDecorator' blah blah blah

<StackPanel 
    IsEnabled="{Binding !IsInterrupted, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">

One way to do this is to write a boolean-inverse value converter (stolen verbatim from Chris Nicol's answer at the other end of that link):

[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBooleanConverter: IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        if (targetType != typeof(bool))
            throw new InvalidOperationException("The target must be a boolean");

        return !(bool)value;
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }

    #endregion
}

Usage:

<UserControl.Resources>
    <local:InverseBooleanConverter x:Key="InverseBooleanConverter" />
</UserControl.Resources>

<!-- stuff etc. -->

IsEnabled="{Binding Path=IsReadOnly, Converter={StaticResource InverseBooleanConverter}}"

You could also write a Style with a DataTrigger that sets IsEnabled to False if IsInterrupted is true.