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

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

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

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

MVVM

MVVM(Model View ViewModel)は構築上のデザインパターンで、表現ロジック(ViewModel)によってデータ(Model)からページ(View)を分離させます。

XAML

XAML(Extensible Application Markup Language)はWPF、Silverlight、Windows PhoneそしてWindows Store appsでユーザーインターフェースを定義するために使われるXML言語です。

WPF

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

Q&A

解決済

3回答

2936閲覧

PrismでViewを動的ロード

yamaj

総合スコア1

C#

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

MVVM

MVVM(Model View ViewModel)は構築上のデザインパターンで、表現ロジック(ViewModel)によってデータ(Model)からページ(View)を分離させます。

XAML

XAML(Extensible Application Markup Language)はWPF、Silverlight、Windows PhoneそしてWindows Store appsでユーザーインターフェースを定義するために使われるXML言語です。

WPF

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

1グッド

1クリップ

投稿2021/05/13 01:58

前提・実現したいこと

WPFでPrismを使用したプログラムを作成しようとしています。

やりたいことは、動作等が全く同じで見た目だけが違うViewを動的にロードして切り替えたいというものです。

今までは同様のプログラムを作成する際、MVVMを使用せず動的にロードしたユーザーコントロールを貼り付けていました。(動作はベースクラスで記述)

始めたばかりで理解度が浅く、どうしていいのか途方に暮れている状態です。

試したこと

とりあえず、やってみてダメだったものをいかに記します。

1.View1プロジェクトを作成し、View1というViewを作成する。

2.App.xaml.csのRegisterTypesでDLLを読み込んでDIに登録する。

C#

1var oAssembly = Assembly.LoadFrom(@".\View1.dll"); 2var oClass = oAssembly.CreateInstance("View1.View1"); 3containerRegistry.RegisterInstance(oClass);

3.で?

という感じです。
実際には、ViewModelのプロジェクトを作成してViewで参照し、assembly付きでDataContextを指定したり、MainWindowViewModel内でRequestNavigateでView1を指定してみたり、DIにViewModelも登録してみたりと、思いつく限りはしてみたのですが、思うようにいきません。

本当に見た目しか違わないので、VMの使いまわし方も含めてご教授いただければと思います。

補足情報(FW/ツールのバージョンなど)

VisualStudio 2019 (V16.9.4)
Prism V8

TN8001👍を押しています

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

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

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

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

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

yamaj

2021/05/14 00:13

説明が稚拙で申し訳ありません。 どうやったら動的にロードしたViewをリージョンマネージャーでナビゲートできるかという質問でした。
hihijiji

2021/05/14 02:33

本当にやりたいこと(目的)はなんですか? ページの外観を動的に変えたいってことですか? 少なくともViewを動的にロード云々は手段であって目的ではないはずです。 まずやるべきは少ない選択肢(知識)の中で手段を考えるのではなく、選択肢(知識)を増やすことです。 WPF + Prism で検索すればサンプルはいくらでも転がってますから、サンプルをたくさん見て選択肢を増やすのが近道です。
yamaj

2021/05/14 03:58

ん~。 Viewの切り替えが主目的ですね。 デバイスエミュレーターみたいなもので、キー数や表示桁数は一緒で配置やサイズが違うフェイスをプラグイン的に切り替えたいので。 なので、VMは使いまわしたい、Viewは動的にロードしたいというのが目的です。 ちなみにViewModelを別プロジェクトへ外出し(XAMLでAssembly指定のDataContext設定)もDI注入部がエラーを吐いて失敗してます。 ※引数なしのコンストラクタがないエラー
hihijiji

2021/05/14 04:36

そもそもViewは動的に割り当てるのが普通なので、基礎さえ身についていれば 悩むところじゃないんですが… つまずくとしたら、 prism:ViewModelLocator.AutoWireViewModel="True" の所のViewの名前からViewModelを引き当てるところぐらい。 これを活かそうとするなら、約束通りの名前のViewModelを作って そこに本来のViewModelの型のプロパティHogeHogeViewModelを1つだけ置きView冒頭で DataContext="{Binding HogeHogeViewModel}" と指定するとか
yamaj

2021/05/14 07:49 編集

引数なしのコンストラクタの場合はおっしゃる通りで実行できます。 ただ、注入を期待して以下のように書いてると…。 Project:ViewProcess public class ProcViewModel : BindableBase {   public ProcViewModel(IDataProvider dp)   {   } } View側のXAMLで以下のように書いても…。 <UserControl x:Class="View1.View1"   ...   xmlns:vm="clr-namespace:ViewProcess;assembly=ViewProcess"   prism:ViewModelLocator.AutoWireViewModel="True">   <UserControl.DataContext>    <vm:ProcViewModel />   </UserControl.DataContext> </UserControl> 「型 'ProcViewModel' はパブリックでないか、パラメーターなしのパブリック コンストラクターまたは型コンバーターを定義していないため、オブジェクト要素として使用できません。」 となってしまいます。 これではDI注入の恩恵を受けることができないのです。 まぁ、同プロジェクトのVMは普通に自動割付されるので、全ての処理を書いたベースクラスを継承すればやりたいことはできそうですが…。
hihijiji

2021/05/14 08:02

<UserControl.DataContext>    <vm:ProcViewModel />   </UserControl.DataContext> この書き方だと自分でProcViewModelのインスタンスをつくってますから そりゃ無理です。 AutoWireViewModelの恩恵を受けるためには約束の名前空間に約束の名前のViewModelが 必要です。 で↑↑のアドバイス
yamaj

2021/05/14 08:34

ご指摘の通りですね。 とりあえず、「ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver」で強制的にAssembly書き換えたら別DLLのVMまで到達しました。 ただ、これをやっちゃっていいものかどうか…。 やはり無難にベースクラス作ったほうが後々「何やってるか分からない!」って事にはならなさそうな気がしてきました。
hihijiji

2021/05/14 09:33

だからView1ViewModelにProcViewModelを置けば簡単に解決するんだって View2ViewModel、View3ViewModelって増えてもProcViewModelは共用するには ProcViewModelをDIに放り込むときシングルトンにすればOK
yamaj

2021/05/17 00:13

あ、これも説明不足かも…。 プラグインのように使いたいので、各DLLにはViewが1つだけ入っているのが最高の理想で、ProcViewModelのインスタンスはView毎に生成されても全く問題はありません。 (処理自体はModelでやるので) って、またトンチンカンな事書いてますかね?
hihijiji

2021/05/17 01:05

> プラグインのように使いたい ようやく本当にやりたいことが出てきましたね。 でもそれって、レイアウトやスタイルだけをリソースにしたら良くない?
yamaj

2021/05/17 01:46

昔(えとひとまわりくらい)は、わざわざデザインツールを作って画像やら座標やらをXMLでパックして…なんてやってましたが、1回作ればほぼ触らないですし、新しいのを作る場合は機能アップがある場合もあるのでロジック側と一緒に画面も作ってしまえばせっかくある純正デザイナーが使えますし、他の人に渡すにも取説つくるより「ソース見ろ」で済みますし。ね?
hihijiji

2021/05/17 10:28

状況はなんとなくわかりましたが、それゆえアドバイス出来ることはなにもないです。m(_ _)m 自分はアプリケーションの仕様書だけはしっかり書いてますが、汎用DLL以外はXMLドキュメントすらお座なりです。
yamaj

2021/05/17 23:45

色々とお付き合い頂きありがとうございました。 仕様書を書かない自分が悪いんですが、時間が経つと自分でも何をしてたかわからなくなるんですよねw 周りにまともに相談できる人がいないので、とても有り難く思いました。 また何かあればよろしくお願いいたします。
guest

回答3

0

個人的にはAutoWireしなければ親のDataContextを引き継ぎますから、どうにでもコントロールできていいと思うのですが(Prism的にはいまいちなんですかね)

あとd:DataContextにデザイン用VMなりVMでデザイン時を判断しデータを入れるようにしておくと気が利くなって思います。

回答の私のコメントを回収しておきます。
VMの条件によってはAutoWireしてもいいと思います。
SetDefaultViewTypeToViewModelTypeResolverが面倒だったら、本体のViewAViewModelをカラ継承しておいておくとか?^^;

本体はプラグインのことを知らないが、プラグインは本体のことを知っていていいんですよね?

本体側

xml

1<Window 2 x:Class="BlankCoreApp1.Views.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:ViewModels="clr-namespace:BlankCoreApp1.ViewModels" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 xmlns:prism="http://prismlibrary.com/" 9 Width="525" 10 Height="350" 11 d:DataContext="{d:DesignInstance {x:Type ViewModels:MainWindowViewModel}}" 12 prism:ViewModelLocator.AutoWireViewModel="True" 13 mc:Ignorable="d"> 14 <Grid> 15 <GroupBox Header="ViewA"> 16 <ContentControl prism:RegionManager.RegionName="ContentRegion" DataContext="{Binding ViewAViewModel}" /> 17 </GroupBox> 18 </Grid> 19</Window>

xml

1<UserControl 2 x:Class="BlankCoreApp1.Views.DefaultViewA" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:ViewModels="clr-namespace:BlankCoreApp1.ViewModels" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 d:DataContext="{d:DesignInstance {x:Type ViewModels:ViewAViewModel}, IsDesignTimeCreatable=True}" 9 mc:Ignorable="d"> 10 <DockPanel> 11 <TextBlock DockPanel.Dock="Top" Text="BlankCoreApp1.Views.DefaultViewA" /> 12 <TextBlock Text="{Binding Message}" /> 13 </DockPanel> 14</UserControl>

cs

1using BlankCoreApp1.Views; 2using Prism.Mvvm; 3using Prism.Regions; 4 5namespace BlankCoreApp1.ViewModels 6{ 7 public class MainWindowViewModel : BindableBase 8 { 9 // 本題でないので直接newするが好きにDIしてください 10 public ViewAViewModel ViewAViewModel { get; } = new ViewAViewModel(); 11 12 public MainWindowViewModel() { } 13 14 public MainWindowViewModel(IRegionManager regionManager) 15 { 16 // CustomViewがなければデフォルトを表示 17 regionManager.RegisterViewWithRegion("ContentRegion", typeof(DefaultViewA)); 18 } 19 } 20 21 public class ViewAViewModel : BindableBase 22 { 23 private string _message; 24 public string Message { get => _message; set => SetProperty(ref _message, value); } 25 26 public ViewAViewModel() => Message = $"{GetType().FullName}"; 27 } 28}

cs

1using BlankCoreApp1.Views; 2using Prism.Ioc; 3using Prism.Modularity; 4using System.Windows; 5 6namespace BlankCoreApp1 7{ 8 public partial class App 9 { 10 protected override Window CreateShell() => Container.Resolve<MainWindow>(); 11 12 protected override IModuleCatalog CreateModuleCatalog() 13 { 14 // 指定フォルダからモジュール読み込み 15 return new DirectoryModuleCatalog() { ModulePath = @".\Modules" }; 16 } 17 18 protected override void RegisterTypes(IContainerRegistry containerRegistry) { } 19 } 20}

プラグイン側

xml

1<UserControl 2 x:Class="CustomViewAModule.Views.ViewA" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:ViewModels="clr-namespace:BlankCoreApp1.ViewModels;assembly=BlankCoreApp1" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 d:DataContext="{d:DesignInstance {x:Type ViewModels:ViewAViewModel}, IsDesignTimeCreatable=True}" 9 mc:Ignorable="d"> 10 <DockPanel> 11 <TextBlock DockPanel.Dock="Top" Text="CustomViewAModule.Views.ViewA" /> 12 <TextBlock Text="{Binding Message}" /> 13 </DockPanel> 14</UserControl>

cs

1using CustomViewAModule.Views; 2using Prism.Ioc; 3using Prism.Modularity; 4using Prism.Regions; 5 6namespace CustomViewAModule 7{ 8 public class CustomViewAModuleModule : IModule 9 { 10 public void OnInitialized(IContainerProvider containerProvider) 11 { 12 var regionManager = containerProvider.Resolve<IRegionManager>(); 13 14 // CustomViewで上書きナビゲート 15 regionManager.RequestNavigate("ContentRegion", "ViewA"); 16 17 // こちらが後に呼ばれているのにうまく出なかった 18 //regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA)); 19 } 20 21 public void RegisterTypes(IContainerRegistry containerRegistry) 22 => containerRegistry.RegisterForNavigation<ViewA>(); 23 } 24}

アプリ画像

投稿2021/05/17 10:30

編集2023/07/27 13:53
TN8001

総合スコア9862

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

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

0

どうやったら動的にロードしたViewをリージョンマネージャーでナビゲートできるかという質問でした。

モジュールについて調べていただけたのか甚だ疑問ですが、どうしてもCreateInstanceしたいということでしょうか?
モジュールにはその辺の機能もありますしロード方法も選べるのですが。

何か事情があるとして(特別な事情は質問に書いてください)提示の方法でやってみます。
Prismはコードが膨れがちでお互い説明や再現するのも大変なので、テンプレートをベースにします。

入っていると思いますがPrism Template PackPrism Full App(.NET Core)で、新規にソリューションを作ります。

テストを含めてプロジェクト6つの、ゴツめのソリューションが生成されます。
すでにモジュールを使うようになっていますが、あえて使わないようにします。

  1. FullApp1をスタートアッププロジェクトにして実行し、どのようなウィンドウが出るのか確認する
  2. FullApp1.Modules.ModuleName.ModuleNameModuleを削除なりコメント化する
  3. ModuleNameModuleの使用箇所、FullApp1.App.ConfigureModuleCatalogを削除なりコメント化する
  4. 一度ソリューションをリビルドする
  5. FullApp1プロジェクトから、FullApp1.Modules.ModuleNameの参照を切る
  6. FullApp1.Modules.ModuleName.dllを、出力ディレクトリを変えるなり手でコピーするなりでFullApp1.exeと同じフォルダに配置する
  7. FullApp1.Appを下記に書き換える

cs

1using FullApp1.Core; 2using FullApp1.Services; 3using FullApp1.Services.Interfaces; 4using FullApp1.Views; 5using Prism.Ioc; 6using Prism.Regions; 7using System.Reflection; 8using System.Windows; 9 10namespace FullApp1 11{ 12 public partial class App 13 { 14 protected override Window CreateShell() 15 { 16 return Container.Resolve<MainWindow>(); 17 } 18 19 protected override void RegisterTypes(IContainerRegistry containerRegistry) 20 { 21 containerRegistry.RegisterSingleton<IMessageService, MessageService>(); 22 23 var oAssembly = Assembly.LoadFrom(@".\FullApp1.Modules.ModuleName.dll"); 24 var oClass = oAssembly.CreateInstance("FullApp1.Modules.ModuleName.Views.ViewA"); 25 containerRegistry.RegisterInstance(oClass, "ViewA"); 26 } 27 28 //protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) 29 //{ 30 // moduleCatalog.AddModule<ModuleNameModule>(); 31 //} 32 33 protected override void OnInitialized() 34 { 35 base.OnInitialized(); 36 37 var regionManager = Container.Resolve<IRegionManager>(); 38 regionManager.RequestNavigate(RegionNames.ContentRegion, "ViewA"); 39 } 40 } 41}

では実行します。初めに実行したときと同じになったでしょうか?

RequestNavigateのタイミングが難しいというか、早すぎると出ないことはありました(よくわかっていません^^;

VMの使いまわし方

それこそRegisterInstanceRegisterSingleton等、好きに登録したらいいんじゃないですか?

投稿2021/05/14 09:59

編集2023/07/27 13:55
TN8001

総合スコア9862

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

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

yamaj

2021/05/17 00:19 編集

すいません、動的ロードについては、休み中にこちらを見る前に解決できてしまいました。 単に以下の通りにすればよかったみたいです。 Assembly oAssembly = Assembly.LoadFrom(@".\View1.dll"); rgnMan.RegisterViewWithRegion("ContentRegion", oAssembly.GetType("View1.View1")); 切り替える際には、一旦リージョンから登録したものを削除して再登録してやればいけました。
yamaj

2021/05/17 01:27

教えていただいたやり方をやってみました。 何分Prism Full Appプロジェクトを作成したのが初めてなもので、まずは生のテンプレートを調べるところから始めないとダメだなと感じました。 モジュールについての指摘は、実は未だにピンときていませんが、モジュール側で個別に初期化処理を走らせる事ができる点についての指摘かなと思っています。(間違っていたらご指摘ください) 今回は、最初の質問で「なんとかしてDIに登録しなければ」という思い込みから始まったもので、着地点が全く違うところ(MainWindowViewModelでRegisterViewWithRegionをする)に自分の中ではなりそうです。 「そんなのは作法とは違う」というご指摘などがあれば是非お願いします。
TN8001

2021/05/17 01:56

まず私はPrismに関してteratailの回答でちょろっと触る程度で1回もまともには使っていませんのでyamajさんと大差ないです。 指摘を入れるほど知りませんし、私のほうが間違っているかもしてません^^; > 動的ロードについては、休み中にこちらを見る前に解決できてしまいました。 じゃああとは何が問題なんですか? ViewとViewModelのアセンブリを分ける方法? 追記依頼のコメントを整理して質問に追記してください。 > 強制的にAssembly書き換えたら別DLLのVMまで到達しました。 [PrismでViewとViewModelのアセンブリを分けたい場合 - nuits.jp blog](https://www.nuits.jp/entry/2016/11/04/093826 これですよね。 > ただ、これをやっちゃっていいものかどうか…。 もともとがリフレクションなんだし自分がわかっていれば別にいい気がしますけど、引っかかってる点「…」を書いてもらわないと伝わりませんよ。 > モジュールについての指摘は、実は未だにピンときていませんが、 指定フォルダをまとめて読むとか読み込むタイミング、(モジュール内に書いていれば)DI登録等が楽でしょうってことが言いたかったのです。 > プラグインのように使いたいので、各DLLにはViewが1つだけ入っているのが最高の理想 > 他の人に渡すにも取説つくるより「ソース見ろ」で済みますし。 どこまでがyamajさんの管理下なんでしょう? 他人がViewを作る想定なんですか?
yamaj

2021/05/17 02:59

とりあえず実現方法は見えてきたので、解決方法を纏めて質問を閉じてもいいんですが、何分一人でやっているもので周りに相談できる人がいないんですよね。 なので、色んな人の意見(それこそお作法なんか)が聞ければいいなとは思っています。 >> 強制的にAssembly書き換えたら… これは、元々ViewとかViewModelというフォルダが鬱陶しくてネットで検索したものに置き換えていたので、デバッガで止めて文字列イジっての確認ですね。(元ネタ紛失) ロジカルなことは一切考えてなかったので、「リテラルで置き換えちゃっていいのかなー」という意味での「やっちゃっていいのかどうか」でした。 管理的な事で言えば「孤独な一人部署」状態ですかねー。 ものを作る時は好き勝手できますが、後々面倒なので、「あとから手を加えそうな部分は分離したい」とか「手を加える範囲は最小限にしたい」とか「一段落したら丸投げしたい」とか、そういった部分で一般的なお作法や考え方が知りたいのです。 プロジェクトテンプレートでも作っておいて、ある程度簡単に(それこそXAML以外はノーコードで)画面が追加できるというのが理想ですね。
TN8001

2021/05/17 04:01

「今は全部自分の管理下だが、自分の手が離れたときに混乱させたくない」 ということですか。 > 色んな人の意見(それこそお作法なんか)が聞ければいいなとは思っています。 hihijijiさんはなにか回答いただけるかもしれませんが、多様なというのは難しいかもしれません。 * WPFネタはアクティブな人があまりいない * teratailの文化的に(必要十分と見られれば)あまり回答をかぶせない * 意見募集を押し出すと「問題・課題が含まれていない質問」と言われてしまう すぐ閉めろという気は全くありませんので、yamajさんが納得した段階で自己回答でも(私は)全く問題ありません。 > プロジェクトテンプレートでも作っておいて、ある程度簡単に(それこそXAML以外はノーコードで)画面が追加できるというのが理想ですね。 これはView(プラグイン)の話ですよね? 個人的にはAutoWireしなければ親のDataContextを引き継ぎますから、どうにでもコントロールできていいと思うのですが(Prism的にはいまいちなんですかね) あとd:DataContextにデザイン用VMなりVMでデザイン時を判断しデータを入れるようにしておくと気が利くなって思います。
yamaj

2021/05/18 00:13

色々とお付き合い頂きありがとうございました。 どちらかというと、物忘れが激しい上に仕様書を残さないので未来の自分の為にやってる感が強いかもしれません。 面倒な処理はベースクラスなんかで隠蔽したり、プロジェクトテンプレートを作ったりして、できるだけ後が楽なように作ろうとしています。 まぁ、結局毎回色んな所をいじりまわしてて隠蔽が意味なかったりするんですけどね。 今回、初めてteratailを使ったのでこの世界の常識もまだ分かっていませんが、少し揉まれながらやってみたいと思います。 質問を閉めるのは、TN8001さんから頂いたもう一つの回答を検証後にしたいと思いますが、ちょっと他に対応しなければいけない事が出てきてしまいすぐには手を付けられない状態になってしまいました。 今回は色々教えて頂き、本当にありがとうございました。
guest

0

自己解決

色々ご意見を頂きましたが、今回は以下のコードで対応しました。

C#

1Assembly oAssembly = Assembly.LoadFrom(@".\View1.dll"); 2rgnMan.RegisterViewWithRegion("ContentRegion", oAssembly.GetType("View1.View1"));

また、VMの使いまわしについてはTN8001さんの回答を元にもう少し考えてみます。
(理解が追いついていない部分があるため)

投稿2021/06/04 05:29

yamaj

総合スコア1

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問