Assume a standard Desktop Explorer style app:
menu at top
navigation tree on left
item view on right
and everything is neatly separated into Menu, Navigation and Item modules.
The menu has a generic "View" -> "Display mode" menu selection, that changes the current item view between:
"Icon view"
"List view"
"Detail view"
To catch the display type change, I currently publish and subscribe to a DisplayModeChanged event.
First problem:
This menu option should only display when an appropriate view is visible that has the display modes. What is the best way to control shared menu buttons so they only show if at least one relevant view is active?
Second Problem:
If a view comes into existence after the selection was made, how should it pickup the current display mode state from the menu?
My first thought was that you could have solved the first problem by storing item view menu settings somewhere together with your item view. Then, on view activation you would ask your new view about its "custom menu actions" that it wants to show and one of them would be "Display mode" for item view. Other views would provide other menu actions and this one will not be shown.
But this solution contradicts with your second requirement, because you obviously want to have some global 'ItemViewDisplayModeSetting', and whenever it is changed you want all item views to be notified and change their display mode together.
So, let's just solve it right by applying the dependency injection principle. Do not look for things, ask for things. Your menu presenter (view model) requires some service that knows whether there are active item views or not. Your item view presenter requires a service that will provide initial display mode and will notify about its changes. We end up with this code:
interface IActiveViewsService : INotifyPropertyChanged
{
bool HasActiveViewsSupportingDisplayMode { get; }
}
interface IDisplayModeService : INotifyPropertyChanged
{
DisplayMode DisplayMode { get; }
}
//your item view presenter (view model)
class ItemViewModel
{
public ItemViewModel(IDisplayModeService displayModeService)
{
//obtain initial setting
//subscribe to property changes
}
}
//your menu presenter (view model)
class MenuViewModel
{
public MenuViewModel(IActiveViewsService activeViewsService)
{
//obtain initial setting
//subscribe to property changes
}
}
Then you need some way to edit your Diplay Mode from menu... you may combine that together into IDisplayModeService or you may create a new interface for that. The implementation will be a simple class that holds one DisplayMode instance and you inject one instance of that class into all your menus and item views. You'll also need to implement IActiveViewsService, this one will probably wrap IRegionManager from PRISM or whatever is your UI management mechanism... it will listen for region changes and react when a new item view is created or when there is no one left.
interface IDisplayModeEditor
{
void ChangeDisplayMode(DisplayMode newMode);
}
//your final menu presenter (view model)
class MenuViewModel
{
public MenuViewModel(IActiveViewsService activeViewsService, IDisplayModeEditor modeEditor)
{
//obtain initial setting
//subscribe to property changes
}
}
//your final menu presenter (view model)
class DisplayModeStorage : IDisplayModeService, IDisplayModeEditor
{
private DisplayMode displayMode;
public DisplayMode DisplayMode
{
get { return this.displayMode; }
//standard propertychange notification
set
{
if(value == this.displayMode)
return;
this.displayMode = value;
this.RaisePropertyChanged("DisplayMode");
}
}
public void ChangeDisplayMode(DisplayMode newMode)
{
this.DisplayMode = newMode;
}
}
HTH.
Related
I have a datagrid in my view(UserControl) in which I have a button, The requirement is to show a window on click of that button. I achieved it by creating an object for the window and calling it's show method and yes it's a wrong way, So, I want a standard way of achieving it.
I'm using Prism framework, So I mapped the window view with Window viewmodel using ViewModelLocationProvider in the windowviewmodel.
var table = new Window();
table.Content = new windowViewModel();
table.Show();
This is the code I implemented in the main viewmodel on click of the button.
It is actually opening a window, but, view is not getting loaded on the window.
Use the IDialogService Introduced in Prism 7
first the dialog view or ViewModel needs to implement the IDialogAware interface like this:
class PopupViewModel : BindableBase, IDialogAware
{
public string Title => "Popup";
public event Action<IDialogResult> RequestClose;
public bool CanCloseDialog()
{
return true;
}
public void OnDialogClosed()
{
}
public DelegateCommand OkCommand => new DelegateCommand(() =>
{
//you can also pass any parameters you want to back pass to the caller
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
});
public void OnDialogOpened(IDialogParameters parameters)
{
//here you get the parameters you passed;
}
}
then you need to register your view like this
containerRegistry.RegisterDialog<Views.Popup>();
to use you inject into your ViewModel the IDialogService and call it
this.dialogService.ShowDialog(nameof(Views.Popup), dialogParmaters, (result)=>
{
if(result.Result == ButtonResult.OK)
{
}
});
you can also register your custom window if you want the window needs to implement IDialogWindow
containerRegistry.RegisterDialogWindow<MyDialogWindow>();
First, you want the view model to be the DataContext (the Content is in fact the view). I'd advise againt the ViewModelLocator here, because it makes it impossible to pass parameters to the new window. Probably, you want to show different content depending on which row you click the button in.
new Window { DataContext = new WindowViewModel( myRow ) }.Show();
Second, there's nothing wrong with this approach but for one minor remark: put the code that shows the window in a service. You want to unittest your view models and that doesn't go well with opening real windows.
public interface IWindowService
{
void ShowWindow( SomeParameter parameter );
}
If you inject that into your view model, you can still test it (verify that ShowWindow is called).
Third, remove calls to new and replace them with calls to injected factories.
var viewModel = _viewModelFactory.Create( "some parameter" );
is a lot nicer than
var viewModel = new ViewModel( "some parameter", new ADependency( new OtherDependency(), new SomeService() );
not to speak of the difficulties if singletons are involved.
The question is "How do I map an event triggered by the VM to a command using XAML?"
I'm new to WPF and MVVM. What I really want to do is this...
The UI has a button and a grid control. The user selects a single item in the grid control and presses the button. Depending on the type of item selected in the grid (A or B), one of two different modal dialogs are displayed to ask the user for extra input. When the user clicks OK on the dialog, the correct command is executed.
Here's how I think I'm supposed to do it...
The XAML binds the button to a command. That command looks at the currently selected item and raises one of two different events to indicate the type of item that is selected. In XAML these two events need to be linked to commands.
This brings me to the question that I asked above. If my VM fires two different events, how can I bind each of those to a command in XAML?
Hello i created a sample for you - you didnt really provide any sample code so i just give an common example
public class MyViewModel : BaseViewModel
{
private object selectedItem;
private ICommand myCommand;
public ICommand MyCommand
{
get
{
if(myCommand == null)
{
myCommand = new RelayCommand(MyCommandMethod, CanIExecuteMyCommand);
}
return myCommand;
}
}
public object SelectedItem
{
get
{
return selectedItem;
}
set
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
CommandManager.InvalidateRequerySuggested();
}
}
private void MyCommandMethod()
{
if(SelectedItem is MyClassA)
{
// do A stuff
}
else if(SelectedItem is MyClassB)
{
// do B stuff
}
//Can this happen?
}
private bool CanIExecuteMyCommand()
{
return selectedItem != null;
}
}
RelayCommand watch here
What you wanna do is bind the SelectedItem to your GridView. So you can validate it in the ViewModel. You talked about Validation - you can do this by giving your Command a Condition like CanIExecuteMyCommand().
You should use a parent class for MyClassA and MyClassB! They should be - because you are using them in the same GridView. Else go with object mate.
The extra information is extra information required for that command, so you should make it a continuation of the execution - no need for events yet.
Instead, invoke the IDialogViewModel's OpenDialog(new ExtraInfoViewModel(this->model)).
When a user clicks OK on the dialog, publish an UserUpdatedEvent from the ExtraInfoViewModel to your Mediator. Now you can simply your original VM subscribe to the UserUpdatedEvent message, and will execute the right command based on the user type info on the event message.
I have a WPF PRISM application with regions -- Menu Region and workspace Region. When the menu is clicked appropriate view/form is opened inside workspace region. I have different modules like Employee, Organization, Finance, etc... with each module having multiple views/forms.
I have a requirement to open a view/form from Finance module as a dialog window when a button in the employee view/form (Module - Employee) is clicked.
Is there a way to achieve the same in PRISM?
[Edit 1]: The 2 modules are independent modules. I do not wish to add a reference of Finance module to Employee module. As further down I may have a requirement to show Employee as a dialog window from one or many Finance views.
Sure - here are two ways for you to consider.
Option 1
Prism's documentation has a section on "Making a command globally available" using their CompositeCommand class. To do this, create a public static class in a common library that both assemblies can reference:
public static class GlobalCommands
{
public static CompositeCommand ShowFinanceFormCommand = new CompositeCommand();
}
In the Finance form viewmodel, you would register your actual command (which has all the logic for showing the form) with:
public FinanceViewModel()
{
GlobalCommands.ShowFinanceFormCommand.RegisterCommand(this.ShowFinancePopupCommand);
}
In the employee view model, bind your button's ICommand to the ShowFinanceFormCommand composite command.
public EmployeeViewModel()
{
this.EmployeeShowFinanceFormCommand = GlobalCommands.ShowFinanceFormCommand;
}
Option 2
If you need to broadcast events across modules in a loosely coupled fashion, use Prism's EventAggregator class. To implement this, create a ShowFinanceFormEvent in a common assembly that both can reference. In the employee viewmodel, publish an event when the button is pressed. In the finance viewmodel, subscribe to that event and react accordingly.
// This sits in a separate module that both can reference
public class ShowFinanceFormEvent : PubSubEvent<object>
{
}
public class EmployeeViewModel
{
private readonly IEventAggregator eventAggregator;
public EmployeeViewModel(IEventAggregator eventAggregator)
{
this.ShowFormCommand = new DelegateCommand(RaiseShowFormEvent);
this.eventAggregator = eventAggregator;
}
public ICommand ShowFormCommand { get; }
private void RaiseShowFormEvent()
{
// Notify any listeners that the button has been pressed
this.eventAggregator.GetEvent<ShowFinanceFormEvent>().Publish(null);
}
}
public class FinanceViewModel
{
public FinanceViewModel(IEventAggregator eventAggregator)
{
// subscribe to button click events
eventAggregator.GetEvent<ShowFinanceFormEvent>().Subscribe(this.ShowForm);
this.ShowFinanceFormRequest = new InteractionRequest<INotification>();
}
public InteractionRequest<INotification> ShowFinanceFormRequest { get; }
private void ShowForm(object src)
{
// Logic goes here to show the form... e.g.
this.ShowFinanceFormRequest.Raise(
new Notification { Content = "Wow it worked", Title = "Finance form title" });
}
}
Option 3, and the better option in my opinion, is just to use Prism's Navigation framework. If you need to show a dialog, then use a dialog service and simply pass the view key to the service to determine which view to show in the dialog. Since you re using unique keys instead of references, you don't have to know or care where the view exists.
I use a similar approach in my PluralSight course here:
https://app.pluralsight.com/library/courses/prism-showing-multiple-shells/table-of-contents
Check out the dialog service demo.
In my Silverlight app I have a view containing a tab control and a view model of this view.
When the selected tab is changed, I need to refresh its data. In order to do that in the view model I'm using a command triggered by EventTrigger in the view and passing the appropriate event args to it (as described here http://weblogs.asp.net/alexeyzakharov/archive/2010/03/24/silverlight-commands-hacks-passing-eventargs-as-commandparameter-to-delegatecommand-triggered-by-eventtrigger.aspx).
Each tab item has its own view model and, therefore, to distinguish which view model I have to use to refresh the data, I'm watching the header in the tab item which I can get from the event args, e.g:
_tabSelectionChangedCommand = new DelegateCommand<SelectionChangedEventArgs>(TabSelectionChanged);
public ICommand TabSelectionChangedCommand
{
get { return _tabSelectionChangedCommand; }
}
private void TabSelectionChanged(SelectionChangedEventArgs e)
{
var tabItem = (TabItem)e.e.AddedItems[0];
if (tabItem.Header == "Header1" )
{
TabItem1ViewModel.Refresh();
}
.....
}
So, my question is :
Is good that I'm using in the view model the types related to the UI(TabItem, SelectionChangedEventArgs) and are there better ways to do what I've described above?
Maybe you can bind the SelectedIndex of the TabControl to a property defined in your viewmodel and attach an InvokeActionCommand to the TabControl and subscrible to its SelectionChanged event.
Then when the command gets called, check which index it is then load the data accordingly?
I am using MVVM Light toolkit in my WPF application. I would like to know what is the best approach for opening a new window from an existing window. I have got this MainViewModel, which is responsible for MainWindow of my application. Now in the MainView, on a button click, I would like to open a second window on top of it. I have got RelayCommmand binded to the Button's Command. In the RelayCommand's method, I can create a new window object and simply call Show(), something like this:
var view2 = new view2()
view2.Show()
but I don't think the ViewModel should be responsible for creating the new view2 object. I have read this post WPF MVVM Get Parent from VIEW MODEL where Bugnion has suggested to pass message to the view1 from the viewmodel1 and then view1 should create the new view2. But I am not sure what does he actually mean by passing the message to the view1? How should the view1 handle the message? In it's code behind or what?
Regards,
Nabeel
Passing a message from ViewModel1 to View1 means to use the messaging capabilities in the MVVM Light Toolkit.
For example, your ViewModel1 could have a command called ShowView2Command, then it would send a message to display the view.
public class ViewModel1 : ViewModelBase
{
public RelayCommand ShowView2Command { private set; get; }
public ViewModel1() : base()
{
ShowView2Command = new RelayCommand(ShowView2CommandExecute);
}
public void ShowView2CommandExecute()
{
Messenger.Default.Send(new NotificationMessage("ShowView2"));
}
}
View1 would register to receive messages in its code behind and display View2 when it receives the correct message.
public partial class View1 : UserControl
{
public View1()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "ShowView2")
{
var view2 = new view2();
view2.Show();
}
}
}
Why do you go this route? Its simple. If you replace your button with a toggleButton, or a hyperlink, or any other number of button-like controls, you don't need to update your "code behind" - its a basic principle of the MVVM pattern. In your new toggleButton (or whatever), you still end up binding to the same exact Command.
For example, I'm creating a project for a client who wants to have 2 UI's - one is going to be fundamentally different in every way, in terms of presentation. Horizontal tabs vs Vertical RadPanelBar (think Accordion) for navigation. Both of these views can point to the same viewModel - when a user clicks the Work Order tab in View 1, it fires the same "WorkOrderCommand" that's fired in the Work Order Header in the panel bar.
In a code-behind model, you'd have to code two separate events. Here you only have to code one.
Furthermore, it allows a designer using Blend to create any layout they want. As long as they have the hooks (EventToCommand control) in place, myself (as a developer) couldn't care less what the final product looks like.
Loose coupling is incredibly powerful.
You can do in this way like you need to create some events and register those in view and call these in view model.and open that pop up window.
Like This example
public class Mainclass : MainView
{
public delegate abc RegisterPopUp(abc A);
public RegisterPopUp POpUpEvent ;
public RelayCommand ShowCommand { private set; get; }
public void ShowCommand()
{
ShowCommand("Your parameter");
}
}
inside the view MainView mn=new MainView();
Register the event here like thake mn.POpUpEvent += than click on tab button double time
and in registers popup method right the code for opening the pop up window.
Unless I am missing the point here - if I were to use the code behind, then why not directly implement button_click event and open the second view?
What Bugnion seems to be suggesting is view1 -> button click -> relay command -> viewmodel1 -> message -> view1 -> view1.cs -> open view 2.
You are going to sacrifice testability anyhow by writing code-behind, so why take such a long route?
You can abstract the view specific features into services using generic interface. In the view layer you can provide concrete instances of these services and build view models using the IoC container and Dependency Injection technique.
In your case you can build an interface IWindowManager or something similar which has the required method. This can be implmented in your view layer. I wrote a small blog post recently demonstrating how to abstract the dialog behaviour out of view model. Similar apporach can be used for any user interface related service like Navigation, MessageBoxes etc.
This link might be helpful for you http://nileshgule.blogspot.com/2011/05/silverlight-use-dialogservice-to.html
Many people also use the approach of firing events from view models which are subscribed on the view.cs file and from there the MessageBox or any other UI related action is performed. I personally like the approach of injecting services because then you can provide multiple implementations of the same service. A simple example would be how navigation is handled in Silverlight and Windows Phone 7 applications. You can use the same view model but inject different implementations of the Navigation service based on the application type.
I find the best way to approach this, is opening and closing the window from the ViewModel. As this link suggests,
Create a DialogCloser class
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(DialogCloser), new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null) window.Close();
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
Create a Base ViewModel inheriting from GalaSoft.MvvmLight.ViewModelBase with there additional members. Once done, use this viewmodel as base for other viewmodels.
bool? _closeWindowFlag;
public bool? CloseWindowFlag
{
get { return _closeWindowFlag; }
set
{
_closeWindowFlag = value;
RaisePropertyChanged("CloseWindowFlag");
}
}
public virtual void CloseWindow(bool? result = true)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() =>
{
CloseWindowFlag = CloseWindowFlag == null ? true : !CloseWindowFlag;
}));
}
In the view, Bind the DialogCloser.DialogResult dependency property with the CloseWindowFlag property in the base viewmodel.
Then you can open/close/hide the window from the viewmodel.