【问题标题】:How to avoid Converters clashing with multibinding in WPF code behind如何避免转换器与背后的 WPF 代码中的多重绑定发生冲突
【发布时间】:2012-08-21 14:29:46
【问题描述】:

我在后面的代码中动态创建 WPF 元素,对于 Grid 中的每一行,我正在构建它由一个 CheckBox 和一个动态编号 TextBoxes 组成。需要的交互如下:

  • 如果一行中的所有TextBoxes 的值为0,则设置CheckBox IsChecked 属性到 true 并禁用它。
  • 如果TextBoxes 之一从0 更改,请启用 CheckBox 并将IsChecked 设置为false。
  • 如果用户点击CheckBox,设置所有关联的TextBoxes 到0 并禁用CheckBox

我能够使用此代码完成最后一个的第一部分:

Binding setScoreToZeroIfIsNormalChecked = new Binding("IsChecked");
setScoreToZeroIfIsNormalChecked.Source = this.NormalCheckBoxControl;
setScoreToZeroIfIsNormalChecked.Converter = m_NormalCheckBoxJointScoresConverter;
tempJointScoreControl.JointScoreContainer.SetBinding(ContainerBase.SingleAnswerProperty, setScoreToZeroIfIsNormalChecked);

和转换器:

public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    if (value is bool && targetType == typeof(Answer))
    {
        if ((bool)value)
        {
            Answer answer = new Answer();
            answer.Value = "0";
            answer.DisplayValue = "0";
            return answer;
        }
        else
            return null;
    }
    else
    {
        return null;
    }
}

但是,在尝试创建另一个转换器来完成其他功能时,我遇到了转换器相互踩踏的问题,因为所有功能都基于 CheckBox.IsChecked 属性。

是否可以使用一两个多重绑定转换器来完成上述所有任务?我真的很想避免为了做到这一点而不得不创建一大堆事件并维护它们。

【问题讨论】:

  • 您在代码中创建控件是否有特定原因?
  • 您使用的是 MVVM 设计模式吗?这将是在您的数据项中完成的非常简单的逻辑,而不是使用转换器来使您的 UI 完成您想要的。如果您不使用 MVVM,最好只处理 CheckBox.Click 和 TextBox.TextChanged 事件并调整后面代码中其他对象的值。您应该只需要两个事件,一个用于所有CheckBoxes,一个用于所有TextBoxes。两者都只能影响与更改的项目共享相同Grid.Row 的项目。
  • @DanielHilgarth 一切都是动态创建的,所以我所有的 UserControls xaml 只代表一个实例。

标签: c# wpf dynamic-binding


【解决方案1】:

这相对容易。一切都应该围绕 CheckBox IsChecked 属性解决。 原因很简单,它是双向属性。所以要么你可以修改它,要么CheckBox可以修改它。

所以你做什么,你使用 MultiBinding,就像这样:

    MultiBinding multiBinding = new MultiBinding();
    multiBinding.Converter = multiBindingConverter;

    multiBinding.Bindings.Add(new Binding("Text") { Source = txtbox1});
    multiBinding.Bindings.Add(new Binding("Text") { Source = txtbox2});

    multiBinding.NotifyOnSourceUpdated = true;//this is important. 
    checkBox.SetBinding(CheckBox.IsCheckedProperty, multiBinding);

在您的 multiBindingConverter 中,您将 object[] 值作为第一个参数,您需要将其转换为 IList 并对其进行迭代 && 进行计算,如果您应该返回 true/false。(IsChecked=true 或 false )

现在将 CheckBox IsEnabled 绑定到 CheckBox IsChecked 属性,并使用 BooleanInverterConverter。 (如果 CheckBox 被选中,则应禁用,反之亦然)

最后一步是让 TextBoxes 监听 CheckBox 的实际 IsChecked 属性。 如果是 TRUE,它们都应该显示 0 值,否则它们可以显示他们想要的。

所以,创建一个新的 MultiBinding。

    MultiBinding multiBinding = new MultiBinding();
    multiBinding.Converter = textboxMultiBindingConverter;

    multiBinding.Bindings.Add(new Binding("IsChecked") { Source = checkbox1});
    multiBinding.Bindings.Add(new Binding("Text") { Source = textbox1});

    multiBinding.NotifyOnSourceUpdated = true;//this is important. 
    textbox1.SetBinding(TextBox.Text, multiBinding);

textboxMultiBindingConverter 中的想法是,如果 value[0]==FALSE,则返回 Text(value[1]);如果 value[0]==TRUE,则返回“0”。

【讨论】:

  • 谢谢,这非常有帮助!我能够实现很多这样的功能,但是在调用 ConvertBack 方法时出现错误(例如,当我单击 CheckBox 时)。我从未见过在 MultiBinding 转换器中实现 ConvertBack() 方法,是否需要将 Binding Mode 设置为 OneWay?
  • 谢谢你;通过一些修改,我能够使用绑定/转换器完成所有操作,除了在切换复选框时将行中的所有关联文本框设置为“0”;事实证明,动态创建控件的方式非常混乱/令人费解,因此我所要做的就是使用简单的 foreach 循环向 CheckBox.IsChecked 添加一个侦听器。
【解决方案2】:

如果你使用 MVVM,这个问题可以很容易地解决。

您将拥有一个表示网格中一行的 ViewModel。每个文本框都有一个属性,复选框有一个属性。

此外,您将有一个 ViewModel 用于包含 Grid 的 View,并且此 ViewModel 将公开一组行 ViewModel。

您所在行的 ViewModel:

public class AnswersViewModel : ViewModelBase // From MvvmLight
{
    public bool IsAnswered
    {
        get { return _isAnswered; }
        set
        {
            if(value == _isAnswered)
                return;
            _isAnswered = value;
            if(_isAnswered)
            {
                Answer1 = "0";
                Answer2 = "0";
            }

            RaisePropertyChanged("IsAnswered");
        }
    }

    public string Answer1
    {
        get { return _answer1; }
        set
        {
            if(value == _answer1)
                return;

            _answer1 = value;
            RaisePropertyChanged("Answer1");

            if(_answer1 == "0" && _answer2 == "0")
            {
                _isAnswered = true;
                RaisePropertyChanged("IsAnswered");
            }
        }
    }

    // The implementation of Answer2 is similar to Answer1
}

视图的 ViewModel:

public class FooViewModel : ViewModelBase
{
    public ObservableCollection<AnswersViewModel> Answers
    {
        get { return _answers; }
    }
}

您的视图将包含带有ItemsSource="{Binding Answers}" 的网格和绑定到AnswersViewModel 属性的项目的 ControlTemplate。

禁用我将通过样式中的触发器处理的复选框。

【讨论】:

  • 你有 MVVM 的例子吗?我以前在几个更简单的应用程序中使用过它,但如果说我完全理解发生了什么,那我就是在撒谎;我从来没有完全理解它,最终通过大量的试验和错误摸索着它
  • @user564636 如果您有兴趣,我有一个very basic MVVM example on my blog。一开始我也很难理解 MVVM 设计模式,因为当时所有现有的材料对我来说都太高级了,所以一旦我理解了它,我就为我的博客写了一个非常基本的 MVVM 示例来尝试帮助其他人也更容易理解它。如果您使用 WPF,这确实是一个很棒的设计模式 :)
  • @Rachel 和 Daniel,感谢你们为我提供了一些关于 MVVM 方法的见解!这绝对是我将学习并希望在未来的应用程序中实现的东西。不幸的是,由于时间限制,将已经开发的内容转换为 MVVM 范式是不可行的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-20
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
相关资源
最近更新 更多