I have MVVM WPF application. I create Menu via a separate service and set to load time.
My shell view which contains the menu is as follows:
<ItemsControl x:Name="MainToolbar" cal:RegionManager.RegionName="{x:Static inf:RegionNames.MainToolBarRegion}" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="1,1,1,1" RenderTransformOrigin="-0.133,-5.917" Height="28" >
<ItemsControl.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</ItemsControl.RenderTransform>
<Menu IsMainMenu="True" Margin="0,0,0,0" Height="28" ItemsSource="{Binding Path=Menu}" Width="400">
<Menu.Resources>
<Style x:Key="ThemeMenuItemStyle" TargetType="MenuItem" BasedOn="{StaticResource KV_MenuItem}">
<Setter Property="Header" Value="{Binding Path=Text}"></Setter>
<Setter Property="Command" Value="{Binding Path=Command}"/>
<Setter Property="CommandParameter" Value="{Binding Path=Text}"/>
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="MinWidth" Value="80"/>
</Style>
</Menu.Resources>
</Menu>
</ItemsControl>
MenuItem has a view model behind:
public class MenuItemViewModel : NotificationObject
{
private string text;
private ICommand command;
private IList<MenuItemViewModel> menuItems;
public MenuItemViewModel()
{
this.menuItems = new List<MenuItemViewModel>();
}
public string Text
{
get
{
return text;
}
set
{
if (text != value)
{
text = value;
RaisePropertyChanged(() => this.Text);
}
}
}
public ICommand Command
{
get
{
return command;
}
set
{
if (command != value)
{
command = value;
RaisePropertyChanged(() => this.Command);
}
}
}
public IList<MenuItemViewModel> MenuItems
{
get { return menuItems; }
set
{
if (menuItems != value)
{
menuItems = value;
RaisePropertyChanged(() => this.MenuItems);
}
}
}
}
My Relay command:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
In menu service, I assign the command object like follows:
...
MenuItemViewModel app = new MenuItemViewModel();
app.Text = "APP";
app.Command = new RelayCommand(
param => this.App(""),
param => true);
MenuItemViewModel exit = new MenuItemViewModel();
exit.Text = "Exit";
exit.Command = new RelayCommand(
param => this.Exit(""),
param => this.CanExit
);
app.MenuItems.Add(exit);
mService.AddMenu(app);
...
My MenuItem is loading fine with assigned header text, but when I click a menu item it is not firing the relevant delegate methods such App("") or Exit("")
What I am doing wrong?
Related
I want my DataGrid have ComboBox with default value for example I have a column for Genre:
Action
Drama
Comedy
I Want ComboBox Show those data and then select the item in database (for example in database Genre is Drama.
I use WPF Net 6 with EF Sqlite for manage Database.
in Database Class I set Genre as string.
DataGrid of other Column I use Something like this:
<DataGridTextColumn Header="ID" Binding="{Binding Path=ID,UpdateSourceTrigger=PropertyChanged}"/>
in Code Behind: DgTest.ItemsSource=db.Test.ToList();
i would strongly advise to use MVVM pattern as it is a best practice for WPF applications. It could look like this
Boilerplate
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
field = value;
OnPropertyChanged(propertyName);
}
}
public class DelegateCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public DelegateCommand(Action execute, Func<bool> canExecute = null)
{
if (execute is null)
throw new ArgumentNullException(nameof(execute));
_execute = execute;
_canExecute = canExecute ?? DefaultCanExecute;
}
private bool DefaultCanExecute() => true;
public event EventHandler CanExecuteChanged;
public bool CanExecute()
{
return _canExecute();
}
public void Execute()
{
_execute();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute();
}
void ICommand.Execute(object parameter)
{
Execute();
}
}
public class DelegateCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Func<T, bool> _canExecute;
public DelegateCommand(Action<T> execute) : this(execute, null) { }
public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
{
if (execute is null)
throw new ArgumentNullException(nameof(execute));
_execute = execute;
_canExecute = canExecute ?? DefaultCanExecute;
}
private bool DefaultCanExecute(T _) => true;
public event EventHandler CanExecuteChanged;
public bool CanExecute(T parameter)
{
return _canExecute(parameter);
}
public void Execute(T parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
Execute((T)parameter);
}
}
Model
public class Movie
{
public int Id { get; set; }
public string Name { get; set; }
public string Genre { get; set; }
}
ViewModel
public class MoviesViewModel : ObservableObject
{
private readonly MyDbContext _dbContext;
private Task _runningQuery;
private CancellationTokenSource _runningQueryCancellation;
private const int _pageSize = 100;
public MoviesViewModel()
{
_dbContext = new MyDbContext();
_dbContext.Movies.Add(new Movie { Name = "foo", Genre = "Genre1" });
_dbContext.Movies.Add(new Movie { Name = "bar", Genre = "Genre1" });
_dbContext.Movies.Add(new Movie { Name = "baz", Genre = "Genre2" });
_dbContext.SaveChanges();
ReloadCommand = new DelegateCommand(ReloadMovies);
NextPageCommand = new DelegateCommand(() => Page++, () => ((Page + 1) * _pageSize) < _movieCount);
PreviousPageCommand = new DelegateCommand(() => Page--, () => Page > 0);
_runningQuery = LoadGenresAndCount();
}
private async Task LoadGenresAndCount()
{
try
{
IsBusy = true;
var genres = await _dbContext.Movies.Select(m => m.Genre).Distinct().OrderBy(g => g).ToListAsync();
genres.Insert(0, null); // add option for no Genre selected
Genres = genres;
MovieCount = await _dbContext.Movies.CountAsync();
ReloadMovies();
}
catch (Exception ex)
{
Error = $"Error while loading {ex}";
}
}
public DelegateCommand ReloadCommand { get; }
public DelegateCommand NextPageCommand { get; }
public DelegateCommand PreviousPageCommand { get; }
// properties use SetValue to tell the view to update itself when the value changes
private bool _isBusy;
public bool IsBusy { get { return _isBusy; } set { SetValue(ref _isBusy, value); } }
private string _error;
public string Error { get { return _error; } private set { SetValue(ref _error, value); } }
private IEnumerable<string> _genres;
public IEnumerable<string> Genres { get => _genres; private set => SetValue(ref _genres, value); }
private string _selectedGenre;
public string SelectedGenre
{
get { return _selectedGenre; }
set { SetValue(ref _selectedGenre, value); ReloadMovies(); }
}
private int _movieCount;
public int MovieCount { get => _movieCount; private set => SetValue(ref _movieCount, value); }
private IEnumerable<Movie> _movies;
public IEnumerable<Movie> Movies { get => _movies; private set => SetValue(ref _movies, value); }
private int _page;
public int Page
{
get { return _page; }
private set
{
SetValue(ref _page, value);
NextPageCommand.RaiseCanExecuteChanged();
PreviousPageCommand.RaiseCanExecuteChanged();
ReloadMovies();
}
}
private void ReloadMovies()
{
IsBusy = true;
Error = null;
// cancel the running query because the filters have changed
_runningQueryCancellation?.Cancel();
_runningQueryCancellation = new CancellationTokenSource();
// having selectedGenre as parameter so it doesn't change for the asynchron operation
_runningQuery = ReloadMoviesAsync(_runningQuery, SelectedGenre, _runningQueryCancellation.Token);
}
private async Task ReloadMoviesAsync(Task queryBefore, string selectedGenre, CancellationToken token)
{
// wait for running query to finish to prevent parallel access to the context which is not thread safe
if (queryBefore != null)
await queryBefore;
try
{
IQueryable<Movie> query = _dbContext.Movies;
if (selectedGenre != null)
{
query = query.Where(m => m.Genre == selectedGenre);
}
Movies = await query
.OrderBy(m => m.Name) // usefull and nessesary for skip take
.Skip(_page * _pageSize).Take(_pageSize) // only load items of the page
.AsNoTracking() // tell ef to not track changes
.ToListAsync(token);
}
catch (Exception ex)
{
Error = $"Error while loading {ex}";
}
IsBusy = false;
}
}
View
<Window x:Class="SomeApplication.MoviesView"
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:SomeApplication"
mc:Ignorable="d"
Title="Movies" Height="450" Width="800">
<FrameworkElement.DataContext>
<local:MoviesViewModel/>
</FrameworkElement.DataContext>
<FrameworkElement.Resources>
<BooleanToVisibilityConverter x:Key="TrueToVisible"/>
</FrameworkElement.Resources>
<DockPanel>
<!--header-->
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<Button Content="Reload" Command="{Binding ReloadCommand}"/>
<ComboBox ItemsSource="{Binding Genres}" SelectedItem="{Binding SelectedGenre}"/>
</StackPanel>
<!--footer-->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" DockPanel.Dock="Bottom">
<TextBlock Text="Page"/>
<TextBlock Text="{Binding Page}"/>
<TextBlock Text="Total Movies" Margin="10,0,0,0"/>
<TextBlock Text="{Binding MovieCount}"/>
<Button Command="{Binding PreviousPageCommand}" Content="<" Margin="5"/>
<Button Command="{Binding NextPageCommand}" Content=">" Margin="5"/>
<!--busy Indicator-->
<TextBlock Text="Loading..." Visibility="{Binding IsBusy, Converter={StaticResource TrueToVisible}}"/>
</StackPanel>
<DataGrid ItemsSource="{Binding Movies}" AutoGenerateColumns="False" HorizontalAlignment="Stretch" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridComboBoxColumn Header="Genre" SelectedItemBinding="{Binding Genre}" ItemsSource="{Binding DataContext.Genres, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</DataGrid.Columns>
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Error}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
</DataGrid>
<TextBlock Text="{Binding Error}" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Error}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DockPanel>
</Window>
This is how i fix my problem. same as Firo but in simple way.
My Model for Genre:
public int ID {get; set;}
public string? Title {get; set;}
my Repository:
Create A list of Genre and then Return it
My View-Model
public ObservableCollection<Genre> AllGenreList => new (_repository.GetAllGenre())
private Genre _genre;
public Genre SelectedGenre{get=> _genre; set{ _genre=value; ONotifyPropertyChanged(nameof(Genre);}
and then Bind AllGenreList to ComboBox.
<ComboBox ItemsSource="{Binding AllGenreList }" SelectedItem="{Binding SelectedGenre}"/>
this is how i fix my problem. hope solve some one else problem.
I am trying to develop a messaging application in WPF..
Now,what I want is when a user clicks on "Enter" the message has to send and when the user clicks "Shift+enter" it should move to a new line.
I have tried something like this,But it doesn't seems to work
if (e.Key == Key.Enter && (e.KeyboardDevice.Modifiers & ModifierKeys.Shift) != ModifierKeys.Shift)
{
//insert newline
}
else if(e.Key==Key.Enter)
{
//Send Message
}
I am using Textbox here.
Set the AcceptsReturn property to true and handle PreviewKeyDown:
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && Keyboard.Modifiers != ModifierKeys.Shift)
{
//TODO: send message...
e.Handled = true;
}
}
XAML:
<TextBox AcceptsReturn="True" PreviewKeyDown="TextBox_PreviewKeyDown" />
Working on a similar concept. Here is what I did. The below solution also somewhat adheres to MVVM architectural pattern if that's your thing.
You'll need the following.
Step 1:
Add the following for you XAML.
<TextBox Text="{Binding Path=MessageText, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="False" AcceptsTab="True" TextWrapping="Wrap" SpellCheck.IsEnabled ="True">
<TextBox.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="3"/>
</Style>
</TextBox.Resources>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SendMessageCommand, Mode=OneWay}" />
<KeyBinding Gesture="Shift+Return" Command="{Binding NewLineCommand, Mode=OneWay}" CommandParameter="{Binding Path=., Mode=OneWay, ElementName=chattext_field}" />
</TextBox.InputBindings>
Step 2 : Create your view model if you don't already have one. In my example, it called AppViewModel.
class AppViewModel : INotifyPropertyChanged
{
private string messageText = string.Empty;
public string MessageText
{
get { return messageText; }
set
{
messageText = value;
NotifyPropertyChanged();
}
}
public ICommand SendMessageCommand { get; private set; }
public ICommand NewLineCommand { get; private set; }
public void Load()
{
NewLineCommand = new CustomCommand(p =>
{
System.Windows.Controls.TextBox txtB = p as System.Windows.Controls.TextBox;
if (txtB == null)
return;
var caretIdx = txtB.CaretIndex;
if (string.IsNullOrEmpty(MessageText))
MessageText += "\r";
else
MessageText = MessageText.Insert(caretIdx, "\r");
txtB.CaretIndex = caretIdx + 1;
});
SendMessageCommand = new CustomCommand(p =>
{
try
{
// your send message code here
}
catch (LogException ex)
{
System.Windows.MessageBox.Show($"Message sending failure.\n{ex}", "Message Center", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Step 3 :
When you load your user control/View using the view model. Initialize/Load the view model when the view is ready.
public partial class MyChatControl : UserControl
{
public MyChatControl()
{
InitializeComponent();
this.Loaded += MyChatControl_Loaded;
}
private void MyChatControl_Loaded(object sender, RoutedEventArgs e)
{
try
{
AppViewModel model = new AppViewModel();
model.Load();
this.DataContext = model;
}
catch (LogException ex)
{
MessageBox.Show($"Failed control content load.\n{ex}", "Failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
Almost forgot, here is my "CustomCommand" implementation if you don't have one yet. I have a Async version called "CustomAsyncCommand" as well if you need.
// Interface
public interface ICustomCommand : ICommand
{
event EventHandler<object> Executed;
}
// Command Class
public class CustomCommand : ICustomCommand
{
#region Private Fields
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
#endregion
#region Constructor
public CustomCommand(Action<object> execute) : this(execute, null)
{
}
public CustomCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute ?? (x => true);
}
#endregion
#region Public Methods
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter = null)
{
Refresh();
_execute(parameter);
Executed?.Invoke(this, parameter);
Refresh();
}
public void Refresh()
{
CommandManager.InvalidateRequerySuggested();
}
#endregion
#region Events
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public event EventHandler<object> Executed;
#endregion
}
Only Set the AcceptsReturn property to true
XMAL
<TextBox AcceptsReturn="True" />
I´m using MVVM, I have defined a Command in a button. I want to use in this Command a parameter, execute an action and proving if Canexecute.
I have this RelayCommand
class RelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Func<T, bool> _canExecute;
/// <summary>
/// Initializes a new instance of the RelayCommand class that
/// can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <exception cref="ArgumentNullException">If the execute argument is null.</exception>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Initializes a new instance of the RelayCommand class.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <exception cref="ArgumentNullException">If the execute argument is null.</exception>
public RelayCommand(Action<T> execute, Func<T, bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
if (canExecute != null)
_canExecute = canExecute;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (_canExecute == null)
return true;
if (parameter == null && typeof(T).IsValueType)
return _canExecute(default(T));
return _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion
}
With this button
<Button Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"
Style="{StaticResource BotonSelect}" Width="200"
Command="{Binding ModificarLicenciaCommand}" >
<Label Content="Modificar Licencia" />
</Button>
And in the View Model.
ModificarLicenciaCommand = new RelayCommand(ModificarLicencia, CanModificarLicencia);
private bool CanModificarLicencia()
{
// Comprobar puedo modificar
return true;
}
private void ModificarLicencia()
{
// Modificar licencia
}
This is Ok, but I want to pass a parameter and using something like this:
CommandParameter="{Binding ElementName=DataGridLicencias}"
<Button Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"
Style="{StaticResource BotonSelect}" Width="200"
Command="{Binding ModificarLicenciaCommand}"
CommandParameter="{Binding ., ElementName=DataGridLicencias}" >
<Label Content="Modificar Licencia" />
</Button>
and in viewModel:
RelayCommand< SfDataGrid >
ModificarLicenciaCommand = new RelayCommand<SfDataGrid>(ModificarLicencia, CanModificarLicencia);
private void ModificarLicencia(SfDataGrid dataGrid)
{
// Modificar licencia
}
Edit:
With this, I have an error in ModificarLicenciaCommand = new RelayCommand(ModificarLicencia, CanModificarLicencia)
In CanModificarLicentia ==> Error Argument2: cannot convert from 'method group' to 'Func'
Any help?
The following view model implementation should work:
public class ViewModel
{
public ViewModel()
{
ModificarLicenciaCommand = new RelayCommand<SfDataGrid>(ModificarLicencia, CanModificarLicencia);
}
private ICommand _modificarLicenciaCommand;
public ICommand ModificarLicenciaCommand
{
get { return _modificarLicenciaCommand; }
set { _modificarLicenciaCommand = value; }
}
private void ModificarLicencia(SfDataGrid dataGrid)
{
// Modificar licencia
}
private bool CanModificarLicencia(SfDataGrid dataGrid)
{
return true;
}
}
Example, you have a Button and a TextBox.
The button will only be Enable if the textbox's text is not empty:
In View:
<TextBox Name="txtCondition" Width="120" Height="35"/>
<Button Width="120" Height="35" Content="Click me!" Command="{Binding YourICommand}" CommandParameter="{Binding ElementName=txtCondition,Path=Text}">
In ViewModel:
public ICommand YourICommand { get; set; }
YourICommand = new RelayCommand<string>((str) =>
{
MessageBox.Show(str);
},(str) => { return !string.IsNullOrEmpty(str); });
Enjoy your code !
I have problems with binding a treeview to a datagrid's selected item.
they are in different views, but datagrid's selected item is already passed to treeview's related viewmodel.
There is a SelectedGroup property in treeview's related viewmodel which is datagrid's selected item and its type is Group. I want to bind the ID field of Group to treeview, i.e. I want the ID of selected item to be selected in treeview and also be updated by selected value of treeview.
I couldn't find out how to bind.
Here's my treeview's skeleton, which can just lists all of the groups hierarchically.
Can anyone help me on filling the required fields please?
Thanks in advance.
<TreeView Grid.Column="1" Grid.Row="4" Height="251" HorizontalAlignment="Left"
Margin="4,3,0,0" Name="parentGroupTreeView" VerticalAlignment="Top"
Width="246" ItemsSource="{Binding Groups}" ItemContainerStyle="{x:Null}"
SelectedValuePath="ID">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding
Converter={x:Static Member=conv:GroupSubGroupsConv.Default}}">
<Label Name="groupLabel" Content="{Binding GroupName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Start by taking a look at the following article by Josh Smith on Simplifying the WPF TreeView by Using the ViewModel Pattern.
I am also using the DataGrid from the WPF toolkit.
To get a sense as to how this code works look at the IsSelected property below.
Here is the XAML that contains a tree and a datagrid:
<Window x:Class="TreeviewDatagrid.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfToolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
xmlns:ViewModels="clr-namespace:TreeviewDatagrid.ViewModels" Title="Main Window" Height="400" Width="800">
<DockPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="7*"/>
</Grid.ColumnDefinitions>
<TreeView ItemsSource="{Binding Groups}"
Grid.Column="0">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type ViewModels:GroupViewModel}"
ItemsSource="{Binding Children}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<WpfToolkit:DataGrid
Grid.Column="1"
SelectedItem="{Binding Path=SelectedGroup, Mode=TwoWay}"
ItemsSource="{Binding Path=Groups, Mode=OneWay}" >
</WpfToolkit:DataGrid>
</Grid>
</DockPanel>
</Window>
Here is the main view model that the TreeView and DataGrid bind to:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using TreeviewDatagrid.Models;
namespace TreeviewDatagrid.ViewModels
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Group g1 = new Group();
g1.Id = 1;
g1.GroupName = "Planners";
g1.Description = "People who plan";
GroupViewModel gvm1 = new GroupViewModel(this, g1);
Group g2 = new Group();
g2.Id = 2;
g2.GroupName = "Thinkers";
g2.Description = "People who think";
GroupViewModel gvm2 = new GroupViewModel(this, g2);
Group g3 = new Group();
g3.Id = 3;
g3.GroupName = "Doers";
g3.Description = "People who do";
GroupViewModel gvm3 = new GroupViewModel(this, g3);
IList<GroupViewModel> list = new List<GroupViewModel>();
list.Add(gvm1);
list.Add(gvm2);
list.Add(gvm3);
_selectedGroup = gvm1;
_groups = new ReadOnlyCollection<GroupViewModel>(list);
}
readonly ReadOnlyCollection<GroupViewModel> _groups;
public ReadOnlyCollection<GroupViewModel> Groups
{
get { return _groups; }
}
private GroupViewModel _selectedGroup;
public GroupViewModel SelectedGroup
{
get
{
return _selectedGroup;
}
set
{
// keep selection in grid in-sync with tree
_selectedGroup.IsSelected = false;
_selectedGroup = value;
_selectedGroup.IsSelected = true;
OnPropertyChanged("SelectedGroup");
}
}
public void ChangeSelectedGroup(GroupViewModel selectedGroup)
{
_selectedGroup = selectedGroup;
OnPropertyChanged("SelectedGroup");
}
}
}
Here is the viewmodel that I use to bind to the grid and the tree:
using TreeviewDatagrid.Models;
namespace TreeviewDatagrid.ViewModels
{
public class GroupViewModel : TreeViewItemViewModel
{
private readonly MainViewModel _mainViewModel;
readonly Group _group;
bool _isSelected;
public GroupViewModel(MainViewModel mainViewModel, Group group) : base(null, true)
{
_mainViewModel = mainViewModel;
_group = group;
}
public string GroupName
{
get { return _group.GroupName; }
}
public override bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
if (_isSelected )
{
// keep tree selection in sync with grid
_mainViewModel.ChangeSelectedGroup(this);
}
this.OnPropertyChanged("IsSelected");
}
}
}
protected override void LoadChildren()
{
// load children in treeview here
}
}
}
For completeness here is the Group object:
namespace TreeviewDatagrid.Models
{
public class Group
{
public int Id { get; set; }
public string GroupName { get; set; }
public string Description { get; set; }
}
}
And also the base class for the TreeView:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace TreeviewDatagrid.ViewModels
{
/// <summary>
/// Base class for all ViewModel classes displayed by TreeViewItems.
/// This acts as an adapter between a raw data object and a TreeViewItem.
/// </summary>
public class TreeViewItemViewModel : INotifyPropertyChanged
{
#region Data
static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel();
readonly ObservableCollection<TreeViewItemViewModel> _children;
readonly TreeViewItemViewModel _parent;
bool _isExpanded;
bool _isSelected;
#endregion // Data
#region Constructors
protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
{
_parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
if (lazyLoadChildren)
_children.Add(DummyChild);
}
// This is used to create the DummyChild instance.
private TreeViewItemViewModel()
{
}
#endregion // Constructors
#region Presentation Members
#region Children
/// <summary>
/// Returns the logical child items of this object.
/// </summary>
public ObservableCollection<TreeViewItemViewModel> Children
{
get { return _children; }
}
#endregion // Children
#region HasLoadedChildren
/// <summary>
/// Returns true if this object's Children have not yet been populated.
/// </summary>
public bool HasDummyChild
{
get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
}
#endregion // HasLoadedChildren
#region IsExpanded
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is expanded.
/// </summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
// Lazy load the child items, if necessary.
if (this.HasDummyChild)
{
this.Children.Remove(DummyChild);
this.LoadChildren();
}
}
}
#endregion // IsExpanded
#region IsSelected
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is selected.
/// </summary>
public virtual bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
#endregion // IsSelected
#region LoadChildren
/// <summary>
/// Invoked when the child items need to be loaded on demand.
/// Subclasses can override this to populate the Children collection.
/// </summary>
protected virtual void LoadChildren()
{
}
#endregion // LoadChildren
#region Parent
public TreeViewItemViewModel Parent
{
get { return _parent; }
}
#endregion // Parent
#endregion // Presentation Members
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion // INotifyPropertyChanged Members
}
}
Does anyone know of WPF code examples using Prism in which modules each register themselves as a menuitem in a menu within another module?
(I've currently got an application which tries to do this with the EventAggregator, so one module listens for published events from other modules which need to have their title in the menu as a menu item, but I'm getting problems with the order of loading and threading etc. I want to find an example that uses classic Prism structure to do this.)
I'm thinking in terms of this:
Shell.xaml:
<DockPanel>
<TextBlock Text="Menu:" DockPanel.Dock="Top"/>
<Menu
Name="MenuRegion"
cal:RegionManager.RegionName="MenuRegion"
DockPanel.Dock="Top"/>
</DockPanel>
Contracts View:
<UserControl x:Class="ContractModule.Views.AllContracts"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<MenuItem Header="Contracts">
</MenuItem>
</UserControl>
Customers View:
<UserControl x:Class="CustomerModule.Views.CustomerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<MenuItem Header="Customers">
</MenuItem>
</UserControl>
But up to know I've done non-Prism MVVM application structure and Menus were always nicely bound to ObservableCollections in the ViewModel and the above seems to break this nice pattern. Is the above the customary way to do it in Prism?
Update:
I created a sample for you. It's here: Sample (now dead link)
It's got a few things you have probably not thought of yet, like a contract that will allow your modules to control your shell (so you can do stuff like Open Window, that kind of thing). It is designed with MVVM in mind. I don't know if you are using that, but I would consider it.
I tried for a few minutes to get the tab titles correct, but I ended up leaving off with "A Tab". It's left as an exercise for you if you go with a tabbed UI. I've designed it to be lookless, so you can replace the XAML in the Shell.xaml without breaking anything. That's one of the advantages to the RegionManager stuff if you use it right.
Anyway, good luck!
I've never seen an example of this, but you'd have to implement this yourself.
You'd have to create your own interface, something like this:
public interface IMenuRegistry
{
void RegisterViewWithMenu(string MenuItemTitle, System.Type viewType);
}
Your Modules then would declare a dependency on an IMenuRegistry and register their views.
Your implementation of IMenuRegistry (which you would likely implement and register in the same project that hosts your Bootstrapper) you would add those menu items to your menu or treeview or whatever you are using for your menu.
When a user clicks on an item you will have to use your Bootstrapper.Container.Resolve(viewType) method to create an instance of the view and stuff it in whatever placeholder you want to show it in.
I am using MEF along with prism 6.0 and MVVM
1.Create a Menuviewmodel class for Leafmenu and TopLevel MenuViewmodel class for Toplevel menu. Menuviewmodel class will have all the properties you want to bind your menu with. Moduleui implementing this interafce must have an attribute like this
[Export(typeof(IMenu))]
public class MenuViewModel:ViewModelBase
{
public String Name { get; private set; }
public UIMenuOptions ParentMenu { get; private set; }
private bool _IsToolTipEnabled;
public bool IsToolTipEnabled
{
get
{
return _IsToolTipEnabled;
}
set
{
SetField(ref _IsToolTipEnabled, value);
}
}
private String _ToolTipMessage;
public String ToolTipMessage
{
get
{
return _ToolTipMessage;
}
set
{
SetField(ref _ToolTipMessage, value);
}
}
private IExtensionView extensionView;
public MenuViewModel(String name, UIMenuOptions parentmenu,
bool isMenuCheckable = false,
IExtensionView extensionView =null)
{
if(name.Contains('_'))
{
name= name.Replace('_', ' ');
}
name = "_" + name;
this.Name = name;
this.ParentMenu = parentmenu;
this.IsMenuCheckable = isMenuCheckable;
this.extensionView = extensionView ;
}
private RelayCommand<object> _OpenMenuCommand;
public ObservableCollection<MenuViewModel> MenuItems { get; set; }
public ICommand OpenMenuCommand
{
get
{
if(_OpenMenuCommand==null)
{
_OpenMenuCommand = new RelayCommand<object>((args =>
OpenMenu(null)));
}
return _OpenMenuCommand;
}
}
private void OpenMenu(object p)
{
if (extensionView != null)
{
extensionView .Show();
}
}
private bool _IsMenuEnabled=true;
public bool IsMenuEnabled
{
get
{
return _IsMenuEnabled;
}
set
{
SetField(ref _IsMenuEnabled, value);
}
}
public bool IsMenuCheckable
{
get;
private set;
}
private bool _IsMenuChecked;
public bool IsMenuChecked
{
get
{
return _IsMenuChecked;
}
set
{
SetField(ref _IsMenuChecked, value);
}
}
}
public class ToplevelMenuViewModel:ViewModelBase
{
public ObservableCollection<MenuViewModel> ChildMenuViewModels {
get; private set; }
public String Header { get; private set; }
public ToplevelMenuViewModel(String header,
IEnumerable<MenuViewModel> childs)
{
this.Header ="_"+ header;
this.ChildMenuViewModels =new
ObservableCollection<MenuViewModel>(childs);
}
}
}
Create an IMenu Interface wich has MenuViewModel property
public interface IMenu
{
MenuViewModel ExtensionMenuViewModel
{
get;
}
}
3.You need to implement IMenu Interface in ModuleUi of all your modules which will get loaded into a menu.
4.Implement MefBootstrapper
5.Override Configure aggregate catalog method
6.To the catalog add diretory catalog containing all your module dlls, IMenu interface dll.Code is below
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
AggregateCatalog.Catalogs.Add(new
AssemblyCatalog(typeof(Bootstrapper).Assembly));
AggregateCatalog.Catalogs.Add(new
AssemblyCatalog(typeof(IMenu).Assembly));
//create a directorycatalog with path of a directory conatining
//your module dlls
DirectoryCatalog dc = new DirectoryCatalog(#".\Extensions");
AggregateCatalog.Catalogs.Add(dc);
}
in your main project add refence to IMenu interafce dll
8.In mainwindow.xaml.cs class declare a property
public ObservableCollection ClientMenuViewModels
{ get; private set; }
declare a private field
private IEnumerable<IMenu> menuExtensions;
In your mainwindow or shell constructor
[ImportingConstructor]
public MainWindow([ImportMany] IEnumerable<IMenu> menuExtensions)
{
this.menuExtensions = menuExtensions;
this.DataContext=this;
}
private void InitalizeMenuAndOwners()
{
if (ClientMenuViewModels == null)
{
ClientMenuViewModels = new
ObservableCollection<ToplevelMenuViewModel>();
}
else
{
ClientMenuViewModels.Clear();
}
if (menuExtensions != null)
{
var groupings = menuExtensions.Select
(mnuext => mnuext.ClientMenuViewModel).GroupBy(mvvm =>
mvvm.ParentMenu);
foreach (IGrouping<UIMenuOptions, MenuViewModel> grouping in
groupings)
{
UIMenuOptions parentMenuName = grouping.Key;
ToplevelMenuViewModel parentMenuVM = new
ToplevelMenuViewModel(
parentMenuName.ToString(),
grouping.Select(grp => { return (MenuViewModel)grp; }));
ClientMenuViewModels.Add(parentMenuVM);
}
}}
}
In your Shell.xaml or Mainwindow.xaml define a menu region and bind the itemssource property to ClientMenuViewModels
<Menu HorizontalAlignment="Left"
Background="#FF0096D6"
Foreground="{StaticResource menuItemForegroundBrush}"
ItemsSource="{Binding ClientMenuViewModels}"
TabIndex="3">
<Menu.Resources>
<Style x:Key="subMneuStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="#FF0096D6" />
<Setter Property="FontFamily" Value="HP Simplified" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Background" Value="White" />
<Setter Property="Command" Value="{Binding
OpenMenuCommand}" />
<Setter Property="IsCheckable" Value="{Binding
IsMenuCheckable}" />
<Setter Property="IsChecked" Value="{Binding
IsMenuChecked, Mode=TwoWay}" />
<Setter Property="IsEnabled" Value="{Binding
IsMenuEnabled, Mode=TwoWay}" />
<Setter Property="ToolTip" Value="{Binding
ToolTipMessage, Mode=OneWay}" />
<Setter Property="ToolTipService.ShowOnDisabled" Value="
{Binding IsToolTipEnabled, Mode=OneWay}" />
<Setter Property="ToolTipService.IsEnabled" Value="
{Binding IsToolTipEnabled, Mode=OneWay}" />
<Setter Property="ToolTipService.ShowDuration"
Value="3000" />
<Setter Property="ToolTipService.InitialShowDelay"
Value="10" />
</Style>
<my:MyStyleSelector x:Key="styleSelector" ChildMenuStyle="
{StaticResource subMneuStyle}" />
<HierarchicalDataTemplate DataType="{x:Type
plugins:ToplevelMenuViewModel}"
ItemContainerStyleSelector="{StaticResource styleSelector}"
ItemsSource="{Binding ChildMenuViewModels}">
<Label Margin="0,-5,0,0"
Content="{Binding Header}"
FontFamily="HP Simplified"
FontSize="12"
Foreground="{StaticResource menuItemForegroundBrush}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type plugins:MenuViewModel}">
<Label VerticalContentAlignment="Center"
Content="{Binding Name}"
Foreground="#FF0096D6" />
</DataTemplate>
</Menu.Resources>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Menu.ItemsPanel>
</Menu>
public class MyStyleSelector : StyleSelector
{
public Style ChildMenuStyle { get; set; }
public Style TopLevelMenuItemStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject
container)
{
if (item is MenuViewModel)
{
return ChildMenuStyle;
}
//if(item is ToplevelMenuViewModel)
//{
// return TopLevelMenuItemStyle;
//}
return null;
}
}
here is ViewModelBase class
public class ViewModelBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler =Volatile.Read(ref PropertyChanged);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
};
}
protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName="")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
RelayCommand class is below
public class RelayCommand<T> : ICommand
{
#region Fields
private readonly Action<T> _execute = null;
private readonly Predicate<T> _canExecute = null;
#endregion
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command with conditional execution.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion
}