【问题标题】:Items in ObservableCollection<string> do not update when bound to a WPF ListBoxObservableCollection<string> 中的项目在绑定到 WPF 列表框时不会更新
【发布时间】:2017-02-25 17:12:36
【问题描述】:

我想通过将strings 中的ObservableCollection 绑定到ListBoxItemsSource 属性并将项目模板设置为TextBox 来操作它。

我的问题是,当我在 ListBox 包含的 TextBox 项目中编辑 ObservableCollection 中的项目时,它们没有得到更新。我做错了什么?

最小工作示例的 XAML 是

    <Window x:Class="ListBoxUpdate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxUpdate" Height="300" Width="300"
>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid Grid.Column="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Content="Show items" Click="HandleButtonClick"/>
        <TextBlock Grid.Row="1" x:Name="textBlock" />
    </Grid>
    <ListBox
        Grid.Column="1"
        ItemsSource="{Binding Strings, Mode=TwoWay}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBox Text="{Binding ., Mode=TwoWay}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

而相应的代码隐藏是

    using System;
    using System.Collections.ObjectModel;
    using System.Windows;

    namespace ListBoxUpdate
    {
        public partial class Window1 : Window
        {
            public ObservableCollection<string> Strings { get; set; }

            public Window1()
            {
                InitializeComponent();

                Strings = new ObservableCollection<string>(new string[] { "one", "two", "three" });
                this.DataContext = this;
            }

            void HandleButtonClick(object sender, RoutedEventArgs e)
            {
                string text = "";

                for (int i = 0; i < Strings.Count; i++) {
                    text += Strings[i] + Environment.NewLine;
                }

                textBlock.Text = text;
            }
        }
    }

非常感谢您的建议。

【问题讨论】:

  • 字符串是不可变的,不能更改。如果您希望能够更改文本,您将需要一个带有字符串属性的包装类。然后你可以改变类的属性。
  • @BrandonKramer:非常感谢,你是对的。我用 StringWrapper 内部类替换了字符串,然后它就可以工作了。
  • 很高兴我能帮上忙。
  • @BrandonKramer:如果您发表评论作为答案,我应该很高兴接受它。我在这样的论坛上比较缺乏经验,但我认为根据别人的评论回答我自己的问题是不礼貌的。
  • @tethered:实际上,这与 string 类型是不可变的没有任何关系。只是因为 WPF 绑定不能改变被绑定的源对象;它只能更改该对象的属性。但是是的,答案仍然是一样的:将要更改的值放在源对象的属性中,而不是使其成为源对象。您应该随时自行回答...布兰登可能不想花时间写一个完整的答案,但您可以轻松发布您必须编写的代码来解决问题,因为您已经拥有它。

标签: c# wpf string data-binding listbox


【解决方案1】:

正如@BrandonKramer 和@Peter Duniho 的cmets 所示,解决方案是数据绑定不能更改它所绑定的对象本身,只能更改该对象的属性。

因此,我必须创建一个包装类,其属性将成为我要操作的字符串。现在是代码隐藏

using System;
using System.Collections.ObjectModel;
using System.Windows;

namespace ListBoxUpdate
{
    public partial class Window1 : Window
    {
        public ObservableCollection<StringWrapper> Strings { get; set; }

        public class StringWrapper 
        {
            public string Content { get; set; }

            public StringWrapper(string content)
            {
                this.Content = content;
            }

            public static implicit operator StringWrapper(string content)
            {
                return new Window1.StringWrapper(content);
            }
        }

        public Window1()
        {
            InitializeComponent();

            this.Strings = new ObservableCollection<StringWrapper>(new StringWrapper[] { "one", "two", "three" });
            this.DataContext = this;
        }

        void HandleButtonClick(object sender, RoutedEventArgs e)
        {
            string text = "";

            for (int i = 0; i < Strings.Count; i++) {
                text += Strings[i].Content + Environment.NewLine;
            }

            textBlock.Text = text;
        }
    }
}

XAML 只需要修改一次

<ListBox
    Grid.Column="1"
    ItemsSource="{Binding Strings, Mode=TwoWay}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Content, Mode=TwoWay}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

谢谢@BrandonKramer 和@Peter Duniho。

更新:我对仅仅为了操作对象而定义一个包装器感到不舒服,在我看来,在列表中编辑对象本身而不是它的一个属性的问题似乎是普遍的,所以我试图找到另一种解决方案。我宁愿决定在ItemTemplate 中挂钩TextBoxLostFocus 事件。

在这种情况下,问题在于从模板化的TextBox 中找到刚刚失去焦点的ListBox 中的索引。我不能使用ListBoxSelectedIndex 属性,因为在触发LostFocus 时,它已经设置为不同的ListBoxItem

我搜索了很多,并在这里找到了 Dennis Troller 的答案:WPF ListBoxItems with DataTemplates - How do I reference the CLR Object bound to ListBoxItem from within the DataTemplate? 诀窍是让TextBoxDataContext 失去焦点并使用ListBoxItemContainerGenerator 来首先识别容器(带有ItemContainerGenerator.ContainerFromItem)然后获取列表中的索引(带有ItemContainerGenerator.IndexFromContainer)。

现在是代码隐藏

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;

namespace ListBoxUpdate
{

    public partial class Window1 : Window
    {
        public ObservableCollection<string> Strings { get; set; }

        public Window1()
        {

            InitializeComponent();

            this.Strings = new ObservableCollection<string>(new string[] { "one", "two", "three" });
            this.DataContext = this;
        }

        void HandleButtonClick(object sender, RoutedEventArgs e)
        {
            string text = "";

            for (int i = 0; i < Strings.Count; i++) {
                text += Strings[i] + Environment.NewLine;
            }

            textBlock.Text = text;
        }

        void HandleTextBoxLostFocus(object sender, RoutedEventArgs e)
        {
            // https://stackoverflow.com/questions/765984/wpf-listboxitems-with-datatemplates-how-do-i-reference-the-clr-object-bound-to?rq=1, @Dennis Troller's answer.
            int index;
            object item;
            DependencyObject container;
            TextBox textBox = sender as TextBox;

            if (textBox == null) return;

            item = textBox.DataContext;
            container = listBox.ItemContainerGenerator.ContainerFromItem(item);

            if (container != null) {
                index = listBox.ItemContainerGenerator.IndexFromContainer(container);
                if (textBox.Text != Strings[index]) {
                    Strings[index] = textBox.Text;
                }
            }
        }
    }
}

完整的 XAML 如下(我单向绑定到文本):

<Window x:Class="ListBoxUpdate.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ListBoxUpdate" Height="300" Width="300"
    >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid Grid.Column="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Button Grid.Row="0" Content="Show items" Click="HandleButtonClick"/>
            <TextBlock Grid.Row="1" x:Name="textBlock" />
        </Grid>
        <ListBox
            x:Name="listBox"
            Grid.Column="1"
            ItemsSource="{Binding Strings}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBox 
                        Text="{Binding ., Mode=OneWay}"
                    LostFocus="HandleTextBoxLostFocus"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

【讨论】:

    猜你喜欢
    • 2011-08-16
    • 1970-01-01
    • 2021-11-15
    • 1970-01-01
    • 1970-01-01
    • 2011-05-07
    • 1970-01-01
    • 2015-11-17
    • 2012-05-30
    相关资源
    最近更新 更多