2016-11-10 28 views
0

WPFでデータバインドの簡単な方法を見つけようとしています。
私はINotifyPropertyChangedインターフェイスを使用しています。抽象基本クラスに実装され、バインドされたメンバーを持つオブジェクトに継承されていれば正常に動作します。WPF INotifyPropertyChanged(ベースクラスを作成せず)

public partial class MainWindow : Window 
{ 
    public static MainWindow Instance; 

    private readonly Vm _vm; 

    public MainWindow() 
    { 
     InitializeComponent(); 
     DataContext = _vm = new Vm 
     { 
      Button1 = new Vm.ObservableButton(button1, new List<string> { "Paused", "Logging" }, false), 
      Button2 = new Vm.ObservableToggleButton(button2, new List<string> { "Log All", "Log VBA" }, false), 
     }; 
    } 

    private class Vm 
    { 
     public abstract class ObservableObject : INotifyPropertyChanged 
     { 
      public event PropertyChangedEventHandler PropertyChanged; 

      protected virtual void OnPropertyChanged ([CallerMemberName] string propName = "") 
      { 
       var pc = PropertyChanged; 
       if (pc != null) 
        pc(this, new PropertyChangedEventArgs(propName)); 
      } 
     } 

     public class ObservableButton : ObservableObject 
     { 
      private readonly Button _b; 
      private readonly List<string> _options; 

      private string _content; 
      public string Content 
      { 
       get { return _content; } 
       set 
       { 
        if (_content == value) return; 
        _content = value; 
        OnPropertyChanged(); 
       } 
      } 

      public Boolean On { set; private get; } 

      public ObservableButton (Button b, List<string> options, Boolean on = true) 
      { 
       _b = b; 
       _options = options; 
       _b.Click += Click; 
       On = on; 
       Content = On ? _options[0] : _options[1]; 
      } 
      public void Click (object sender, RoutedEventArgs e) 
      { 
       On = !On; 
       Content = On ? _options[0] : _options[1]; 
      } 

     } 

     public class ObservableToggleButton : ObservableObject 
     { 
      private readonly ToggleButton _b; 
      private readonly List<string> _options; 

      private string _content; 
      public string Content 
      { 
       get { return _content; } 
       private set 
       { 
        if (_content == value) return; 
        _content = value; 
        OnPropertyChanged(); 
       } 
      } 

      private Boolean _on; 
      public Boolean On 
      { 
       private get { return _on; } 
       set 
       { 
        if (_on == value) return; 
        _on = value; 
        Content = value ? _options[0] : _options[1]; 
       } 
      } 

      public ObservableToggleButton (ToggleButton b, List<string> options, Boolean on = true) 
      { 
       _b = b; 
       _options = options; 
       On = on; 
       Content = _b.IsChecked ?? false ? _options[0] : _options[1]; 
      } 

      public void Push() 
      { 
       var peer = new ToggleButtonAutomationPeer(_b); 
       var toggleProvider = peer.GetPattern(PatternInterface.Toggle) as IToggleProvider; 
       if (toggleProvider != null) toggleProvider.Toggle(); 
       //On = !On; 
      } 
     } 

     public ObservableButton Button1 { get; set; } 

     public ObservableToggleButton Button2 { get; set; } 

     public Vm() 
     { 
     } 
    } 
} 

<Grid Margin="0,0,183,134"> 
    <Button x:Name="button1" Content="{Binding Button1.Content}" HorizontalAlignment="Left" Margin="112,134,0,0" VerticalAlignment="Top" Width="75"/> 
    <ToggleButton x:Name="button2" IsChecked="{Binding Button2.On, Mode=OneWayToSource}" Content="{Binding Button2.Content}" HorizontalAlignment="Left" Margin="206,134,0,0" VerticalAlignment="Top"/> 

</Grid> 

でも、私は、基本クラスを燃焼せずにこれをやってみたかったので、私はバックビューモデル上の単一のインタフェースを介して、ビューモデルにINotifyPropertyChangedを実装し、結合したメンバーから変更イベントをルーティングされました。バインディングオブジェクトにはソースと正しいプロパティ名への参照がありますが、これは暗黙のうちに失敗します。

バインディングオブジェクトで型チェックが行われるため、バインドされたプロパティで偽の実装が作成されたため、機能しないと考えました。ここではそのシナリオのためのコードは、あなたがあるため、彼らObservableButtonObservableToggleButtonタイプはまだ彼らの親によって変更通知をルーティングしている上のインタフェースは、バインディング・オブジェクトが幸せであっても、ことがわかります...

public partial class MainWindow : Window 
{ 
    public static MainWindow Instance; 

    public MainWindow() 
    { 
     InitializeComponent(); 
     DataContext = new ViewModel 
     { 
      Button1 = new ViewModel.ObservableButton(button1, new List<string> { "Paused", "Logging" }, false), 
      Button2 = new ViewModel.ObservableToggleButton(button2, new List<string> { "Log All", "Log VBA" }, false), 
     }; 
    } 

    public class ViewModel : INotifyPropertyChanged 
    { 
     private static ViewModel _instance; 

     public event PropertyChangedEventHandler PropertyChanged; 

     protected virtual void OnPropertyChanged<T> (T control, [CallerMemberName] string propName = "") 
     { 
      var pc = PropertyChanged; 
      if (pc != null) 
       pc(control, new PropertyChangedEventArgs(propName)); 
     } 

     public class ObservableButton : INotifyPropertyChanged 
     { 
      public event PropertyChangedEventHandler PropertyChanged; 
      protected virtual void OnPropertyChanged() {} 

      private readonly Button _b; 
      private readonly List<string> _options; 

      private string _content; 
      public string Content 
      { 
       get { return _content; } 
       set 
       { 
        if (_content == value) return; 
        _content = value; 
        _instance.OnPropertyChanged(this); 
       } 
      } 

      public Boolean On { set; private get; } 

      public ObservableButton (Button b, List<string> options, Boolean on = true) 
      { 
       _b = b; 
       _options = options; 
       _b.Click += Click; 
       On = on; 
       Content = On ? _options[0] : _options[1]; 
      } 
      public void Click (object sender, RoutedEventArgs e) 
      { 
       On = !On; 
       Content = On ? _options[0] : _options[1]; 
      } 

     } 

     public class ObservableToggleButton : INotifyPropertyChanged 
     { 
      public event PropertyChangedEventHandler PropertyChanged; 
      protected virtual void OnPropertyChanged() {} 

      private readonly ToggleButton _b; 
      private readonly List<string> _options; 

      private string _content; 
      public string Content 
      { 
       get { return _content; } 
       private set 
       { 
        if (_content == value) return; 
        _content = value; 
        _instance.OnPropertyChanged(this); 
       } 
      } 

      private Boolean _on; 
      public Boolean On 
      { 
       private get { return _on; } 
       set 
       { 
        if (_on == value) return; 
        _on = value; 
        Content = value ? _options[0] : _options[1]; 
       } 
      } 

      public ObservableToggleButton (ToggleButton b, List<string> options, Boolean on = true) 
      { 
       _b = b; 
       _options = options; 
       On = on; 
       Content = _b.IsChecked ?? false ? _options[0] : _options[1]; 
      } 

     } 

     public ObservableButton Button1 { get; set; } 

     public ObservableToggleButton Button2 { get; set; } 

     public ViewModel() 
     { 
      _instance = this; 
     } 
    } 
} 

    <Grid Margin="0,0,183,134"> 
     <Button x:Name="button1" Content="{Binding Button1.Content}" HorizontalAlignment="Left" Margin="112,134,0,0" VerticalAlignment="Top" Width="75"/> 
     <ToggleButton x:Name="button2" IsChecked="{Binding Button2.On, Mode=OneWayToSource}" Content="{Binding Button2.Content}" HorizontalAlignment="Left" Margin="206,134,0,0" VerticalAlignment="Top"/> 

    </Grid> 

ですつま先をタイプします。

子オブジェクトがそれを使わずにバインディングを完了する必要があるのに、インターフェイスを実装する必要がある理由はありますか?

+3

なぜ親から何かを正確にルーティングしたいのですか?この質問には直接関係はありませんが、コントロールインスタンス(この場合はButton)をビューモデルに渡しているため、ビュービューモデルパターン全体の目的が損なわれ、役に立たなくなります。上記のコードブロック全体と質問自体は、あなたがそのパターンを打ち破っていないことを示しています(または、設計通りに動作したくない)ことを示しています。たぶんそれは最初に修正する必要があります。 – Evk

+0

@Evkの場合、最初の例では親を通して何もルーティングされていませんが、シグナルは抽象基底クラスから継承したインタフェースを介してボタンとバインディングの間に直接あります。基底クラスはどこにでも定義することができますが、私はモデルのコンパクトさのためにView Modelでそれを持っています。 –

+0

ViewModelのUI要素を保持するだけで、MVVMパターンが完全に壊れてしまいます...私もxyに直面していると思います。http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – Mat

答えて

0

ViewModelには、最初の例であることがある問題:あなたはあなたのDataContextは、「VM」で、結合プロパティが持っていることを知らないだろうと言うたび

private class Vm 
{ 
    ... 
} 

したがって、INofityPropertyChangedインタフェースを実装していません変更したため、それはINotifyPropertyChangedのを実装していないビューモデル...

そして、あなたは、ビューモデルクラスにINofityPropertyChangedを実装しているため、2番目の例には、それが働いている

public class ViewModel : INotifyPropertyChanged 
{ 
    ... 
} 

基本クラスがそれを実装しておらず、基本クラスが子の変更を監視していて、その変更を "独自"として発生する場合、子クラスがINotifyPropertyChangedを実装するかどうかは関係ありません。

+0

最初の例は正常に動作します。バインディング内のパスは、変更するプロパティに接続します。 –

+0

@ A.N。これは間違っています。サブクラスにインターフェイスを実装するだけで済みます。しかし、 'Vm'の修飾子が' private'なので、これはまったく動作しません。 @マット、本当ですか? – Mat

+0

私のコードはプライベートとしてVMに問題はありません。私はまだ険しい学習曲線に苦労していますが、VMインスタンスを 'DataContext'オブジェクトに割り当てると、ViewがVM名前空間の下に来るように思えます。 –

1

私は、OP質問を修正しようとするのではなく、WPFでこれをどのように行うべきか明確な例を提供しようとしています。

XAML

<StackPanel> 
    <StackPanel.Resources> 
     <BooleanToVisibilityConverter x:Key="bToV" /> 
    </StackPanel.Resources> 
    <!--bind the text to the viewmodel content. Use a bool to visibilty converter to convert from true to Visible--> 
    <TextBlock 
       Text="{Binding Path=Content}" 
       Visibility="{Binding Path=IsContentVisible, Converter={StaticResource bToV}}" /> 
    <!--Use a two way binding to sync the IsChecked property with the viewmodel--> 
    <ToggleButton IsChecked="{Binding Path=IsContentVisible,Mode=TwoWay}" 
        Content="{Binding Path=ToogleActionName}" /> 
</StackPanel> 

コード

の背後には、私は暖かく、別のファイルに各クラスを置くことをお勧め明確なプロジェクトの構造を維持します。しかし、簡単に投稿できるように3つのクラスを1つのファイルにまとめました。

using System.ComponentModel; 
using System.Runtime.CompilerServices; 
using System.Windows; 

namespace WpfApplication4 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow : Window 
    { 

     public MainWindow() 
     { 
      InitializeComponent(); 

      DataContext = new ContentViewModel() { Content = "foo" }; 
     } 
    } 

    public class ContentViewModel : ViewModelBase 
    { 
     private string _toogleActionName = "turn it off"; 
     private bool _isContentVisible = true; 
     private string _content; 
     public bool IsContentVisible 
     { 
      get 
      { 
       return _isContentVisible; 
      } 
      set 
      { 
       _isContentVisible = value; 

       //switch action name 
       if (value) 
        ToogleActionName = "turn it off"; 
       else 
        ToogleActionName = "turn it on"; 

       OnPropertyChanged(); 
      } 
     } 

     public string Content 
     { 
      get 
      { 
       return _content; 
      } 
      set 
      { 
       _content = value; 
       OnPropertyChanged(); 
      } 
     } 

     public string ToogleActionName 
     { 
      get 
      { 
       return _toogleActionName; 
      } 
      set 
      { 
       _toogleActionName = value; 
       OnPropertyChanged(); 
      } 
     } 

    } 


    public class ViewModelBase : INotifyPropertyChanged 
    { 
     public event PropertyChangedEventHandler PropertyChanged; 

     protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 
     { 
      if (PropertyChanged != null) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 
    } 
} 

これは、WPFがMVVMパターンで動作する方法を示していることを期待しています。

+0

ああ、OK:それを得た!はい!!私はそれが大好きです。申し訳ありませんが、私はちょうどその時に言語を学ぶことで自分のやり方を争っています。時には私は好奇心をそそられます。私はMVVMを勉強しなければならないと知っていますが、私はまだそれをやっていません(私は3週目だと思います)。技術的に、彼は私の質問に答えることはできませんが、最高のTLDRは+1します。私はこれまでこの生態系で見たことがあります。私が読んだものはすべて、信じられないほど長く巻き込まれ、クロスカップルです。 –

+1

ありがとうございます。あなたの学習のために幸運;-)そして、忘れないでください:「WPFは難しいことを簡単にし、単純なことを難しくします」 – Mat

関連する問題