検索条件
前のページ
1
2
3
次のページ
Homeモジュールは一旦削除し、TreasurerHelper.Homeを新たに作成。
作成されたモデル等のモジュール名が「HomeModule.cs」のままだったのでCashCalculatorはTreasurerHelper.CashCalculatorに名前を変更。
デバッグでエラー潰しながらビルド成功すると作成されたdllはCashCalculator.dllのまま。
プロジェクトのプロパティでアセンブリ名を変更する必要ありました。
Homeモジュール上にHomeモジュールへのリンクは不要なので、メニュー一つだけでは寂しいのでもう一つ出納帳モジュール追加。
デザインそのままいただきました。それでも画像や余白のせいでしょうか、デザイナーさん入っていない感があります(^^;)。
自分のメニュー用の画像は自分のプロジェクトに保存したい。
pack://~というMaterialDesignを参照する時によく出てくるやり方で表示できました。
menuService.AddMainMenuItem(new MenuItem { Title = "現金計算", IconName = "Calculator", Description = "紙幣・硬貨毎の枚数入力",
NavigatePath = "TreasurerHelper.CashCalculator.Views.CashCalculator",
DisplayOnHome = true,
ImageSource = "pack://application:,,,/TreasurerHelper.CashCalculator;component/Resources/CashCalculator.jpg"
});
binフォルダの中ではモジュールのdllは同じフォルダ内になるしResourcesのフォルダ名や画像ファイル名が被ったらどうなるのか心配したら、出力されたファイルに.jpgファイルは見つかりません。
それでもexeを実行するとちゃんと画像が表示されます。
プロジェクトに画像を追加した際、デフォルトでファイルの「ビルドアクション:リソース、出力ディレクトリにコピー:コピーしない」になっているためのようです。
ファイルをコピーし忘れる心配がないのはいいですが、「もっといい画像あるからこれ使って」とか言われたらビルドし直す必要が出てきます。
「ビルドアクション:なし、出力ディレクトリにコピー:新しい場合はコピーする」に変えたらちゃんと出力されました。
ところが画像へのパスの指定方法が分からず、一旦元に戻しました。
ホーム画面にホームモジュールの表示は不要ですが、ドロワーには必要。MenuItemのリストは一つでHomeモジュールのViewModelでLinqで省いて渡せばいいわ、と思ったところLinq使ったとたんHomeでのメニューが消える…。
調査は後回しにし、引いても駄目なら押して見る。
public void AddMainMenuItem(MenuItem menuItem)
{
MainMenuItems.Add(menuItem);
if (menuItem.DisplayOnHome)
{
HomeMenuItems.Add(menuItem);
}
}
色々心配しましたが、Viewがモジュール間にまたがっていても通常の入れ子になったViewと違いは無い。親のViewのViewModelが継承されている。
通常のバインド
Command="{Binding NavigateCommand}"
Command="{Binding Path=DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
他にも書き方あるかも知れませんが一番てっとりばやそうなので
Command="{Binding Path=DataContext.NavigateCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
またまたいてまえ系コーディング。やってみたら動いたよ…。
HomeModuleのViewAのViewModelで。
public ViewAViewModel(IMenuService menuService)
{
MainMenuItems = menuService.GetMainMenuItems();
Message = "Home Module";
}
デバッグしてここで待ち受けているとMainMenuItemsの中は空(HomeModuleのOnInitializedよりも先にこちらを通る)だったのですが、実行するとメニューが表示されました。
Cardで包んでボタンをAccentButtonに変えたらグッと良くなるはず、と思ってましたがいまいち垢抜けません…。
WrapPanelがWrapしてくれず苦労しました。MenuItemを拡張してグリッドの位置をモデルから渡そうかと思ったくらい。
<Grid>
<ItemsControl ItemsSource="{Binding MainMenuItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<materialDesign:Card Margin="12" Padding="16" Width="280">
<StackPanel>
<TextBlock Style="{DynamicResource MaterialDesignTitleTextBlock}" Text="{Binding Title}"></TextBlock>
<TextBlock Text="{Binding Description}" Margin="10 8 0 0"></TextBlock>
<Button
Margin="12 16 12 0"
Style="{StaticResource MaterialDesignRaisedAccentButton}"
Command="{Binding Path=DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding NavigatePath}">
<TextBlock Margin="6 0 0 0" Style="{StaticResource MaterialDesignBody1TextBlock}"
Text="OPEN" />
</Button>
</StackPanel>
</materialDesign:Card>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
発行方法は3種類あるようですが、一番簡単そうなXCOPYでいくことに。
ところがその方法を書いてくれていない。
普通に「発行」するとexeが出来ずにインストーラーが出来てました。
ビルドをリリースに変えてbinフォルダの中にできたものをデスクトップに移動しexeをクリックしたらちゃんと動きました。
現在は親からモジュールへ参照を張っているのでモジュールのdllも一緒にリリースビルドに含まれています。
dllの並び見ているとモジュールのプロジェクト名を変えたくなってきて一旦終了。
MenuServiceを作成し、ナビゲーション用Viewの重複を無くします。
見た目、機能は5日目 とまったく変わりません。
リファクタリング版です。
通常のクラスライブラリテンプレートを使用。
メニューモジュールを作る、という方法も考えられますが、Viewの重複を省きたい、という単純な要件なので、共通機能の一つとして作成。
MenuServiceとはまったく関係ありませんが、しょうもないTYPOで時間を潰さないように。
MainNavigationRegionが無くなり一つだけになりますが、また増える可能性もあるので。
public static class RegionNames
{
public const String ContentRegion = "ContentRegion";
}
最低限の項目。サブメニュー追加したり、ロール認証使う場合はここに追加。
public class MenuItem
{
public string Title { get; set; }
public string IconName { get; set; }
public string Description { get; set; }
public string NavigatePath { get; set; }
}
public interface IMenuService
{
List<MenuItem> GetMainMenuItems();
void AddMainMenuItem(MenuItem menuItem);
}
public class MenuService : IMenuService
{
public MenuService()
{
MainMenuItems = new List<MenuItem>();
}
List<MenuItem> MainMenuItems { get; set; }
public List<MenuItem> GetMainMenuItems()
{
return MainMenuItems;
}
public void AddMainMenuItem(MenuItem menuItem)
{
MainMenuItems.Add(menuItem);
}
}
メニューサービスは完成。後は親アプリとモジュール間でサービスを共有させる方法をどうするか。
MenuServiceに登録する場所はドキュメントにあります 。実際のサンプルコードはありませんが。
このあたりから感を頼りにいてまえコーディングです。
containerProviderがIRegionManagerをResolveできるのならIMenuServiceもResolveできるでは?と言う感じ(^^;)。
NavigatePathはモジュール外から呼ばれることになるので元のCashCalculatorではまずいのは分かるけど、よく分からないのでとりあえずフルネーム(CashCalculator.Views.CashCalculator)にしてみた。
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(Views.CashCalculator));
var menuService = containerProvider.Resolve<IMenuService>();
menuService.AddMainMenuItem(new MenuItem { Title = "現金計算", IconName = "Calculator", Description = "紙幣・硬貨毎の枚数入力", NavigatePath = "CashCalculator.Views.CashCalculator" });
}
親側がIMenuServiceを関知しなかったらモジュール側がいくらResolveしようとしても無理なはず。どこかに登録する必要あるはず。
MainWindow.xaml.csは無い。MainWindowViewModel.csかApp.xaml.csの2択のはず。
なんとなく元Bootstrapper.csのApp.xaml.csっぽい。
App.xaml.csを見ると中身の無いRegisterTypesが用意されています。
containerRegistryでポチするとインテリセンスでRegisterが出てきたので設定。
(実は動かないバージョン)
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<IMenuService, MenuService>();
}
public DelegateCommand<string> NavigateCommand { get; private set; }
// 上の真似をしてViewに渡すデータの器を用意
public List<MenuItem> MainMenuItems { get; set; }
// IRegionManagerがパラメータとして受け取られていたので、真似してIMenuServiceを追加。エラー出ず!
public MainWindowViewModel(IRegionManager regionManager, IMenuService menuService)
{
_regionManager = regionManager;
// サービスからデータ取得
MainMenuItems = menuService.GetMainMenuItems();
NavigateCommand = new DelegateCommand<string>(Navigate);
}
モジュール毎に書いていたボタンをテンプレート化する。
(ボタンが動作しないバージョン)
<ItemsControl ItemsSource="{Binding MainMenuItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button HorizontalAlignment="Left"
Style="{DynamicResource MaterialDesignFlatButton}"
Command="{Binding NavigateCommand}" CommandParameter="{Binding NavigatePath}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon x:Name="PackIcon" Kind="{Binding IconName}" />
<TextBlock Margin="6 0 0 0" Style="{StaticResource MaterialDesignBody1TextBlock}"
Text="{Binding Title}" />
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
なんとエラーは出ず画面がちゃんと立ち上がる(^^;)。
ただしメニューは空。MainWindowが先にNewされメニューを読み込んだ後でモジュール側で追加しても遅いのか?と思いつつデバッグしてみると、Homeモジュールで追加したメニューがCashCalculatorモジュールで追加する際には消え、CashCalculatorのメニューだけが追加される。
MenuServiceが毎回Newされている感じ。
えーっシングルトンってどうやって書くんだっけ?と調べかけた時、以前にRegisterSingletonというメソッドをどこかで見かけて記憶がよみがえりcontainerRegistryのインテリセンスの中を探したらRegisterSingletonが見つかりました!(本当はサービス側をシングルトンにしておいた方がいいのかも知れませんが)
下記のように変えて無事アイコン付きのメニューが表示される。
予想以上に順調でちょっと感動。
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<IMenuService, MenuService>();
}
残るはボタンを押した際の画面遷移。
一番苦労したのがここ。下記では動かないことは他のMVVM系フレームワークの経験から分かる。NavigateCommandはここには無く親のスコープにあるので。
Command="{Binding NavigateCommand}"
Parentを付けるとかもっとシンプルな答えを探したのですが、予想以上に複雑な書き方が見つかり試行錯誤の結果動いたのが下記の書き方。
ダサい。もう少し何とかならないかと思いますが、NavigateCommandのブレークポイントを通った時の感動はひとしお。
NavigatePathの書き方も間違っていなかったようで、ちゃんとViewが切り替わりました。
Command="{Binding Path=DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
起動時はHomeモジュール表示
ハンバーガートグルボタンでメインメニュー表示
ドロワー上のボタンをクリックしてモジュール切り替え
Prism6版 を確認したら、NavigateCommandを親プロジェクトの既存のViewModelに移動しても問題なく動いたとあった(記憶には無かった)のでモジュール側のメニューはViewだけを残して他はバッサリ削除。
「動かん!?」と思ったらNavigate先のRegion名を間違えてました(Typo)。
エラー出してよ、と思いました。
Viewもテンプレート化すれば重複減らせそうですが、それは次の課題。
デスクトップアプリでメインメニューにDrawer使うとか他人様にやられると「はぁ?」という感じですが、ものは試しで(^^;)。
setの見慣れない書き方はよく分かってませんが、Titleの真似で。
private bool _mainMenuIsOpen = false;
public bool MainMenuIsOpen
{
get { return _mainMenuIsOpen; }
set { SetProperty(ref _mainMenuIsOpen, value); }
}
2箇所あるので要注意
<materialDesign:DrawerHost IsLeftDrawerOpen="{Binding ElementName=MenuToggleButton, Path=IsChecked}">
<materialDesign:DrawerHost.LeftDrawerContent>
<DockPanel MinWidth="212">
<ToggleButton Style="{StaticResource MaterialDesignHamburgerToggleButton}"
DockPanel.Dock="Top"
HorizontalAlignment="Right" Margin="16"
IsChecked="{Binding MainMenuIsOpen}" />
<StackPanel Margin="0">
<ItemsControl x:Name="NavigationItemsControl" prism:RegionManager.RegionName="MainNavigationRegion" Margin="0" Padding="0" />
</StackPanel>
</DockPanel>
</materialDesign:DrawerHost.LeftDrawerContent>
<DockPanel>
<materialDesign:ColorZone Padding="16" materialDesign:ShadowAssist.ShadowDepth="Depth2"
Mode="PrimaryMid" DockPanel.Dock="Top">
<DockPanel>
<ToggleButton Style="{StaticResource MaterialDesignHamburgerToggleButton}" IsChecked="{Binding MainMenuIsOpen}"
x:Name="MenuToggleButton"/>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="22" Text="{Binding Title}"></TextBlock>
</DockPanel>
</materialDesign:ColorZone>
・・・・
private void Navigate(string navigatePath)
{
MainMenuIsOpen = false;
if (navigatePath != null)
_regionManager.RequestNavigate("ContentRegion", navigatePath);
}
モジュール間の切り替えを行うメインのナビゲーション
Gridで画面を左右に分割し、左側にMainNavigationRegionを追加
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Grid.Row="2" MinWidth="100" Margin="5,0,0,5">
<ItemsControl x:Name="NavigationItemsControl" prism:RegionManager.RegionName="MainNavigationRegion" Grid.Column="0" Margin="5" Padding="5" />
</Border>
<ContentControl prism:RegionManager.RegionName="ContentRegion"
Grid.Column="1" Grid.Row="2" Margin="5,8,5,5" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
</Grid>
Prism UserControlのテンプレート使用
<Button x:Name="Root" ToolTip="紙幣・硬貨毎の枚数入力" ToolTipService.InitialShowDelay="0"
Style="{DynamicResource MaterialDesignFlatButton}"
Click="Button_Click" >
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon x:Name="PackIcon" Kind="Calculator" />
<TextBlock Margin="6 0 0 0" Style="{StaticResource MaterialDesignBody1TextBlock}"
Text="現金計算" />
</StackPanel>
</Button>
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ContentRegion", typeof(Views.CashCalculator));
regionManager.RegisterViewWithRegion("MainNavigationRegion",typeof(Views.CashCalculatorNavigationItemView));
}
View Injection とView Activation/Deactivation の合わせ技
IContainerExtension _container;
IRegionManager _regionManager;
public CashCalculatorNavigationItemView(IContainerExtension container, IRegionManager regionManager)
{
InitializeComponent();
_container = container;
_regionManager = regionManager;
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
var view = _container.Resolve<CashCalculator>();
IRegion region = _regionManager.Regions["ContentRegion"];
region.Add(view);
region.Activate(view);
}
ナビゲーションがちゃんと動作しているか確認するためにもう一つModuleを作成しましたが、同じような記述を繰り返すことになり、ちょっとダサい。
何とかする前に動いた状態のものを一旦コミット 。
前のページ
1
2
3
次のページ
WPF WPF WPF WPF WPF WPF WPF WPF WPF WPF WPF WPF