Односторонняя привязка WPF

Я пытаюсь связать 2 разных элемента управления WPF с тем же свойством в ViewModel, CheckBox.IsChecked и Expander.IsExpanded. Поведение, которое я хочу достичь, состоит в том, чтобы CheckBox влиял на ViewModel (и, следовательно, на Expander), но не на другую сторону. Что-то вроде:

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

Здесь 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>

и код:

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 { };
    }
}

Теперь моя проблема в том, что, как только я нажимаю на Expander, чтобы развернуть/свернуть его, Binding, похоже, перестает работать. Может ли кто-нибудь объяснить мне, почему это происходит и как я могу это достичь? Спасибо заранее!

Ответ 1

Новый ответ

Обнаружено, что вы можете сделать это, установив UpdateSourceTrigger на Explicit на своем расширителе. Это сохраняет привязку как двухстороннюю, но никогда не обновляет источник, поскольку вы говорите ему не обновлять источник, если вы явно не указали его.

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

Оставив мой старый ответ ниже, чтобы комментарии были понятны, и потому что я все еще чувствую, что нет проблем с кодом, специфичным для просмотра, в коде для представления:)


Старый ответ

Лично, поскольку это код, специфичный для просмотра, я не вижу проблемы с использованием события кликов CheckBox, чтобы установить значение Expander IsExpanded.

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

Вы можете сделать это еще более общим, удалив имена и перемещаясь по визуальному дереву, чтобы найти Expander, связанный с CheckBox. Вот пример использования некоторых помощников визуального дерева, которые я построил.

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();
}

Ответ 2

Если вы хотите, чтобы флажок влиял на расширитель (но не наоборот), тогда обычно связывайте расширитель и используйте OneWayToSource в поле:

<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>

Использование OneWayToSource в этом флажке позволит:

  • изменить базовое свойство (и, следовательно, повлиять на расширитель, который также связан с этим свойством)
  • не будут затронуты другими компонентами, которые вносят изменения в базовое свойство

Ответ 3

Если вы хотите избежать кода, вы можете добавить степень разделения между состояниями Expander и CheckBox в 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>