WPF、MVVM、およびメニューの前景色
-
06-07-2019 - |
質問
WPFとMVVMの両方が初めてです。 MVVM partternでメニューを動的に作成する良い方法を探しましたが、自分の好みに合ったものが見つからないので、独自のソリューションを展開しました。動作しますが、何らかの理由でメニューの前景(テキスト)の色が時々(ちょうど)正しくありません。
下の画像へのリンクを追加しました。
http://img220.imageshack.us/img220/1912/ badmenu.jpg (デッドリンク)
一番下のサブメニューは白い前景で正しく表示されますが、その親メニューの前景は黒になり、ほとんど読むことができません。メニューをハードコーディングしていた場合、親の前景色は白になります。親の上にマウスを移動すると、テキストが白に戻り、サブメニューが黒になります。
さらに、マウスを親から離すと、そのブールプロパティ IsHighlighted、IsSubmenuOpenなど
がすべてfalseになります。 。最終的な結果は、スタイルトリガーではこれを解決できなかったということです。
これが私のXAMLです。
<Window.Resources>
<DataTemplate DataType="{x:Type src:ParentMenu}" >
<Menu >
<MenuItem Header="{Binding MenuName}" ItemsSource="{Binding ChildMenuItems}" />
</Menu>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type src:ChildMenu}"
ItemsSource="{Binding ChildMenuItems}" >
<MenuItem Header="{Binding MenuName}" Command="{Binding Path=Command}" />
</HierarchicalDataTemplate>
'StackOverflowはWindow.Resourcesの終了タグをマスクしています
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Menus}" />
<Grid>
<!-- Add additional content here -->
</Grid>
</DockPanel>
ParentMenu
と ChildMenu
の両方は、実際にすべてのメニューを保持し、 ChildMenuItems
コレクションを介してサブメニューを公開する共通クラスを継承します。 ChildMenuItems
は、 ChildMenu
オブジェクトのリストです。私の ViewModels
は、 ParentMenu
オブジェクトのリストを公開します。
ここで望んでいることを達成するためのおそらくより良い方法があります。次に例を示します。
img132.imageshack.us/img132/4160/bettermenu.jpg (デッドリンク)
間違っていることやディスプレイの問題の修正方法に関する提案はありますか?
解決
問題は、VMが自動的にMenuItemsにラップされるため、MenuItemsがMenuItemsのヘッダーとしてネストされていることです。
MenuTypeをDataTypeとして使用して、VMにDataBindするStyle(NameからHeader、DelegateCommandsからCommandなど)を定義(およびItemContainerStyleを介してポイント)することで、これを回避できます。
これを行う方法の例を以下に示します。 ItemContainerStyleを優先してHierarchicalDataTemplateを削除したことに注意してください。また、MainViewModelのDataTemplateを定義する自由を取りました。これは、データがどのようにバインドされているかがあまり明確ではなかったためです。
<Window.Resources>
<DataTemplate DataType="{x:Type src:MainViewModel}">
<ItemsControl ItemsSource="{Binding Menus}"></ItemsControl>
</DataTemplate>
<DataTemplate DataType="{x:Type src:ParentMenu}" >
<Menu>
<MenuItem Header="{Binding Name}"
ItemsSource="{Binding ChildMenuItems}" ItemContainerStyle="{DynamicResource ChildMenuItemStyle}" />
</Menu>
</DataTemplate>
<Style x:Key="ChildMenuItemStyle" TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"></Setter>
<Setter Property="ItemsSource" Value="{Binding ChildMenuItems}"></Setter>
</Style>
</Window.Resources>
簡単にするために、コマンドバインドの一部も切り取りましたが、必要に応じて追加し直すことができます。
他のヒント
要求に応じて、ここに私のViewModelsがあります。
ViewModelBaseは、スタジオによって作成された標準的なものです。 MainVieModelには、実験に使用していたテストメニューを作成するのに十分な容量があります。
基本的には、幅広いクライアントに販売する一連のアプリで使用できる親/子メニュークラスの作成に取り組んでいます。各顧客が自分のニーズやライセンスを購入したモジュールに基づいてカスタマイズ可能なメニューのコレクションを作成できる場所にしたいと思っています。
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
パブリッククラスMainViewModel:ViewModelBase
{
public MainViewModel() { MakeMenus(); }
private void MakeMenus()
{
// Makes some dummy menus to test with.
Menus = new ObservableCollection<MyMenuItem>();
ParentMenu parent;
ChildMenu child;
parent = new ParentMenu("First Level");
Menus.Add(parent);
child = new ChildMenu(parent, "second level");
parent.ChildMenuItems.Add(child);
ChildMenu child2 = new ChildMenu(child, "third level");
child2.MenuCommand = new DelegateCommand(CommandTest,
CommandCanExecute_First);
child.ChildMenuItems.Add(child2);
child = new ChildMenu(parent, "second level 2");
parent.ChildMenuItems.Add(child);
child2 = new ChildMenu(child, "third level 2");
child2.MenuCommand = new DelegateCommand(CommandTest,
CommandCanExecute_Second);
child.ChildMenuItems.Add(child2);
parent = new ParentMenu("Another First");
parent.ChildMenuItems.Add(new ChildMenu(parent, "Another Second"));
Menus.Add(parent);
OnPropertyChanged("Menus");
}
private bool ExecuteToggle { get; set; }
private void CommandTest() { ExecuteToggle = !ExecuteToggle; }
public ObservableCollection<MyMenuItem> Menus { get; private set;}
public bool CommandCanExecute_First() { return ExecuteToggle; }
public bool CommandCanExecute_Second() { return !ExecuteToggle; }
}