2016-10-26 6 views
0

この質問の多くは読んでいますが、「INotifyPropertyChangedが見つかりませんでした。 私はObservableObjectなど、ViewModelBaseの実施のためMVVM光を使用ItemsControlはObservableCollectionがCollectionChangedを起動したときに自身を更新しません

ビュー:

<Window x:Class="BaseFlyingFigure.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:local="clr-namespace:BaseFlyingFigure" 
    xmlns:helpers="clr-namespace:BaseFlyingFigure.Helpers" 
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
    xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform" 
    xmlns:system="clr-namespace:System;assembly=mscorlib" 
    mc:Ignorable="d" 
    Title="MainWindow" Height="450" Width="525" 
    DataContext="{Binding MainViewModel, Source={StaticResource Locator}}"> 
<i:Interaction.Triggers> 
    <i:EventTrigger EventName="Loaded"> 
     <cmd:EventToCommand Command="{Binding LoadedCommand}" /> 
    </i:EventTrigger> 
    <i:EventTrigger EventName="PreviewKeyDown"> 
     <cmd:EventToCommand Command="{Binding PreviewKeyDownCommand}" 
          PassEventArgsToCommand="True" /> 
    </i:EventTrigger> 
</i:Interaction.Triggers> 
<Grid> 
    <Grid.RowDefinitions> 
     <RowDefinition Height="Auto" /> 
     <RowDefinition Height="*" /> 
    </Grid.RowDefinitions> 
    <Menu Grid.Row="0"> 
     <MenuItem Header="File"> 
      <MenuItem Header="Exit" Command="{Binding AppExitCommand}" /> 
     </MenuItem> 
    </Menu> 
    <ItemsControl Grid.Row="1" helpers:SizeObserver.Observe="True" 
        helpers:SizeObserver.ObservedWidth="{Binding CanvasWidth, Mode=OneWayToSource}" 
        helpers:SizeObserver.ObservedHeight="{Binding CanvasHeight, Mode=OneWayToSource}" 
        ItemsSource="{Binding Elements, Converter={helpers:ElementToShapeConverter}, 
     Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" 
        > 
     <ItemsControl.ItemsPanel> 
      <ItemsPanelTemplate> 
       <Canvas ClipToBounds="True" /> 
      </ItemsPanelTemplate> 
     </ItemsControl.ItemsPanel> 
    </ItemsControl> 
</Grid> 

のViewModel:

using System.Collections.ObjectModel; 
using System.Diagnostics; 
using System.Windows; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Shapes; 
using BaseFlyingFigure.Services.Interfaces; 
using GalaSoft.MvvmLight; 
using GalaSoft.MvvmLight.Command; 

namespace BaseFlyingFigure.ViewModels 
{ 
    public class MainViewModel : ViewModelBase 
    { 
     private readonly IFigureRepository _repository; 
     private ObservableCollection<Element> _elements = new ObservableCollection<Element>(); 


     public MainViewModel(IFigureRepository repository) 
     { 
      _repository = repository; 

      AppExitCommand = new RelayCommand(Exit); 
      LoadedCommand = new RelayCommand(WindowLoaded); 

      PreviewKeyDownCommand = new RelayCommand<KeyEventArgs>(PreviewKeyDown); 

      Elements.Add(new Element(new Ellipse { 
        Fill = Brushes.HotPink, 
        Width = 100, 
        Height = 100 
       }) 
       {Left = 250, Top = 250}); 
     } 

     public RelayCommand AppExitCommand { get; private set; } 
     public RelayCommand LoadedCommand { get; private set; } 
     public RelayCommand<KeyEventArgs> PreviewKeyDownCommand { get; private set; } 

     public double CanvasWidth { get; set; } 
     public double CanvasHeight { get; set; } 

     public ObservableCollection<Element> Elements 
     { 
      get { return _elements; } 
      set 
      { 
       if (value != _elements) 
        Set(ref _elements, value); 
      } 
     } 

     private void PreviewKeyDown(KeyEventArgs e) 
     { 
      switch (e.Key) { 
       case Key.OemPlus: 
        Elements.Add(new Element(new Ellipse 
        { 
          Fill = Brushes.HotPink, 
          Width = 100, 
          Height = 100 
         }) 
         {Left = 250, Top = 250}); 
        Debug.WriteLine("+"); 
        break; 
      } 
     } 

     private void WindowLoaded() 
     { 
      Elements.CollectionChanged += (sender, args) => Debug.WriteLine("changed"); 
     } 

     private void Exit() => Application.Current.Shutdown(); 
    } 
} 

コンバータ:

using System; 
using System.Collections.Generic; 
using System.Globalization; 
using System.Linq; 
using System.Windows.Data; 
using System.Windows.Markup; 
using BaseFlyingFigure.ViewModels; 

namespace BaseFlyingFigure.Helpers 
{ 
    public class ElementToShapeConverter : MarkupExtension, IValueConverter 
    { 
     private static ElementToShapeConverter _converter; 

     public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
     { 
      var list = (value as ICollection<Element>)?.Select(el => el.Shape).ToList(); 
      return list; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
     { 
      return null; 
     } 

     public override object ProvideValue(IServiceProvider serviceProvider) 
     { 
      return _converter ?? (_converter = new ElementToShapeConverter()); 
     } 
    } 
} 

要素:我々は+を押すと

using System.Windows.Controls; 
using System.Windows.Shapes; 
using GalaSoft.MvvmLight; 

namespace BaseFlyingFigure.ViewModels 
{ 
    public class Element : ObservableObject 
    { 
     private double _left; 
     private Shape _shape; 
     private double _top; 

     public Element(Shape shape) 
     { 
      Shape = shape; 
     } 

     public double Left 
     { 
      get { return _left; } 
      set 
      { 
       Set(ref _left, value); 
       Canvas.SetLeft(Shape, value); 
      } 
     } 

     public double Top 
     { 
      get { return _top; } 
      set 
      { 
       Set(ref _top, value); 
       Canvas.SetTop(Shape, value); 
      } 
     } 

     public Shape Shape 
     { 
      get { return _shape; } 
      set { Set(ref _shape, value); } 
     } 
    } 
} 

は火をて、CollectionChanged。しかし、キャンバスはコンストラクタで作成され追加されたシェイプを表示します。 ViewModelLocator:

public class ViewModelLocator 
{ 
    public ViewModelLocator() 
    { 
     ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); 
     SimpleIoc.Default.Register<MainViewModel>(); 
     SimpleIoc.Default.Register<IFigureRepository, FigureRepository>(); 
    } 

    public MainViewModel MainViewModel => ServiceLocator.Current.GetInstance<MainViewModel>(); 
} 

それと間違っているのですか? XAMLやその他の何か間違い?

+1

<ItemsControl ItemsSource="{Binding Elements}"> ... <ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Canvas.Left" Value="{Binding X}"/> <Setter Property="Canvas.Top" Value="{Binding Y}"/> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl> 
:要素クラス

public class Element { ... public double X { get; set; } public double Y { get; set; } } 

に2つのプロパティを追加し、これらのプロパティを使用することのItemsControlにItemsContainerStyleを追加ItemsSourceバインディングは無意味です。 OneWayはデフォルトのモードで、UpdateSourceTriggerはTwoWay(およびOneWayToSource)バインディングにのみ影響します。 – Clemens

+1

それ以外に、ItemsSourceバインディングにコンバーターを置くべきではありません。代わりに、ItemControlのItemContainerStyleおよび/またはItemTemplateプロパティを、シェイプ要素を視覚化するStye/DataTemplateに設定します。 – Clemens

+2

'ElementToShapeConverter'は通常の動作を壊します。それは 'ObservableCollection'(UIに通知することができます)を' List'(これはできません)に置き換えます。 – ASh

答えて

1

アプローチの基本的な問題は、ビューモデルでShapeオブジェクトを使用することです。

(のUIElementは、単一の親を持つことができるので)、それは、このビューモデルを視覚化し、複数のビューを持つことができなくなります、それはまたList<Shape>ObservableCollection<Element>を変換するために、型にはまらない、欠陥のアプローチを使用するためにあなたを強制することを加えてItemsSourceバインディングのコンバーターで。コメントとその他の回答に既に記載されているように、List<Shape>はコンバーターから返され、ObservableCollection<Element>の変更についてビューに通知しません。

適切なMVVMアプローチでは、UI要素を持たないシェイプの表現を使用します。このように:

public class Element 
{ 
    public Geometry Shape { get; set; } 
    public Brush Fill { get; set; } 
    public Brush Stroke { get; set; } 
    public double StrokeThickness { get; set; } 
} 

今、あなたのItemsControlにおける形状の可視化のための定期的なDataTemplateを宣言することができます:あなたのビューモデルにサンプル楕円を追加

<ItemsControl ItemsSource="{Binding Elements}"> 
    <ItemsControl.ItemsPanel> 
     <ItemsPanelTemplate> 
      <Canvas/> 
     </ItemsPanelTemplate> 
    </ItemsControl.ItemsPanel> 
    <ItemsControl.ItemTemplate> 
     <DataTemplate> 
      <Path Data="{Binding Shape}" 
        Fill="{Binding Fill}" 
        Stroke="{Binding Stroke}" 
        StrokeThickness="{Binding StrokeThickness}"/> 
     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
</ItemsControl> 

は今、次のようになります。

Elements.Add(new Element 
{ 
    Shape = new EllipseGeometry(new Point(250, 250), 50, 50), 
    Fill = Brushes.HotPink 
}); 

何らかの理由であなたはまた、各要素の追加オフセットのx/y位置を必要とする場合は、あなたがかもしれません上 `モード= OneWay`と` UpdateSourceTrigger = PropertyChanged`設定、メモとして

+0

X、Yプロパティを持つ要素がINotifyPropertyChangedを実装する必要がありますか?要素の変更プロパティがいつ表示されるかを知りたいですか? – DdarkSideE

+0

はい、これらのプロパティはすべて変更について通知する必要があります(変更が発生した場合のみ)。私はこれを簡潔にするために残しました。 – Clemens

0

これは、ElementToShapeConverter.Convertが新しいリスト<>を作成してそれを返すためだと思います。ビューモデルのコレクションへのバインディングが破られることはありませんが、ビューモデルのコレクションは変更によって無効になりません。

ViewModelには、コンバータの代わりに呼び出されたときにシェイプをElementsコレクションからフィルタリングするShapesという別のObservableCollectionプロパティが必要です。 ElementsコレクションのCollectionChangedイベントで無効にすることができます。

+0

実際にはBindingを壊すわけではありませんが、Listを返すので、ObservableCollectionソースに加えられた変更は無視されます。 – Clemens

+0

@クレメンス:そうですね。 –