質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

1回答

2033閲覧

ViewModelを持つUserControlのCommandの処理内容をFormに記述する方法

退会済みユーザー

退会済みユーザー

総合スコア0

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

0グッド

0クリップ

投稿2018/06/28 10:58

編集2018/06/30 14:56

環境

VisualStudio2017(C#)
.NetFramework 4.7.2
Prism.Wpf 6.3.0
ReactiveProperty 5.0.0

前提・実現したいこと

ViewModelを持つUserControl上にあるボタンを押下した際に、Bindingされたコマンドの
内容をFormのViewModelに記述したい
使い所は、複数画面を持つシステムの、各画面にファンクションキー12個分のボタンを持つ
UserControlを配置し、各ボタンの処理は各画面のViewModelに記述したい
そういった処理を行いたい場合、どのように行ったらよろしいでしょうか?

イメージ説明

試してみたソース

UserControlのXAML(上の画像とは異なります)

C#

1<UserControl x:Class="UserControlSample.Views.PrismUserControl1" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:prism="http://prismlibrary.com/" 5 prism:ViewModelLocator.AutoWireViewModel="True"> 6 <Button Width="50" Height="50" Command="{Binding CommandExecute}" Content="ボタン" /> 7</UserControl>

UserControlのViewModel

C#

1using Prism.Mvvm; 2 3namespace UserControlSample.ViewModels 4{ 5 public class PrismUserControl1ViewModel : BindableBase 6 { 7 public PrismUserControl1ViewModel() 8 { 9 } 10 } 11}

FormのXAML

C#

1<Window x:Class="UserControlSample.Views.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:prism="http://prismlibrary.com/" 5 xmlns:u="clr-namespace:UserControlSample.Views" 6 Title="{Binding Title}" 7 Width="525" 8 Height="350" 9 prism:ViewModelLocator.AutoWireViewModel="True"> 10 <StackPanel> 11 <u:PrismUserControl1 /> 12 </StackPanel> 13</Window>

FormのViewModel

C#

1using Prism.Mvvm; 2using Reactive.Bindings; 3using System; 4 5namespace UserControlSample.ViewModels 6{ 7 public class MainWindowViewModel : BindableBase 8 { 9 public ReactiveCommand CommandExecute { get; set; } = new ReactiveCommand(); 10 11 public MainWindowViewModel() 12 { 13 this.CommandExecute.Subscribe(Xxx); 14 } 15 16 private void Xxx() 17 { 18 // ボタン押下時にこの処理が行われるようにしたいが、このソースだとここは動かない 19 Console.WriteLine("終了ボタン押下"); 20 } 21 } 22}

試したこと

UserControlのViewModelを削除し、Viewだけにすれば、上記「終了ボタン押下」が実行されるが
UserControlにViewModelがあると、Xxxメソッドが呼び出されない

【追記】

コマンド部分を別クラスに分けて MainWindowViewModel / PrismUserControl1ViewModel の両方に注入する方法。

UserControlプロジェクトにコマンドのみのクラス追加

C#

1using Reactive.Bindings; 2 3namespace PrismUserControl1 4{ 5 public class FunctionCommand 6 { 7 public ReactiveCommand CommandExecute { get; set; } = new ReactiveCommand(); 8 } 9}

UserControlのViewModelにあるコンストラクタで上記コマンドのクラスを受け取るようにする

C#

1using Prism.Mvvm; 2 3namespace PrismUserControl1.ViewModels 4{ 5 public class PrismUserControl1ViewModel : BindableBase 6 { 7 // PrismUserControl1 でバインドして使えるようにアクセサを設ける。 8 public FunctionCommand FunctionCommand { get; set; } 9 10 public PrismUserControl1ViewModel() 11 { 12 } 13 14 public PrismUserControl1ViewModel(FunctionCommand functionCommand) 15 { 16 // アクセサに設定する。 17 FunctionCommand = functionCommand; 18 } 19 } 20}

FormのViewModelもUserControlと同様にコンストラクタでFunctionComandを受け取るようにする

C#

1using Prism.Mvvm; 2using PrismUserControl1; 3using System; 4 5namespace UserControlSample.ViewModels 6{ 7 public class MainWindowViewModel : BindableBase 8 { 9 // PrismUserControl1 でバインドして使えるようにアクセサを設ける。 10 public FunctionCommand FunctionCommand { get; set; } 11 12 public MainWindowViewModel() 13 { 14 } 15 16 public MainWindowViewModel(FunctionCommand functionCommand) 17 { 18 // アクセサに設定する。 19 FunctionCommand = functionCommand; 20 21 // CommandExecuteに処理のわりあて 22 FunctionCommand.CommandExecute.Subscribe(Xxx); 23 } 24 25 private void Xxx() 26 { 27 Console.WriteLine("ボタン押下"); 28 } 29 } 30}

ConfigureContainerでFunctionCommandを注入できるようにする

c#

1using Microsoft.Practices.Unity; 2using Prism.Modularity; 3using Prism.Unity; 4using PrismUserControl1; 5using System.Windows; 6using UserControlSample.Views; 7 8namespace UserControlSample 9{ 10 internal class Bootstrapper : UnityBootstrapper 11 { 12 protected override DependencyObject CreateShell() 13 { 14 return Container.Resolve<MainWindow>(); 15 } 16 17 protected override void InitializeShell() 18 { 19 Application.Current.MainWindow.Show(); 20 } 21 22 protected override void ConfigureModuleCatalog() 23 { 24 var moduleCatalog = (ModuleCatalog)ModuleCatalog; 25 //moduleCatalog.AddModule(typeof(YOUR_MODULE)); 26 } 27 28 protected override void ConfigureContainer() 29 { 30 base.ConfigureContainer(); 31 32 // FunctionCommandを設定できるようにする 33 Container.RegisterType<FunctionCommand>(new ContainerControlledLifetimeManager()); 34 } 35 } 36}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

Prism を使っているなら話が早いです。
掲示されているソースコードから ViewModelLocator を使っていることも読み取れます。
それを前提とした上で、以下の実装を追加することで実現できます。

  • Prism の DI コンテナに MainWindowViewModel をシングルトンとして扱うよう指示する。
  • PrismUserControl1ViewModel に MainWindowViewModel のアクセサを設ける。
  • PrismUserControl1ViewModel のコンストラクタに引数を追加し、DI コンテナからシングルトンの MainWindowViewModel インスタンスを受け取る入口を作る。
  • PrismUserControl1 に PrismUserControl1ViewModel.MainWindowViewModel のコマンドをバインドする。

具体的な実装は以下の通りです。

Bootstrapper.cs

C#

1using Microsoft.Practices.Unity; 2using Prism.Unity; 3 4namespace UserControlSample 5{ 6 // DI コンテナが Unity 以外の場合は適宜読み替えること。 7 public class Bootstrapper : UnityBootstrapper 8 { 9 protected override void ConfigureContainer() 10 { 11 base.ConfigureContainer(); 12 13 // Prism の DI コンテナに MainWindowViewModel をシングルトンとして扱うよう指示する。 14 // こうすることで MainWindow 自身が持つインスタンスと PrismUserControl1ViewModel に DI されるインスタンスが同一(シングルトン)のものにできる。 15 // この設定をしないとそれぞれ個別に new されたインスタンスを参照する形となる。 16 Container.RegisterType<MainWindowViewModel>(new ContainerControlledLifetimeManager()); 17 } 18 19 // ... 20 } 21}

PrismUserControl1ViewModel.cs

C#

1using Prism.Mvvm; 2 3namespace UserControlSample.ViewModels 4{ 5 public class PrismUserControl1ViewModel : BindableBase 6 { 7 // PrismUserControl1 でバインドして使えるようにアクセサを設ける。 8 public MainWindowViewModel MainWindowViewModel { get; set; } 9 10 // Visual Studio のデザイナのお怒りを鎮めるために、デフォルトコンストラクタは残しておいた方が良いかも。 11 public PrismUserControl1ViewModel() 12 { 13 } 14 15 // コンストラクタインジェクション用に MainWindowViewModel を引数に加える。 16 // これだけで DI コンテナが勝手に設定してくれる。 17 public UserControl1ViewModel(MainWindowViewModel mainWindowViewModel) 18 { 19 // アクセサに設定する。 20 MainWindowViewModel = mainWindowViewModel; 21 } 22 } 23}

MainWindow

C#

1<UserControl x:Class="UserControlSample.Views.PrismUserControl1" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:prism="http://prismlibrary.com/" 5 prism:ViewModelLocator.AutoWireViewModel="True"> 6 <!-- MainWindowViewModel アクセサ経由で CommandExecute をバインド! --> 7 <Button Width="50" Height="50" Command="{Binding MainWindowViewModel.CommandExecute}" Content="ボタン" /> 8</UserControl>

こういうことが簡単にできる。それが Prism の DI コンテナの真価であり神髄!
ViewModelLocator の意義はこの DI コンテナパワーを自然と ViewModel に届けることにあると私は考えている。

投稿2018/06/29 17:22

編集2018/06/29 17:27
toydev

総合スコア297

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/06/30 00:03

ご回答ありがとうございます。 ご提示いただいた方法で実装してみて動くことを確認しました。 ありがとうございます。 以下、実装していて2点疑問点がありました。 対応方法はなにかありますでしょうか? また、そもそも考え方が間違っている等ありましたら、ご指摘いただけないでしょうか。 1.最終的なアプリの形態としては、UserControlとFormを別のプロジェクト   として管理したいと考えています。そうなった際に   UserControlはMainWindowViewModelを知っている必要があるので   UserControlはFormのプロジェクトを参照する必要がある。   また、FormはForm上にUserControlを配置するのでUserControlを参照する   必要がある。   結果、循環参照となり参照ができませんでした。   Q.別プロジェクトの場合でもうまく動作させる方法はありますでしょうか? 2.UserControlは複数のFormから呼ばれることになる為   Bootstrapper.csにあるConfigureContainerに画面数分の(ViewModel数分の)   「Container.RegisterType<MainWindowViewModel>(new ContainerControlledLifetimeManager());」   を記述することになる?   そもそも、UserControlのXAMLのCommandに   「{Binding MainWindowViewModel.CommandExecute}」を書く必要があるので、複数の画面ViewModelを指定する事ができない?   Q.複数の画面(複数ViewModel)からでも動作させる事は可能でしょうか?
toydev

2018/06/30 01:43 編集

それぞれに回答します。 1. あります。一般的な循環参照の解決法を使えば良いです。 具体的な方法には複数の選択肢があります。 ・コマンド部分を別クラスに分けて MainWindowViewModel / PrismUserControl1ViewModel の両方に注入する方法。  コマンド部分のクラスを UserControl 側に実装すれば循環参照が消える。 ・コマンド部分をインターフェイスにし MainWindowViewModel にそのインターフェイスを実装させる方法。  PrismUserControlViewModel はそのインターフェイスのインスタンスを受け取る形にする。  コマンド部分のインターフェイスを UserControl プロジェクト側に実装すれば循環参照が消える。 また、関連としてもしかしたら Prism の Region や Module といった機能を必要としているのかもしれません。 2. Container.RegisterType1行の記述で複数の画面で動作します。 回答の本文で例示した Container.RegisterType の1文は、MainWindowViewModel をシングルトンとして扱えという命令です。 シングルトンという単語がソース上に出てこないのでぱっと見わかりづらいですが、 クラス名(ContainerControlledLifetimeManager)的には「MainWindowViewModel インスタンスの寿命を DI コンテナの寿命と一緒にする」という命令であり、 総じてその1つの MainWindowViewModel が全てのインジェクションで共有されるという動作になり、結果、シングルトンになるみたいな感じです。 DI コンテナ上だけの話なので、自分で個別に new したらシングルトンじゃなくなります。 そういう命令なので1行の記述で複数の画面にインジェクションできます。 > そもそも考え方が間違っている等ありましたら、ご指摘いただけないでしょうか。 なぜ「ViewModelを持つUserControl上にあるボタンを押下した際に、Bindingされたコマンドの内容をFormのViewModelに記述したい」と思っているのかという根本の部分が一番の疑問です。 技術的には可能ということで解決法を掲示しましたが、正直なところそこがまず間違っているのでは?という雰囲気を感じています。 前提を変えたら、こんなことしなくてももっと簡単な問題だったりするかもしれません。
退会済みユーザー

退会済みユーザー

2018/06/30 14:56

早速のご回答ありがとうございます。 まずは >なぜ「ViewModelを持つUserControl上にあるボタンを押下した際に、 >Bindingされたコマンドの内容をFormのViewModelに記述したい」と思っているのか について、回答させていただきます。 画像で提示させていただいている通り、複数画面にファンクションキー想定のボタンが あるようなアプリの開発を想定しています。 そして、このボタンのContentの値は各画面から、UserControlが持つ依存プロパティに 例えば画面IDを渡すことにより、UserControlのModelで、DBや定義ファイル(XML等)から 取得することを想定しています。 Contentの内容を外部に持たせたい理由は ・複数人で開発するプロジェクトで、一箇所にボタンのContentの内容を集約する事により各画面での  表記ゆれ、例えば、マスタメンテ画面のデータ登録時に人によってContentの内容を「保存」とする人も  いれば「登録」とする人もいる、というような事を防ぐ ・開発スキルの無い人間(例えば開発スキルのない設計者)でもボタンのContentの内容を  変更する事が可能になる ・Contentの内容を一括で変更したいとき、例えば、旧仕様で「保存」だったものを  新仕様で「登録」にしたい場合、各画面を修正せず、外部ファイルでの定義なら全画面分のContentの  修正を1ファイルを一括置換で対応可能になる こういった事を実現させたる事ができる為、UserControlにViewModelとModelを持たせて、そのModelに ボタンのContent取得処理を記述し、結果をViewModelに渡すのが良いのかなぁと考えています。 そして、実際のファンクションキー押下時の処理は各画面ごとに異なるので UserControl側ではなく、各画面側で記述できるようにしたい。といった事を考えています。
退会済みユーザー

退会済みユーザー

2018/06/30 14:57

>・コマンド部分を別クラスに分けて MainWindowViewModel / PrismUserControl1ViewModel の両方に注入する方法。   →ご教示いただいた内容で実装してみました。想定している挙動になりました。   質問に追記しています、こんな感じで認識あっていますでしょうか? >・コマンド部分をインターフェイスにし MainWindowViewModel にそのインターフェイスを実装させる方法。  →実装方法がわかりませんでした。   Container.RegisterTypeで型をInterfaceに指定すると   コンパイルは通りますが、実行時にInterfaceは指定できないと怒られてしまいます。   どういった記述をすればよろしいでしょうか?
退会済みユーザー

退会済みユーザー

2018/06/30 14:58

>2. Container.RegisterType1行の記述で複数の画面で動作します。  →ご説明いただいた内容理解できました。ありがとうございます。
toydev

2018/06/30 16:17

コメントでやりとり可能なレベルを超えてきたように思いますw なのでざっくり可能な範囲で回答しますね。 >>・コマンド部分を別クラスに分けて MainWindowViewModel / PrismUserControl1ViewModel の両方に注入する方法。 > →ご教示いただいた内容で実装してみました。想定している挙動になりました。 >  質問に追記しています、こんな感じで認識あっていますでしょうか? あっています。 >>・コマンド部分をインターフェイスにし MainWindowViewModel にそのインターフェイスを実装させる方法。 > →実装方法がわかりませんでした。 >  Container.RegisterTypeで型をInterfaceに指定すると >  コンパイルは通りますが、実行時にInterfaceは指定できないと怒られてしまいます。 >  どういった記述をすればよろしいでしょうか? ・インターフェイス IMainWindowViewModel を作り、MainWindowViewModel に実装する。 ・PrismUserControl1ViewModel のコンストラクタ・アクセサの型を IMainWindowViewModel にする。 ・DI コンテナに IMainWindowViewModel のインスタンスを登録する。具体的には以下のように書く。 Container.RegisterType<MainWindowViewModel>(new ContainerControlledLifetimeManager()); Container.RegisterInstance<IMainWIndowViewModel>(Container.Resolve<MainWindowViewModel>()); 色々聞いた感じ、このインターフェイスの方法は恐らく今回使いません。こんな方法もあると覚えておく程度でいいでしょう。 ---- これまでを踏まえてですが「Form(=MainWindow であってる?)のViewModel」である必要はないと思います。 Content を統一する目的であればそれ用の ViewModel をシングルトンとして作り、必要な ViewModel に注入すればいいです。 例えば ContentViewModel とかの名前で作ります。MainWindowViewModel より名前も明確でわかりやすいです。 今回 FunctionCommand を作った方法と同じですから応用でしたいことが自由にできるでしょう。 MainWindow でも使用するのであれば MainWindow にも同じものを注入すれば良いと思います。 他にも横断的にまとめたいものがあれば専用の ViewModel にわかりやすい名前を付けて作り、注入すれば良いでしょう。 最後に一個突っ込みをいれたいとすれば Content の文字の統一といったら、Resources に定義したものを使うのが普通な気が…というぐらいですかねぇ…。 ・参考:Resourcesに定義した文字列を XAML で使う方法(https://tnakamura.hatenablog.com/entry/20100928/xaml_properties_resources)
退会済みユーザー

退会済みユーザー

2018/07/01 01:07

色々とご教示ありがとうございました。 まだインターフェイスでの実装はできてないので、これからやってみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問