Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF One Way Binding broken

Tags:

mvvm

binding

wpf

Im trying to bind 2 different WPF controls to the same property in the ViewModel, a CheckBox.IsChecked and an Expander.IsExpanded. The behavior I want to achieve is to have the CheckBox affect the ViewModel (and therefore the Expander as well), but not the other way bound. Something like:

Checkbox Checked -> ViewModel property set to frue -> Expander.Expand
Checkbox Unchecked -> ViewModel property set to false -> Expander.Collapse
Expander Expanded -> Nothing else affected
Expander Collapsed -> Nothing else affected

Here's the XAML:

<Window x:Class="WpfApplication9.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Expander IsExpanded="{Binding IsChecked, Mode=OneWay}">
        <Expander.Header>
            <CheckBox IsChecked="{Binding IsChecked}" Content="Is Checked"/>
        </Expander.Header>
        <TextBlock Text="Expanded!"/>
    </Expander>
</Window>

and the Code:

using System.ComponentModel;
using System.Windows;

namespace WpfApplication9
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new ViewModel();
        }
    }

    public class ViewModel: INotifyPropertyChanged
    {
        private bool _isChecked;
        public bool IsChecked
        {
            get { return _isChecked; }
            set
            {
                _isChecked = value;
                NotifyPropertyChange("IsChecked");
            }
        }

        protected void NotifyPropertyChange(string PropertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged = delegate { };
    }
}

Now my problem is, as soon as I click on the Expander to expand / collapse it, the Binding seems to stop working. Can anyone explain to me why this is happening and how do I achieve this? Thanks in advance!

like image 282
Federico Berasategui Avatar asked Dec 30 '11 14:12

Federico Berasategui


3 Answers

New Answer

Discovered you could do this by setting your UpdateSourceTrigger to Explicit on your Expander. This keeps the binding as Two-Way, but never updates the Source since you're telling it not to update the source unless you explicitly tell it to.

<Expander IsExpanded="{Binding IsChecked, UpdateSourceTrigger=Explicit}">
    <Expander.Header>
        <CheckBox IsChecked="{Binding IsChecked}" Content="Is Checked"/>
    </Expander.Header>
    <TextBlock Text="Expanded!"/>
</Expander>

Leaving my old answer below so the comments make sense, and because I still feel there is no problem with view-specific code going in the code-behind of a view :)


Old Answer

Personally since this is View-Specific code, I see no problem with using a CheckBox click event to set the Expander's IsExpanded value.

private void MyCheckBox_Click(object sender, RoutedEventArgs e)
{
    MyExpander.IsExpanded = ((CheckBox)sender).IsChecked.GetValueOrDefault();
}

You could make this even more generic by removing the names and navigating the Visual Tree to find the Expander associated with the CheckBox. Here's an example using some Visual Tree Helpers I built

private void CheckBox_Click(object sender, RoutedEventArgs e)
{
    var chk = (CheckBox)sender;
    var expander = VisualTreeHelpers.FindAncestor<Expander>(chk);

    if (expander != null)
        expander.IsExpanded = chk.IsChecked.GetValueOrDefault();
}
like image 96
Rachel Avatar answered Nov 02 '22 19:11

Rachel


If you want the checkbox to affect the expander (but not vice versa) then bind the expander normally and use OneWayToSource on the checkbox:

<Window x:Class="WpfApplication9.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
   <Expander IsExpanded="{Binding IsChecked}">
      <Expander.Header>
         <CheckBox IsChecked="{Binding IsChecked, Mode=OneWayToSource}" Content="Is Checked"/>
      </Expander.Header>
      <TextBlock Text="Expanded!"/>
   </Expander>
</Window>

Using OneWayToSource on the checkbox will allow it to:

  • modify the underlying property (and therefore affect the expander, which is also bound to that property)
  • not be affected by other components that make changes to the underlying property
like image 1
Nate Kohl Avatar answered Nov 02 '22 18:11

Nate Kohl


If you'd like to avoid any code-behind, you can add a degree of separation between the Expander and CheckBox states in your ViewModel:

            private bool _isChecked;
            public bool IsChecked
            {
                get { return _isChecked; }
                set
                {
                    _isChecked = value;
                    NotifyPropertyChange("IsChecked");
                    IsExpanded = value;
                }
            }

            private bool _isExpanded;
            public bool IsExpanded
            {
                get { return _isExpanded; }
                set
                {
                    _isExpanded = value;
                    NotifyPropertyChange("IsExpanded");
                }
            }

    <Expander IsExpanded="{Binding IsExpanded}">
        <Expander.Header>
            <CheckBox IsChecked="{Binding IsChecked}" Content="Is Checked" x:Name="cb"/>
        </Expander.Header>
        <TextBlock Text="Expanded!"/>
    </Expander>
like image 1
Esoteric Screen Name Avatar answered Nov 02 '22 20:11

Esoteric Screen Name