5

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

视图模型...

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

4 回答 4

3

更新 8/19

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

在 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();
        }
    }
于 2009-08-18T22:45:53.613 回答
3

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

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

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

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

[编辑 - 添加我的方法] ViewModel: public class TabSampleViewModel { private 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 );
    }
}

查看:<controls:TabControl ItemsSource="{Binding TabItems}"/>

于 2010-02-25T15:44:10.870 回答
0

暴露

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

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

于 2009-08-18T18:32:14.140 回答
0

问题可能是您的 ValueConverter 返回 aList<TabItem>而不是ObservableCollection<TabItem>. 尝试更改一行,看看是否有帮助。

于 2009-08-18T19:38:01.717 回答