5

IValueConverterを使用してObservableCollectionにバインドされたTabControlを備えたSilverlight3アプリ。最初に、アプリの起動時にバインディングが機能します(コンバーターが呼び出されます)。バインドされたコレクションへの変更、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();
        }
    }
}
4

4 に答える 4

3

この時点でこれは少し古い質問だと思いますが、ビューモデルのバインドされたプロパティで INotifyPropertyChanged を実行する必要がある理由を誰かが説明したかどうかはわかりません。

コレクション変更イベントで ItemsControl を再評価するには、ItemsControl 自体を ObservableCollection にバインドする必要があります。コンバーターは、単一の ObservableCollection を保持してアイテムを追加するのではなく、呼び出されるたびに個別の List (または Observable) コレクションを返しています。したがって、これらのコレクションでは、コレクション変更イベントが発生することはありません。バインディングが再実行されるたびに、コレクションは常に新しいものになります。

PropertyChanged を発生させると、バインドが強制的に再評価され、コンバーターが再実行され、個別のコレクションが返され、変更が反映されます。

Converter ではなく 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 に答える
3

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();
        }
    }
于 2009-08-18T22:45:53.613 に答える
0

問題は、ValueConverter がList<TabItem>ではなく を返すことである可能性がありますObservableCollection<TabItem>。その1行の変更を試して、それが役立つかどうかを確認してください.

于 2009-08-18T19:38:01.717 に答える
0

公開

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

valueconverter をデバッグすると、思ったほど頻繁に呼び出されていないことがわかります。

于 2009-08-18T18:32:14.140 に答える