【问题标题】:Silverlight TabControl bound to ObservableCollection<string> not updating when collection changed绑定到 ObservableCollection<string> 的 Silverlight TabControl 在集合更改时不更新
【发布时间】:2010-11-20 15:02:54
【问题描述】:

具有 TabControl 的 Silverlight 3 应用程序使用 IValueConverter 绑定到 ObservableCollection。在应用程序启动时初始化绑定工作(调用转换器)。对绑定集合的更改、Clear() 或 Add() 不会反映在 TabControl... 转换器未调用。

注意:绑定的 ListBox 反映了对绑定集合的更改,而 TabControl 没有。

想法?

/jhd


XAML 绑定...

<UserControl.Resources>
    <local:ViewModel x:Key="TheViewModel"/>
    <local:TabConverter x:Key="TabConverter" />
</UserControl.Resources>
<StackPanel DataContext="{StaticResource TheViewModel}">
    <ListBox ItemsSource="{Binding Classnames}" />
    <controls:TabControl x:Name="TheTabControl" 
        ItemsSource="{Binding Classnames, Converter={StaticResource TabConverter}, ConverterParameter=SomeParameter}"/>
    <Button Click="Button_Click" Content="Change ObservableCollection" />
</StackPanel>

ViewModel...

namespace DatabindingSpike
{
    public class ViewModel
    {
        private ObservableCollection<string> _classnames = new ObservableCollection<string>();

        public ViewModel()
        {
            _classnames.Add("default 1 of 2");
            _classnames.Add("default 2 of 2");
        }

        public ObservableCollection<string> Classnames
        {
            get { return _classnames; }
            set { _classnames = value; }
        }
    }
}

转换器(为了完整性)...

namespace DatabindingSpike
{
    public class TabConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var source = value as ObservableCollection<string>;
            if (source == null)
                return null;

            var param = parameter as string;
            if (string.IsNullOrEmpty(param) || param != "SomeParameter")
                throw new NotImplementedException("Null or unknow parameter pasased to the tab converter");

            var tabItems = new List<TabItem>();
            foreach (string classname in source)
            {
                var tabItem = new TabItem
                                  {
                                      Header = classname,
                                      Content = new Button {Content = classname}
                                  };
                tabItems.Add(tabItem);
            }

            return tabItems;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

【问题讨论】:

  • 默认模式是一次性的吗?将尝试明确设置模式。 /jhd
  • 试过显式模式=OneWay,没有乐趣。我将使用 CollectionChanged 事件并重新设置 TabControl.ItemsSource,直到找到更好的方法。 /jhd
  • 我创建了扩展选项卡控件,它可以与 ObservableCollection 类一起正常工作。 vortexwolf.wordpress.com/2011/04/09/…

标签: silverlight data-binding tabcontrol observablecollection


【解决方案1】:

8/19 更新

简洁的答案是您必须在视图模型上实现 INotifyPropertyChanged 并在属性/集合更改时通知侦听器。

在 ViewModel 上实现 INotifyPropertyChanged

* implement the interface INotifyPropertyChanged
* define the event (public event PropertyChangedEventHandler PropertyChanged)
* subscribe to the CollectionChanged event (Classnames.CollectionChanged += ...)
* fire the event for listeners

最好的,

/jhd


ViewModel 根据上述更新... ValueConverter 现在调用属性/集合的所有更改

public class ViewModel : INotifyPropertyChanged
{
    private readonly ObservableCollection<string> _classnames = new ObservableCollection<string>();

    public ViewModel()
    {
        Classnames.CollectionChanged += Classnames_CollectionChanged;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void Classnames_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        NotifyPropertyChanged("Classnames");
    }

    private void NotifyPropertyChanged(string info)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            foreach (PropertyChangedEventHandler d in handler.GetInvocationList())
            {
                    d(this, new PropertyChangedEventArgs(info));
            }
        }
    }

    public ObservableCollection<string> Classnames
    {
        get { return _classnames; }
    }
}

XAML 绑定...

<UserControl.Resources>
    <local:ViewModel x:Key="TheViewModel"/>
    <local:TabConverter x:Key="TabConverter" />
</UserControl.Resources>

<StackPanel DataContext="{StaticResource TheViewModel}">
    <ListBox ItemsSource="{Binding Classnames}" />
    <controls:TabControl x:Name="TheTabControl" 
        ItemsSource="{Binding Classnames, Converter={StaticResource TabConverter}, ConverterParameter={StaticResource TheViewModel}}"/>
    <Button Click="Button_Click" Content="Change Classnames" />
</StackPanel>

ValueConverter(基本不变

    public class TabConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var source = value as ObservableCollection<string>;
            if (source == null)
                return null;

            //also sorted out the binding syntax to pass the ViewModel as a parameter
            var viewModel = parameter as ViewModel;
            if (viewModel == null)
                throw new ArgumentException("ConverterParameter must be ViewModel (e.g. ConverterParameter={StaticResource TheViewModel}");

            var tabItems = new List<TabItem>();
            foreach (string classname in source)
            {
                // real code dynamically loads controls by name
                var tabItem = new TabItem
                                  {
                                      Header = "Tab " + classname,
                                      Content = new Button {Content = "Content " + classname}
                                  };
                tabItems.Add(tabItem);
            }

            return tabItems;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

【讨论】:

  • 注意:您不想在实际代码中的每个 CollectionChanged 事件上发送 PropertyChanged 事件...您应该在完成对源集合的操作后引发一个 PropertyChanged 事件。 /jhd
【解决方案2】:

我意识到这是一个有点老的问题,但我不知道有人解释了为什么需要对视图模型的绑定属性执行 INotifyPropertyChanged。

ItemsControl 本身需要绑定到 ObservableCollection 以使集合更改事件导致 ItemsControl 重新评估。您的转换器在每次调用时都会返回一个不同的 List(或 Observable)集合,而不是保留一个 ObservableCollection 并向其中添加项目。因此,这些集合永远不会引发任何集合更改事件...它们始终是新的,每次重新完成绑定时。

提高 PropertyChanged 会强制重新评估绑定并重新运行您的转换器,返回一个不同的集合并反映您的更改。

我觉得更好的方法可能是在您的 ViewModel 中而不是在转换器中进行转换。公开您直接绑定到并就地修改的 TabItem 的 ObservableCollection。然后 TabControl 应该看到直接对您的集合所做的更改,而无需引发 PropertyChanged 并重新评估整个绑定。

[编辑 - 添加我的方法] 视图模型: 公共类 TabSampleViewModel { 私有 ObservableCollection _tabItems = new ObservableCollection();

    public TabSampleViewModel()
    {
        AddTabItem("Alpba");
        AddTabItem("Beta");
    }

    public ObservableCollection<TabItem> TabItems
    {
        get
        {
            return _tabItems;
        }
    }

    public void AddTabItem( string newTabItemName )
    {
        TabItem newTabItem = new TabItem();

        newTabItem.Header = newTabItemName;
        newTabItem.Content = newTabItemName;

        TabItems.Add( newTabItem );
    }
}

查看:

【讨论】:

    【解决方案3】:

    曝光

    public ObservableCollection<TabItem> Classnames
    {
        get { return _classnames; }
        set { _classnames = value; }
    }
    

    如果您调试 valueconverter,您会发现它没有像您想象的那样经常被调用。

    【讨论】:

    • 你是说当绑定源发生变化时不应该调用 ValueConverter 吗?这可能是正确的,但对我来说是违反直觉的。鉴于 ViewModel 当前位于 Data 项目/命名空间中,我不希望引用 S.W.C 来获取 TabItem,但它是比我现在更好的解决方法。谢谢。 /jhd
    • 嗨 Graeme,ValueConverter 没有被调用(在初始绑定之后),因为我没有在 ViewModel 上实现 INotifyPropertyChanged。请参阅下面的答案。
    【解决方案4】:

    问题可能是您的 ValueConverter 返回 List&lt;TabItem&gt; 而不是 ObservableCollection&lt;TabItem&gt;。尝试更改一行,看看是否有帮助。

    【讨论】:

    • 很好,可惜改变它没有[明显的]效果。数据绑定不是订阅了源,在案例中是 Classnames 吗?
    猜你喜欢
    • 2011-04-07
    • 2016-04-22
    • 1970-01-01
    • 2017-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-10
    • 2012-02-29
    相关资源
    最近更新 更多