12

わかりましたので、この質問は Windows Phone 7/Silverlight (更新された WP7 ツール、2010 年 9 月) に関連しており、具体的には基になるObservableCollection<T>.

WP7 テンプレートのピボット コントロール アプリケーションをいじっていると、 の基になる項目を変更してもObservableCollection<T>、画面上の ListBox が更新されないという問題に遭遇しました。基本的に、サンプル アプリには 2 つのピボットがあり、1 つ目は基になるObservableCollection<T>に直接バインドされ、2 つ目は にバインドされますCollectionViewSource(つまり、基になる のフィルター処理されたビューを表しますObservableCollection<T>)。

次のように、 ObservableCollection<T>implementに追加されている基になるアイテム。INotifyPropertyChanged

public class ItemViewModel : INotifyPropertyChanged
{       
    public string LineOne
    {
        get { return _lineOne; }
        set
        {
            if (value != _lineOne)
            {
                _lineOne = value;
                NotifyPropertyChanged("LineOne");
            }
        }
    } private string _lineOne;

    public string LineTwo
    {
        get { return _lineTwo; }
        set
        {
            if (value != _lineTwo)
            {
                _lineTwo = value;
                NotifyPropertyChanged("LineTwo");
            }
        }
    } private string _lineTwo;

    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            if (value != _isSelected)
            {
                _isSelected = value;
                NotifyPropertyChanged("IsSelected");
            }
        }
    } private bool _isSelected = false;

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

次に、メイン クラスで、データ コレクションが作成されます (簡潔にするためにリストを省略します。また、他の項目とは異なり、LoadData() エントリの 3 つが IsSelected == true であることに注意してください)。

 public class MainViewModel : INotifyPropertyChanged
 {
  public MainViewModel()
  {
   this.Items = new ObservableCollection<ItemViewModel>();
  }

  public ObservableCollection<ItemViewModel> Items { get; private set; }

  public bool IsDataLoaded
  {
   get;
   private set;
  }

   public void LoadData()
  {
   this.Items.Add(new ItemViewModel() { LineOne = "runtime one", IsSelected = true, LineTwo = "Maecenas praesent accumsan bibendum" });
   this.Items.Add(new ItemViewModel() { LineOne = "runtime two", LineTwo = "Dictumst eleifend facilisi faucibus" });
   this.Items.Add(new ItemViewModel() { LineOne = "runtime three", IsSelected = true, LineTwo = "Habitant inceptos interdum lobortis" });
   this.Items.Add(new ItemViewModel() { LineOne = "runtime four", LineTwo = "Nascetur pharetra placerat pulvinar" });
   this.Items.Add(new ItemViewModel() { LineOne = "runtime five", IsSelected = true, LineTwo = "Maecenas praesent accumsan bibendum" });
   this.Items.Add(new ItemViewModel() { LineOne = "runtime six", LineTwo = "Dictumst eleifend facilisi faucibus" });
   this.IsDataLoaded = true;
  }

  public event PropertyChangedEventHandler PropertyChanged;
  public void NotifyPropertyChanged(String propertyName)
  {
   if (null != PropertyChanged)
   {
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
   }
  }
 }

MainPage.xaml ファイルでは、最初のピボットがリストItemSourceに直接基づいていObservableCollection<T>ます。2 番目のピボット内では、画面上の ListBox のPropertyItemSourceが に設定されておりCollectionViewSource、その基になるソースは上記で設定された に基づいています。ObservableCollection<T>LoadData()

<phone:PhoneApplicationPage.Resources>
    <CollectionViewSource x:Key="IsSelectedCollectionView" Filter="CollectionViewSource_SelectedListFilter">
    </CollectionViewSource>
</phone:PhoneApplicationPage.Resources>

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <!--Pivot Control-->
    <controls:Pivot Title="MY APPLICATION">
        <!--Pivot item one-->
        <controls:PivotItem Header="first">
            <!--Double line list with text wrapping-->
            <ListBox x:Name="FirstListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                      <StackPanel Margin="0,0,0,17" Width="432">
                          <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                          <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                      </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </controls:PivotItem>

        <!--Pivot item two-->
        <controls:PivotItem Header="second"> 
            <!--Triple line list no text wrapping-->
            <ListBox x:Name="SecondListBox" Margin="0,0,-12,0" ItemsSource="{Binding  Source={StaticResource IsSelectedCollectionView}}">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Margin="0,0,0,17">
                                <TextBlock Text="{Binding LineOne}" TextWrapping="NoWrap" Margin="12,0,0,0" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                                <TextBlock Text="{Binding LineThree}" TextWrapping="NoWrap" Margin="12,-6,0,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                            </StackPanel>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
        </controls:PivotItem>
    </controls:Pivot>
</Grid>

<!--Sample code showing usage of ApplicationBar-->
<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
        <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1" Click="ApplicationBarIconButton_Click"/>
        <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
        <shell:ApplicationBar.MenuItems>
            <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
            <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
        </shell:ApplicationBar.MenuItems>
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

MainPage.xaml.cs では、上記のセクションのFilter属性にフィルター ハンドラーが割り当てられていることに注意してください。フィルター ハンドラーは、 trueに設定された項目をふるいにかけます。CollectionViewSourceResourcesIsSelected

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
        DataContext = App.ViewModel;
        this.Loaded += new RoutedEventHandler(MainPage_Loaded);
    }

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        if (!App.ViewModel.IsDataLoaded)
        {
            App.ViewModel.LoadData();
            CollectionViewSource isSelectedListView = this.Resources["IsSelectedCollectionView"] as CollectionViewSource;
            if (isSelectedListView != null)
            {
                isSelectedListView .Source = App.ViewModel.Items;
            }
        }
    }

    private void CollectionViewSource_SelectedListFilter(object sender, System.Windows.Data.FilterEventArgs e)
    {
        e.Accepted = ((ItemViewModel)e.Item).IsSelected;
    }

    private void ApplicationBarIconButton_Click(object sender, EventArgs e)
    {
        ItemViewModel item = App.ViewModel.Items[App.ViewModel.Items.Count - 1];
        item.IsSelected = !item.IsSelected;
    }
}

また、データをロードした直後に を取得し、CollectionViewSourceそのデータ ソースをObservableCollection<T>リストとして設定することにも注意してください。これは、フィルタリングを実行できるベース データがあるためです。

アプリケーションが読み込まれると、期待どおりにデータが表示され、true のアイテムがObservableCollection<T>2IsSelected番目のピボットに表示されます。

代替テキスト 代替テキスト

アプリケーション バー アイコンのコメントを外したことに気付くでしょう。最初のアイコンは、クリックIsSelectedされたときに最後の項目のプロパティを切り替えObservableCollection<T>ます (MainPage.xaml.cs の最後の関数を参照)。

これが私の質問の要点です。該当するバー アイコンをクリックすると、リストの最後の項目のIsSelectedプロパティがいつ true に設定されているかがわかりますが、2 番目のピボットにはこの変更された項目が表示されません。ハンドラーがアイテムに対して起動されていることがわかりますがNotifyPropertyChanged()、コレクションはこの事実を認識していません。そのため、ピボット 2 のリスト ボックスは、コレクションに新しいアイテムが追加されるはずであるという事実を反映して変化しません。

ここで非常に基本的な/基本的なものが欠けていると確信していますが、それができない場合、コレクションを取得する最良の方法を知っている人はいますか?

この問題は、フィルタリングだけでなくソートにも当てはまると思います ((aCollectionViewSourceがソートに基づいている場合、ソートで使用されるアイテムのプロパティが変更された場合、コレクションのソート順はこれを次のように反映する必要があるという意味で)良い))

4

2 に答える 2

4

私はこの問題を処理しなければなりませんでした。'Refresh()' ソリューションはうまく機能しますが、1 つの項目プロパティ変更イベントだけでリスト全体が更新されるため、実行にかなり時間がかかります。あまりよくない。そして、リアルタイム データが 1 秒ごとにコレクションに入るシナリオで、このアプローチを使用した場合のユーザー エクスペリエンスの結果を想像してもらいます :)

コレクションビューにラップされたコレクションにアイテムを追加すると、アイテムはフィルター述語によって評価され、この結果に基づいて、ビューに表示されるかどうかが決まります。

そのため、refresh() を呼び出す代わりに、プロパティが更新されたオブジェクトの挿入をシミュレートすることにしました。オブジェクトの挿入をシミュレートすることにより、リスト全体を更新する必要なく、フィルター述語によって自動的に評価されます。

これを行うためのコードは次のとおりです。

派生した監視可能なコレクション:

namespace dotnetexplorer.blog.com
{
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

/// <summary>
/// Derived class used to be able to manage filter application when a collection item property changed
///   whithout having to do a refresh
/// </summary>
internal sealed class CustomObservableCollection : ObservableCollection<object>
{
    /// <summary>
    ///   Initializes a new instance of the <see cref = "CustomObservableCollection " /> class.
    /// </summary>
    public CustomObservableCollection ()
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="CustomObservableCollection "/> class.
    /// </summary>
    /// <param name="source">
    /// The source.
    /// </param>
    public CustomObservableCollection (IEnumerable<object> source)
        : base(source)
    {
    }

    /// <summary>
    /// Custom Raise collection changed
    /// </summary>
    /// <param name="e">
    /// The notification action
    /// </param>
    public void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        OnCollectionChanged(e);
    }
}
}

そして、代替ソースが CustomObservableCollection である項目プロパティ変更イベントを受け取るときに使用するコードがあります。

        private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {

                // To avoid doing a refresh on a property change which would end in a very hawful user experience
                // we simulate a replace to the collection because the filter is automatically applied in this case
                int index = _substituteSource.IndexOf(sender);

                var argsReplace = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
                                                                       new List<object> { sender },
                                                                       new List<object> { sender }, index);
                _substituteSource.RaiseCollectionChanged(argsReplace);
            }

        }
    }

これが役立つことを願っています!

于 2011-04-12T11:48:17.040 に答える
3

質問を投稿してから5分も経っていないのに、それが起こったとき、あなたはそれが嫌いではありません.問題が何であるかを理解しました-そしてそれ非常に基本的なものでした. CollectionViewSourceオブジェクトにはView、機能を持つプロパティがありRefresh()ます。変更に含まれる基本アイテムのプロパティの後にこの関数を呼び出すと、ObservableCollection<T>それが行われたようです。

基本的に、私がしなければならなかったのは、CollectionViewSourceオブジェクトをメンバー変数に変更し、呼び出されたときにそれを保存することだけでしたLoadData():

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    if (!App.ViewModel.IsDataLoaded)
    {
        App.ViewModel.LoadData();
        m_isSelectedListView = this.Resources["IsSelectedCollectionView"] as CollectionViewSource;
        if (m_isSelectedListView != null)
        {
            m_isSelectedListView.Source = App.ViewModel.Items;
        }
    }
}

Refresh()次に、基になる変更のいずれかの項目の後で、ビューを呼び出しObservableCollection<T>ます。したがって、MainPage.xaml.cs で、最後の項目を変更した直後に、refresh の呼び出しを追加します。

private void ApplicationBarIconButton_Click(object sender, EventArgs e)
{
    ItemViewModel item = App.ViewModel.Items[App.ViewModel.Items.Count - 1];
    item.IsSelected = !item.IsSelected;
    m_isSelectedListView.View.Refresh();
}

...そして、2 番目のピボットの ListBox が即座に更新されます。このような短いコード行で、世界が大きく変わります!

その質問を書き上げるのにかかった時間の中で、私ができたことが 100 あります :-( ああ、遅くなったほうがいいと思います - 他の誰かが引き裂かれるのを防ぐためだけに、ここに答えを投稿することを考えました。私がしたように彼らの髪。

于 2010-09-22T16:55:38.310 に答える