プロが教えるわが家の防犯対策術!

visual C# 2010 Express を使用しています。

UserControl1とUserControl2をFooクラスのメンバAuthorizedによって
切り替えるということをしたいと思い、
MainWindow.xamlの内容を次のようにしました。

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/pre …
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication1">
<Window.DataContext>
<my:Foo/>
</Window.DataContext>
<Grid>
<my:UserControl1 HorizontalAlignment="Left" Margin="140,74,0,0" x:Name="userControl11" VerticalAlignment="Top" Height="44" Width="82" Visibility="{Binding Path=NotAuthorized, Converter={StaticResource BooleanToVisibilityConverter1}}" />
<my:UserControl2 Height="45" HorizontalAlignment="Left" Margin="288,65,0,0" x:Name="userControl21" VerticalAlignment="Top" Width="77" Visibility="{Binding Path=Authorized, Converter={StaticResource BooleanToVisibilityConverter1}}" />
<Button Content="Button" Height="38" HorizontalAlignment="Left" Margin="38,109,0,0" Name="button1" VerticalAlignment="Top" Width="80" Click="button1_Click" />
<TextBox Height="40" HorizontalAlignment="Left" Margin="46,34,0,0" Name="textBox1" VerticalAlignment="Top" Width="88" />
</Grid>
</Window>

そしてFooクラス

[Foo.cs]
class Foo
{
private bool _authorized = false;

public bool Authorized
{
get { return _authorized; }
set
{
_authorized = value;
}
}

public bool NotAuthorized { get { return !Authorized; } }
}



そして、MainWindow.xaml.csのbutton1_Click内で、

Foo f = new Foo();
f.Authorized = true;

とやったのですが、切り替わらないのです。
なんとなく自分でもこれだけでは切り替わらないだろうなと想像はつくのですが、
でも下の図のプロパティでは
[ソース: (Datacontext - Foo)]
とありDatacontextはFooクラスだと認識してますよね?
なのでこの記述

Visibility="{Binding Path=NotAuthorized, Converter={StaticResource BooleanToVisibilityConverter1}}"

だけでも認識してくれるのかなと期待したのですが、駄目でした・・・
この場合はどう記述すれば良いのでしょうか?またなぜそれが必要なのかという理由も教えて頂けないでしょうか?

「DataContextについて」の質問画像

A 回答 (3件)

> なるほど、Fooクラスというのはアプリケーションの全体のモデルということですか。


> ですが、その概念が分からなかったというか、今でもあまりイメージできないのですが、
> というのも、今まで自分はPHP言語のフレームワークを利用してなにかと作業(かじった程度ですが^^;))していたのですが、

了解です。
こう言ってはなんですが,WPFで作る時にはPHPのMVCに対する考え方を一端忘れてしまってもかまわないと思います。


PHPのフレームワーク自体には明るくないのですが,WebアプリケーションにおけるMVCについては一応は知っているつもりです。
で,MVCに関しては,
・元々Smalltalkでの開発時に生まれた
→今で言うデスクトップアプリケーションに相当するもので使われるもの
・Webの世界でMVCという名前が流用されて,こちらが有名になった
→Modelに関して大きな誤解ができる余地ができた
・MVCをWPFやSilverlightに適用しようとした場合に,MVVMというパターンがWPF等に都合が良いように作られた
→私がModelやViewModelと言っているのは,MVVMにおけるもの
という状況だと考えてください。


デスクトップアプリケーションでは,Modelが状態を保持します (Modelは「ステートフル」)。
つまり,例えばログインした後のユーザー名やログイントークン,入力中のテキストなどをModelが保持し続けます。
また,それが可能です。


それに対して,同様のことをWebアプリケーションで行う場合,HTTPというプロトコルの特性上,リクエスト-レスポンスでModelは原則的に消滅します (Modelは「ステートレス」)。
このため,「アプリケーション全体」という考え方が存在しません。
呼び出しごとにアプリケーション全体の情報を用意して,必要な部分だけ使う,というのは無意味ですから。
データベースサーバーにシリアライズされたデータを,必要な部分だけデシリアライズして使うことになります。


> MVCといえば、Model、View、Controller、に分けて、その名の通り
> データ関係はModel
> 表示関係はView
> 制御関係はController
> が担当して、コントローラ内でモデルを呼び出し処理をさせて
> コントローラ内でviewに変数をアサイン(?)する。その変数を参照してviewで表示。

オリジナルのMVCでは,入力をCが受け付け,Mを呼び出します。
VはMの変更を「監視し」,Mの変更があるとV自身を修正します。
# ツールキットの都合上,Vが入力を受け付けてCに通知し,という形にならざるを得ないこともありますが。

「監視し」,という点が重要で,サーバー側でMVCが完結するWebアプリケーションにおいては,監視ができないため,歪なMVCになってしまっています。
オリジナルはMがステートフルなのです。

将来的にHTML5やその後継が普及した場合,JavaScriptでの記述が増え,jsによるMVC or 相当品が出てくるでしょう。
その場合,Mはおそらくステートフルになると思います。
# ロジックはサーバーに投げるとしても,元データはMが保持し続ける。


> そのケースにおいて今回のFooクラスというかアプリケーション全体のモデルに相当する箇所は、
> PHPのフレームワークの場合ではどのあたりなのでしょうか?

WebアプリケーションのMVCというでは存在しませんし,存在するだけ無駄になります。
ステートフルModelであるクライアントアプリケーションと,ステートレスModelであるWeb (HTTP) アプリケーションの違いの部分でもあります。


references)
日本のMVVMに関する第一人者といってもよい,MVVMライブラリであるLivetの作者でもある尾上氏のBlog
Blog: MVVMパターンとイベント駆動開発、そしてMVC/MVP/PMパターンとの関係 – 何故MVVMなのか - the sea of fertility
http://ugaya40.net/wpf/mvvm-mvc-mvp-pm-eventdriv …
Blog: MVVMパターンの適応 – 2011年のMVVMパターンの常識 - the sea of fertility
http://ugaya40.net/mvvm/mvvm-2011.html


> それと、あれからいくつか試してみたのですが、コード側で
> Binding binding = new Binding("NotAuthorized");
> binding.Converter = new BooleanToVisibilityConverter();
> this.userControl11.SetBinding(UserControl.VisibilityProperty, binding);
> this.DataContext = this.f;
> とやったらできました。
> xamlはあまり分からないので^^;)、コード側で記述できると安心するというか。。。

XAMLの記述はほぼコードに直すことができます。
たとえば,
<Window.DataCotnext>
<local:Foo />
</Window.DataContext>
は,
this.DataContext = new Foo();
に相当します。
属性の設定は,プロパティへの代入という形でだいたい処理できます。
ただし,DataTemplateなどを考えると,コードだけに頼るのはWPFの強みを消している気がします。

なお,XAMLで書いた物は,objディレクトリの中に.g.csのような形でC#コード化されているはずです。


> とくにBooleanToVisibilityConverterというかそこらへんに絞ると圧倒的に情報が少なくなりますよね。。

単純なBindingの話ならそれなりに見つかりますが,確かにConverterは少なくなりますね。
Converterが必要な状況というのはそれほど多くない上,VMである程度制御できてしまうので。
MSDNあさるのが一番色々載っているかもしれません。


> あと、これも教えて頂きたいのですが、
> <local:Foo/>
> とのことなのですが、それだと自分の環境(たぶん初期の状態)では
> 「'local' は宣言されていないプレフィックスです。」
> とエラーが出てしまいます・・・
> なので自分は
> <my:Foo/>
> にしてやっているのですが、どうすればlocalでできるようになるのでしょうか?

myでもいいですよ。
私自身が,自身の慣例としてlocalをXML名前空間に使っているだけですので。


XAMLではなく,XMLの世界の話ですが,XML名前空間というものがあります。
例えばXHTMLとMathMLを混合させて使うような場合に,同じ要素名を分けて使うための物ですが,
その機構で定義されるのがlocalでありmyです。

Windowの開始タグに,xmlns:my="clr-namespace:(コードの名前空間名)"という属性がありませんか。
これがXML名前空間の宣言です。
xmlns="(名前空間URI)"だと,プリフィックス無しのタグの名前空間の宣言,
xmlns:(プリフィックス)="(名前空間URI)"だと,プリフィックスで指定したタグや属性の名前空間の宣言になります。
XML名前空間はURI (≒URL) を使って宣言する (XHTMLだとhttp://www.w3.org/1999/xhtml ) のですが,
XAMLでは,スキームとしてclr-namespaceを指定することで,コードの名前空間を指定できます。

MSDN: XAML 名前空間および WPF XAML の名前空間の割り当て # アセンブリ内の XML 名前空間への CLR 名前空間の割り当て
http://msdn.microsoft.com/ja-jp/library/ms747086 …
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。
なるほど、とても参考になりました。
なんといって良いのか分かりませんが、一度に多くの情報を得たので、
まだ整理しきれてませんが、ここのサイト(http://ugaya40.net/mvvm/mvvm-2011.html
も含めて、一通り読んだだけですけどとても興味ある情報があったので、
これから時間のあるときにじっくり読んで考えてみたいと思います。
それと、仰るとおりlocalに変更してできました。
ありがとうございます。

お礼日時:2011/10/15 20:11

> と変更してみたのですがPropertyChangedがnullなので


> 当然のことながら入ってこないのです。。
> どこでPropertyChangedを生成というかnullじゃない状態に
> するのでしょうか?

えーっと,バインドするオブジェクト自体は,WindowなりControlなりのDataContextに指定します。
コードなら,
public partial class MainWindow : Window
{
private Foo _foo;
public MainWindow()
{
InitializeComponent();
_foo = new Foo();
DataContext = _foo;
}

private void button1_Click(object sender, RoutedEventArgs e)
{
_foo.Authorized = true;
}
}
という感じになります。
また,XAMLでは
<Window.DataContext>
<local:Foo/> <!-- localはFooの名前空間に対してつけたxmlのプリフィックス。Windowの属性として,xmlns:local="clr-namespace:WpfApplication1"のように定義 -->
</Window.DataContext>
と書けば,自動でオブジェクトを作ってバインドしてくれます。コードビハインドは,
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void button1_Click(object sender, RoutedEventArgs e)
{
((Foo)DataContext).Authorized = true;
}
}
となります。
キャストが邪魔に思えるかもしれませんが,最終的にbutton1_ClickすらFooクラス内に記述してバインドで処理できるようになるので (Q7064034のANo.3,RelayCommandとLoginプロパティ等を参照),
このClickイベントが削除されるまでの一時的なものです。


> それとUserControl1に
> DataContext="{Binding ModelForUserControl1}"
> は追加しないと駄目なのでしょうか?
> そもそもFooクラスとModelForUserControl1の関係が
> いまいち理解できないというか、、
> ModelForUserControl1をnewするときにFooクラス自身を渡していますが、
> これも
> DataContext="{Binding ModelForUserControl1}"
> この存在を理解しないとたぶんイメージできないと思うので
> 申し訳ないのですが、そのあたりの解説もお願いできないでしょうか?

MVVMという物を念頭に置いているのですが,最終目標はViewとLogicの分離です。
ただ,今回の作りはMVVMのMとVMを(意図的に)分離していません。
そのため,Logicを含むModelがViewに引きずられています。

まず,Fooという名前をつけてしまいましたが,これはアプリケーション全体のModelに相当します。
# MainViewModelとかの方がよかったですね。
で,UserControl1についてもViewとLogicを分離すれば当然Logic部分に相当する,Modelが出てきます。
これがUserControl1ViewModelクラスです。
で,このModelは,Fooの特定のインスタンスから生成されるのが自然です。
# アプリケーション全体の一部分なので。
そのため,FooクラスがUserControl1ViewModelクラスを生成します。
その生成したインスタンスをUserControl1に教えてあげる必要があります。
そのために,ModelForUserControl1というプロパティを用意して外部から取得できるようにして,
UserControl1のDataContextにバインドする形で「自分自身に紐付いたFooクラスのインスタンスに紐付いたUserControl1ViewModelクラスのインスタンス」を「自分自身の子コントロールであるUserControl」に引き渡しています。

UserControl1ViewModelクラスの作成時にFooクラスのインスタンスを渡しているのは,単純に楽になるから,という程度の物です。
必要なければ渡す必要はありません。
# 例示したものでは,UserControl1ViewModelクラス中で認証処理を行って,結果をFoo側に渡す必要があったためFooを渡していました。

画像を添付しますので,クラス同士の関係について,ある程度イメージが掴めて頂けるとうれしいです。
元画像: http://pub.idisk-just.com/fview/uG65y08xEVsl4AXc …


データバインディング関係の記事では,C#関連の記事が色々あって有名な
Site: ++C++;// 未確認飛行 C
http://ufcpp.net/
の管理人である岩永さんの@ITにおける記事
Site: WPF入門 - @IT
http://www.atmarkit.co.jp/fdotnet/chushin/introw …
の第5回,第6回などが役に立つと思います。
「DataContextについて」の回答画像2
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。
画像まで提示して頂きありがとうございます。
なるほど、Fooクラスというのはアプリケーションの全体のモデルということですか。
ですが、その概念が分からなかったというか、今でもあまりイメージできないのですが、
というのも、今まで自分はPHP言語のフレームワークを利用してなにかと作業(かじった程度ですが^^;))していたのですが、
MVCといえば、Model、View、Controller、に分けて、その名の通り
データ関係はModel
表示関係はView
制御関係はController
が担当して、コントローラ内でモデルを呼び出し処理をさせて
コントローラ内でviewに変数をアサイン(?)する。その変数を参照してviewで表示。
という流れだと思います。
そこでモデルなのですが、例えばUserモデル(id,username,password)とArticleモデル(id,body,author)
があったとします。そのケースにおいて今回のFooクラスというかアプリケーション全体のモデルに相当する箇所は、
PHPのフレームワークの場合ではどのあたりなのでしょうか?
PHPのフレームワークの場合で全体をまとめるモデルというのがピンとこないというか、イメージできないのです・・・
Yune-KichiさんがPHPのフレームワークについてどれくらいご存知か分からないのですが、
もしご存知でしたら、その2つを比べることによってイメージできればと思いまして。

それと、あれからいくつか試してみたのですが、コード側で

Binding binding = new Binding("NotAuthorized");
binding.Converter = new BooleanToVisibilityConverter();
this.userControl11.SetBinding(UserControl.VisibilityProperty, binding);
this.DataContext = this.f;

とやったらできました。
xamlはあまり分からないので^^;)、コード側で記述できると安心するというか。。。
ただこれひとつネットなどで探すにしても苦労しました・・・
とくにBooleanToVisibilityConverterというかそこらへんに絞ると圧倒的に情報が少なくなりますよね。。

あと、これも教えて頂きたいのですが、
<local:Foo/>
とのことなのですが、それだと自分の環境(たぶん初期の状態)では
「'local' は宣言されていないプレフィックスです。」
とエラーが出てしまいます・・・
なので自分は
<my:Foo/>
にしてやっているのですが、どうすればlocalでできるようになるのでしょうか?

お礼日時:2011/10/15 12:25

Q7064034で回答した者です。



WinForms/WPF/Silverlightすべてのデータバインドで双方向のバインドを行うには,
System.ComponentModel.INotifyPropertyChangedインターフェースを実装したクラスを利用する必要があります。
# .NET 1.xではプロパティごとに(PropertyName)Changedというイベントを用意するのですが。

そして,プロパティの値が変更されたタイミングで,PropertyChangedイベントを発生させる必要があります。
プロパティの値が変わったことを知る方法は毎回取得する以外に存在しませんが,
PropertyChangedイベントによって,UI側はバインドされているプロパティの値が変更されたことを知ります。

同じように,コレクションをバインドした場合,INotifyCollectionChangedというインターフェースがあります。
特定の要素の位置が変更したとか,追加されたとかを通知するインターフェースです。
# 実装クラスであるObservableCollection<T>があるので,あまり使うことはありませんが。


Q7064034の回答2のコード,AuthorizaedのSet部分に,
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Authorized"));
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("NotAuthorized"));
}
とあるのは,変更を通知する為です。
NotAuthorizedはAuthorizedに連動させているため,Authorizedの設定時にプロパティの変更を通知しています。


references)
MSDN: INotifyPropertyChanged インターフェイス (System.ComponentModel)
http://msdn.microsoft.com/ja-jp/library/system.c …

MSDN: データ バインド (WPF)
http://msdn.microsoft.com/ja-jp/library/ms750612 …

MSDN: データ バインディングの概要
http://msdn.microsoft.com/ja-jp/library/ms752347 …

MSDN: バインディング ソースの概要
http://msdn.microsoft.com/ja-jp/library/ms743643 …

MSDN: 方法 : プロパティの変更通知を実装する
http://msdn.microsoft.com/ja-jp/library/ms743695 …

この回答への補足

すいません、訂正です。

>それとVisibilityに

ではなくて

それとUserControl1に

補足日時:2011/10/13 12:54
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。
その節はありがとうございました。
すいません、せっかくコードを提示して頂いて本当ならその場で
疑問を解決するのがベストなんでしょうけど、なかなか自分の理解力だと
1つづ疑問を片付けていかないと理解できないので^^;)申し訳ないのです。。

仰るとおり、FooクラスにAuthorizedにPropertyChangedを追加して
そしてSystem.ComponentModel.INotifyPropertyChangedも継承して

class Foo : System.ComponentModel.INotifyPropertyChanged
{
private bool _authorized = false;
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

public bool Authorized
{
get { return _authorized; }
set
{
_authorized = value;
if (PropertyChanged != null)
{
MessageBox.Show("in");
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Authorized"));
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("NotAuthorized"));
}
}
}

public bool NotAuthorized { get { return !Authorized; } }
}

MainWindow.xam.csは

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void button1_Click(object sender, RoutedEventArgs e)
{
Foo f = new Foo();
f.Authorized = true;
}
}


と変更してみたのですがPropertyChangedがnullなので
当然のことながら入ってこないのです。。
どこでPropertyChangedを生成というかnullじゃない状態に
するのでしょうか?

それとVisibilityに

DataContext="{Binding ModelForUserControl1}"

は追加しないと駄目なのでしょうか?
そもそもFooクラスとModelForUserControl1の関係が
いまいち理解できないというか、、
ModelForUserControl1をnewするときにFooクラス自身を渡していますが、
これも
DataContext="{Binding ModelForUserControl1}"
この存在を理解しないとたぶんイメージできないと思うので
申し訳ないのですが、そのあたりの解説もお願いできないでしょうか?

お礼日時:2011/10/13 12:39

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!