【问题标题】:Using a TwoWay binding to an ItemsSource with an IValueConverter使用 TwoWay 绑定到带有 IValueConverter 的 ItemsSource
【发布时间】:2014-03-31 09:18:45
【问题描述】:

在我的 ViewModel 中,我有一个模型类 Foo,其中包含一个属性 Bar

class Foo
{
    byte Bar { get; set; }
}

我想要在我的视图中将该属性显示为代表该值位的复选框列表。我设法通过使用ItemsControl 和绑定转换器来做到这一点。

    <ItemsControl ItemsSource="{Binding Bar, Converter={StaticResource ValueToLed}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <CheckBox Style="{StaticResource LedCheckBox}" 
                                    IsChecked="{Binding Value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                    ToolTip="{Binding Name}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

我的转换器使用BitArray 从该值中获取bool 的数组。我还将每个 bool 值包装在一个包装器类中,以便为该值提供更多属性,例如索引和名称(用于 toollip)。你可以说我在IValueConverter 中为这样的 CheckBox 构建了一个 ViewModel。 这很好用,但在其他方向上却不行。当我更改其中一个位时,我希望我的 ConvertBack 将更新后的值存储在我的 ViewModel/Model 中,但我的 ConvertBack 永远不会被调用。我已经阅读了一些与该主题相关的内容,到目前为止我的结论是,这是不可能的,因为只有在 ItemsSource 本身发生变化而不是其中的项目时才会调用 ConvertBack。那么这是真的吗?

这种方法的唯一(?)替代方法是在我的 ViewModel 中而不是在 IValueConverter 中进行转换,对吧?

【问题讨论】:

  • 对不起,我的意思是byte,实际上是一个无符号字节。

标签: c# wpf mvvm ivalueconverter itemssource


【解决方案1】:

C# 语言(也是 C# 开发人员)应该不知道(即过敏)内部位表示。虽然您需要任何布尔变量,但只需使用它即可。

如果您的程序处理一些低级接口(例如 I/O 流中的位掩码),只需在最接近的可能级别打包/解包实际字节数组。

也就是说,WPF 和位本身非常敏感,绑定到单个位的复选框需要无用但混乱的代码。我不建议这样做。

只需在 MVVM 模式中公开您的 un/check 变量,作为列表中模板化的每个项目的一部分。复选框绑定很简单,您的代码也很干净。

编辑:我现在明白你的问题是什么。

您是否在 bool 的包装器中实现了 INotifyPropertyChanged 模式,特别是针对“Value”属性?

EDIT2:这对我来说很好用。

    <ItemsControl ItemsSource="{Binding Path=Bar}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <CheckBox  
                    IsChecked="{Binding Value,Mode=TwoWay}"
                    ToolTip="{Binding Name}"
                    />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

这是视图模型:

class MyCollection
{
    public IEnumerable<MyWrapper> Bar { get; set; }
}

class MyWrapper : INotifyPropertyChanged
{
    #region PROP Value

    private bool _value;

    public bool Value
    {
        get { return this._value; }
        set
        {
            if (this._value != value)
            {
                this._value = value;
                this.OnPropertyChanged("Value");
                Console.WriteLine("changed="+ this._value);
            }
        }
    }

    #endregion


    #region EVT PropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = this.PropertyChanged;

        if (handler != null)
        {
            handler(
                this,
                new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion

}

单击复选框时,会修改相关属性。 Console.Writeline 证明了这一点。

EDIT3:更正代码如下:

class MyCollection : INotifyPropertyChanged
{
    #region PROP Bar

    private byte _bar;

    public byte Bar
    {
        get { return this._bar; }
        set
        {
            if (this._bar != value)
            {
                this._bar = value;
                this.OnPropertyChanged("Bar");
                Console.WriteLine("bar="+ this._bar);
                this.UpdateItems();
            }
        }
    }

    #endregion


    public void UpdateItems()
    {
        //rebuild the children collection
        var collection = new List<MyWrapper>();
        for (int i = 0; i < 8; i++)
        {
            var item = new MyWrapper();
            item.Value = ((this._bar >> i) & 1) != 0;
            collection.Add(item);
        }
        this.Items = collection;
    }


    #region PROP Items

    private IEnumerable<MyWrapper> _items;

    public IEnumerable<MyWrapper> Items
    {
        get { return this._items; }
        set
        {
            if (this._items != value)
            {
                this._items = value;
                this.OnPropertyChanged("Items");

                if (this._items != null)
                {
                    foreach (var child in this._items)
                    {
                        child.PropertyChanged += child_PropertyChanged;
                    }
                }
            }
        }
    }

    #endregion

    void child_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        //rebuild the scalar value
        int value = 0;
        foreach (var item in this._items)
        {
            value = value >> 1;
            if (item.Value) value |= 0x80;
        }
        this.Bar = (byte)value;
    }


    #region EVT PropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;


    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = this.PropertyChanged;

        if (handler != null)
        {
            handler(
                this,
                new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion

}

【讨论】:

  • 对我来说,位操作(我编写了一个电子技术应用程序)和 WPF 并不是排他性的,但正如您稍后所说的那样,这不是重点。是的,当 bool 值更改时,我的 bool 包装器会触发 PropertyChanged 事件,但这不会触发 ConvertBack,因为集合本身(这也是可观察的)不会改变 - 就像 helb 在他的帖子中提到的那样。
  • 我也在开发用于与嵌入式设备交换数据的软件,但框架和 C# 倾向于抽象内部表示。但是,集合的可观察性并不重要,除非您必须修改位数。看看这里:cetdevelop.codeplex.com
  • 不需要转换器。我的怀疑是关于你的 LedCheckBox 风格:你能告诉我们吗?
  • 我的观点是在切换复选框时无法获得 PropertyChanged 事件。您的示例与我的场景的不同之处在于 Bar 不是任何类型的集合,而是一个 ubyte。因此,我需要一个转换器,从这个单个值中生成一个集合,它工作正常,但反过来,当该集合中的一个项目发生更改时,从该集合中生成一个值,不起作用。我将更改我的模型以提供bool 的集合,而不是单个ubyte 值。感谢您的帮助。
  • 再一次,什么是“ubyte”???有“字节”和“sbyte”:根本没有“ubyte”!
【解决方案2】:

您应该创建一个值转换器来将单个 ubyte 值转换为选中/取消选中复选框,因为如果集合中项目的值发生更改,则不会为集合调用 Converter 的 ConvertBack 方法。

您的 XAML 应如下所示:

<ItemsControl ItemsSource="{Binding Bar">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <CheckBox Style="{StaticResource LedCheckBox}" 
                      IsChecked="{Binding Value, 
                                          Mode=TwoWay,
                                          Converter={StaticResource ValueToLedConverter},
                                          UpdateSourceTrigger=PropertyChanged}"
                      ToolTip="{Binding Name}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

【讨论】:

  • 我会将模型更改为 bool 集合并将其绑定到我的复选框。这样我就完全不需要转换器了。谢谢。
猜你喜欢
  • 1970-01-01
  • 2010-12-26
  • 1970-01-01
  • 2013-11-15
  • 1970-01-01
  • 1970-01-01
  • 2013-04-10
  • 2012-09-20
  • 1970-01-01
相关资源
最近更新 更多