C# と VB.NET の質問掲示板

わんくま同盟

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

ツリー一括表示

【WPF】DataTemplatedeのViewModel /tro (19/08/23(Fri) 09:13) #92071
Re[1]: 【WPF】DataTemplatedeのViewModel /Hongliang (19/08/23(Fri) 10:03) #92073
  └ Re[2]: 【WPF】DataTemplatedeのViewModel /tro (19/08/23(Fri) 10:25) #92075
    └ Re[3]: 【WPF】DataTemplatedeのViewModel /Hongliang (19/08/23(Fri) 10:46) #92076
      └ Re[4]: 【WPF】DataTemplatedeのViewModel /tro (19/08/23(Fri) 10:53) #92077
        └ Re[5]: 【WPF】DataTemplatedeのViewModel /Hongliang (19/08/23(Fri) 13:59) #92084
          └ Re[6]: 【WPF】DataTemplatedeのViewModel /Hongliang (19/08/23(Fri) 14:26) #92085
            └ Re[7]: 【WPF】DataTemplatedeのViewModel /tro (19/08/23(Fri) 18:46) #92086
              └ Re[8]: 【WPF】DataTemplatedeのViewModel /Hongliang (19/08/23(Fri) 19:02) #92087
                └ Re[9]: 【WPF】DataTemplatedeのViewModel /tro (19/08/23(Fri) 21:31) #92088


親記事 / ▼[ 92073 ]
■92071 / 親階層)  【WPF】DataTemplatedeのViewModel
□投稿者/ tro (12回)-(2019/08/23(Fri) 09:13:29)

分類:[.NET 全般] 

DataTemplateを使用してContentControlの部分の画面遷移を実現しようと考えていますが、
表示するViewのViewModelが上手くデータバインディングできずに困っています。
DataTemplateを使用しない場合は問題ないのですが。

■サンプルコード
<Window x:Class="Test.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:Test"
xmlns:view="clr-namespace:Test.View"
xmlns:vm="clr-namespace:Test.ViewModel"
mc:Ignorable="d"
Title="Test" Height="768" Width="1024">

<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>

<Window.Resources>
<DataTemplate DataType="{x:Type vm:APageViewModel}">
<view:APageView DataContext="{Binding APageViewModel}"/> ←データバインディングしたViewModelが使用されない
</DataTemplate>
<DataTemplate DataType="{x:Type vm:BPageViewModel}">
<view:BPageView DataContext="{Binding BPageViewModel}"/>
</DataTemplate>
</Window.Resources>

<Grid>
<ContentControl Content="{Binding CurrentPage, Mode=TwoWay}"/>
<!--<view:APageView DataContext="{Binding APageViewModel}"/>--> ←直接表示する場合はデータバインディングしたViewModelが使用される
</Grid>
</Window>


■APageViewのxaml
<UserControl x:Class="Test.View.APageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Test.View"
xmlns:vm="clr-namespace:Test.ViewModel"
mc:Ignorable="d"
d:DesignHeight="768" d:DesignWidth="1024">

<UserControl.DataContext>
<vm:APageViewModel/>
</UserControl.DataContext>

</Grid>
</Grid>
</UserControl>

[ □ Tree ] 返信 編集キー/

▲[ 92071 ] / ▼[ 92075 ]
■92073 / 1階層)  Re[1]: 【WPF】DataTemplatedeのViewModel
□投稿者/ Hongliang (872回)-(2019/08/23(Fri) 10:03:21)
一般論を言うと、DataContextは基本的に論理ツリーの親から継承するものです。
Windowのようなルート要素を除いて、明示的にDataContextプロパティに
設定することはあんまりありません(ないとは言いませんが)。

UserControlもWindow(とかPage)の子孫要素となる存在なので、
XAML上のルートといえどDataContextプロパティに設定することは普通ではありません。
デザイナで一時的にバインディングが必要であればd:DataContextを使います。

さて、
> <Window.DataContext>
>     <local:MainViewModel/>
> </Window.DataContext>
なので、以下のDataContextはMainViewModelインスタンスが継承されます。
途中でDatacontextが変更されたりしていないので、
> <ContentControl Content="{Binding CurrentPage, Mode=TwoWay}"/>
はMainViewModel::CurrentPageが返すインスタンスがバインドされます。

ここでContentControlはContentTemplateを実体化し、
実体化したUI要素にCurrentPageが返すインスタンスを
以降のツリーのDataContextとして継承させることになります。

CurrentPageが返すインスタンスがAPageViewModel型の場合、
> <DataTemplate DataType="{x:Type vm:APageViewModel}">
>     <view:APageView DataContext="{Binding APageViewModel}"/>
> </DataTemplate>
このDataTemplateが選択されるわけですが、ここで継承されているDataContextは
上述の通りCurrentPageが返すAPageViewModelなので、
このバインディングは
「APageViewModelインスタンスが持つAPageViewModelプロパティ」
ということになります。

このXAMLが要求するViewModelをコードで表現するとこういう形になります。
// INotifyPropertyChanged関連は省略。

class MainViewModel {
    public Hoge CurrentPage { get; set; }
}
class APageViewModel : Hoge {
    // 実際にはこれはコンパイルできない
    public Piyo APageViewModel { get; set; }
}

結論としては、
> <view:APageView DataContext="{Binding APageViewModel}"/>
でわざわざDataContextプロパティを設定する必要はありません。
そしてAPageViewのXAMLでDataContextプロパティを設定しないようにします。

[ 親 92071 / □ Tree ] 返信 編集キー/

▲[ 92073 ] / ▼[ 92076 ]
■92075 / 2階層)  Re[2]: 【WPF】DataTemplatedeのViewModel
□投稿者/ tro (13回)-(2019/08/23(Fri) 10:25:39)
回答ありがとうございます。
事情があってAPageViewのxamlのDataContextプロパティは設定したままとしたいです。

いろいろ調べてDateTemplate内のDataContextを以下のように親のViewModelを参照するように変更してみたのですが、
これでもまだ上手くいきません。

<Window.Resources>
<DataTemplate DataType="{x:Type vm:APageViewModel}">
<view:APageView DataContext="{Binding DataContext.APageViewModel, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Mode=TwoWay}"/>
</DataTemplate>


[ 親 92071 / □ Tree ] 返信 編集キー/

▲[ 92075 ] / ▼[ 92077 ]
■92076 / 3階層)  Re[3]: 【WPF】DataTemplatedeのViewModel
□投稿者/ Hongliang (873回)-(2019/08/23(Fri) 10:46:43)
今後の参考としたいので、是非ともその事情というのをお聞きしたいです。

DataContextプロパティを設定しなければならないのであれば、継承したDataContextをそのままバインディングすればいいのではないでしょうか。
<view:APageView DataContext="{Binding}"/>
[ 親 92071 / □ Tree ] 返信 編集キー/

▲[ 92076 ] / ▼[ 92084 ]
■92077 / 4階層)  Re[4]: 【WPF】DataTemplatedeのViewModel
□投稿者/ tro (14回)-(2019/08/23(Fri) 10:53:18)
No92076 (Hongliang さん) に返信
> 今後の参考としたいので、是非ともその事情というのをお聞きしたいです。
>
> DataContextプロパティを設定しなければならないのであれば、継承したDataContextをそのままバインディングすればいいのではないでしょうか。
> <view:APageView DataContext="{Binding}"/

上記のコードを試してみましたが、
APageViewのDataContextプロパティに設定されているViewModelが優先されてしまうようです。

事情というのは、
APageViewはコンポーネント化されており、
Windowに貼り付けてそのまま使用できる状態をデフォルトとしたいためです。

[ 親 92071 / □ Tree ] 返信 編集キー/

▲[ 92077 ] / ▼[ 92085 ]
■92084 / 5階層)  Re[5]: 【WPF】DataTemplatedeのViewModel
□投稿者/ Hongliang (874回)-(2019/08/23(Fri) 13:59:06)
> 上記のコードを試してみましたが、
> APageViewのDataContextプロパティに設定されているViewModelが優先されてしまうようです。

んー、これはタイミングの問題なのかな。
XAMLで <UserControl.DataContext> ... と定義するのをやめて、
代わりにAPageViewのコンストラクタで
SetCurrentValue(DataContextProperty, new ...)
としてみてください。


> 事情というのは、
> APageViewはコンポーネント化されており、
> Windowに貼り付けてそのまま使用できる状態をデフォルトとしたいためです。

コンポーネント化するのであれば、objectでしかないDataContextなど
ではなく、独自に依存関係プロパティを定義した方が良いのでは?
[ 親 92071 / □ Tree ] 返信 編集キー/

▲[ 92084 ] / ▼[ 92086 ]
■92085 / 6階層)  Re[6]: 【WPF】DataTemplatedeのViewModel
□投稿者/ Hongliang (875回)-(2019/08/23(Fri) 14:26:51)
おっと、
DataContextの場合
> SetCurrentValue(DataContextProperty, new ...)
これは継承によって消えてしまって既定の値になりませんね。

以下の2通りの解決案を考えました。

A. 独自の依存関係プロパティを作る
こちらの方がおすすめです。
既定値はコンストラクタのSetCurrentValueで設定。
APageView.xaml内でのDataContextの継承は、以下のように記述できます。
// 依存関係プロパティの名前をValueとする。
<UserControl ...>
  <Grid DataContext="{Binding Path=Value,
     RelativeSource={RelativeSource AncesctorType={x:Type local:APageView}}">
    <!-- 以降、{Binding Path=Hoge} でValueのHogeプロパティを参照できる -->

B. DataContextにこだわるなら、既定のスタイルを使う
1) プロジェクトにThemesフォルダを作り、そこにGeneric.xamlという
  リソースディクショナリを追加して以下を記述します。
<Style TargetType="{x:Type local:APageView}"
       BasedOn="{StaticResource {x:Type UserControl}}">
  <Setter Property="DataContext">
    <Setter.Value>以下略

2) APageViewに静的コンストラクタを作成し、以下を記述します。
DefaultStyleKeyProperty.OverrideMetadata(typeof(APageView),
    new FrameworkPropertyMetadata(tyepof(APageView)));

[ 親 92071 / □ Tree ] 返信 編集キー/

▲[ 92085 ] / ▼[ 92087 ]
■92086 / 7階層)  Re[7]: 【WPF】DataTemplatedeのViewModel
□投稿者/ tro (15回)-(2019/08/23(Fri) 18:46:32)
教えていただいたAの対策で依存関係プロパティを作成し、下記まで記述してみましたが変わらないようです。
<UserControl ...>
<Grid DataContext="{Binding Path=Value,
RelativeSource={RelativeSource AncesctorType={x:Type local:APageView}}">

下記の使い方がよく分かりませんでした。
<!-- 以降、{Binding Path=Hoge} でValueのHogeプロパティを参照できる -->
[ 親 92071 / □ Tree ] 返信 編集キー/

▲[ 92086 ] / ▼[ 92088 ]
■92087 / 8階層)  Re[8]: 【WPF】DataTemplatedeのViewModel
□投稿者/ Hongliang (876回)-(2019/08/23(Fri) 19:02:52)
そこだけだと何が悪いのか分かりませんね。
Window側のXAMLでValue="{Binding}"していますか?
出力ウィンドウにバインディングのエラーが出ていませんか?

> 下記の使い方がよく分かりませんでした。
> <!-- 以降、{Binding Path=Hoge} でValueのHogeプロパティを参照できる -->

class MainViewModel : INotifyPropertyChanged {
  public PageViewModelBase CurrentPage { get; set; }
}
class PageViewModelBase : INotifyPropertyChanged {
}
class APageViewModel : PageViewModelBase {
  public string Name { get; set; }
}

<Window ...>
  <local:APageView Value="{Binding CurrentPage}"/>

<UserControl ...>
  <Grid DataContext="...">
    <TextBlock Text="{Binding Name}"/>

[ 親 92071 / □ Tree ] 返信 編集キー/

▲[ 92087 ] / 返信無し
■92088 / 9階層)  Re[9]: 【WPF】DataTemplatedeのViewModel
□投稿者/ tro (16回)-(2019/08/23(Fri) 21:31:11)
2019/08/23(Fri) 21:31:45 編集(投稿者)

このようにしたら一応動きました。

<Window ...>
x:Name="mainBase">

<Window.Resources>
<DataTemplate DataType="{x:Type vm:APageViewModel}">
<local:APageView Value="{Binding ElementName=mainBase, Path=DataContext.CurrentPage, Mode=TwoWay}"/>
</DataTemplate>
</Window.Resources>


ただ下記のように教えていただいたGridのDataContextにバインドするのではなく、
<UserControl ...>
<Grid DataContext="{Binding Path=Value,
RelativeSource={RelativeSource AncesctorType={x:Type local:APageView}}">

このような指定は残しておきたいのですが、
他に方法はないのでしょうか。
<UserControl.DataContext>
<vm:APageViewModel/>
</UserControl.DataContext>


[ 親 92071 / □ Tree ] 返信 編集キー/


管理者用

- Child Tree -