Background : I have a Window having a tab control with each tab having a separate UserControl. I have followed MVVM for each user control and MEF to obtain the controls to be displayed in the tab at runtime. This is my implementation
interface ITabControl
{
}
[Export(typeof(UserControl1ViewModel))]
class UserControl1ViewModel
{
}
class UserControl1: ITabControl
{
[Import(typeof(UserControl1ViewModel))]
public UserControl1ViewModel ViewModel
{
get { return this.DataContext as UserControl1ViewModel; }
set { this.DataContext = value; }
}
}
//Other user controls have similar implementation
public class WindowViewModel
{
//Import all type of ITabControl and set the TabCollection(bound to ItemSource property of tab control)
}
Problem : Now I have some validations to be done on a particular set of tabs based on the user action in the main window. So I have used another interface called IConfiguration which is implemented by some user control ViewModels.
interface IConfiguration
{
public void Action1();
public void Action2();
------------------- (many more)
}
public class Window
{
//Import all type of IConfiguration and call Action1/Action2 for all these types based on user actions.
}
Now, if an error is encountered during validation (IConfigure actions impelemented in different ViewModels) in any of the above tabs, I need to set the SelectedTabItem property of the tab control to that particular tab. Since these actions are implemeted in the ViewModel, I'm unable to obtain the UserControl to set the SelectedTabItem property. How do I achieve this?
PS: I know I can achieve this by implementing IConfiguration in UserControl view instead of ViewModel this way
public class UserControl1 : IConfiguration
{
public void Action1
{
this.ViewModel.Action1();
}
public void Action2
{
this.ViewModel.Action2();
}
//--------
}
I wonder if there is a better way to achieve this.
Use an overarching viewmodel which contains a collection of ViewModels (one per tab) and a property which represents the active tab.
When you need to swap the active tab you can do it in the viewmodel just by updating the property that represents the active tab. This answer here shows you how to bind the active tab in the TabControl.
Related
I am new to WPF and MVVM (coming in from WinForms and Events), so please bear with me!
I am trying to figure out how to use the same INotifyPropertyChanged value binding between multiple views. I am using MVVM Light. I have ViewModels that inherit from ViewModelBase backing my Views (with no code behind). I'm not sure how to explain the issue, but I think an example will make it clear what I'm after.
I have a main window. It has a standard TabControl. On the Login TabItem I have a custom login control. Below the TabControl, I have a custom status bar control. The desired behavior is that when the user logs in, the status bar is updated with their login status and name, and the other TabItems on the main window become enabled (they should be disabled when not logged in).
So to summarize I have:
MainWindow (view) with MainWindowViewModel
Login (view) with LoginViewModel (in TabControl of MainWindow)
StatusBar (view) with StatusBarViewModel (at bottom of MainWindow)
Here is what my StatusBarViewModel looks like:
public class StatusBarViewModel : ViewModelBase, IStatusBarViewModel
{
private bool _isLoggedIn;
public bool IsLoggedIn
{
get { return _isLoggedIn; }
set { Set(ref _isLoggedIn, value); RaisePropertyChanged(); }
}
// other properties follow
}
I inject (using Ninject) the (singleton) concrete instance of IStatusBarViewModel into the LoginViewModel via constructor injection:
public class LoginViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public LoginViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
And I do the same for the MainWindowViewModel:
public class MainWindowViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public bool IsLoggedIn => _statusBar.IsLoggedIn;
public MainWindowViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
Note: I think this is where my problem is... Not sure if MVVM Light interprets this as a bindable property and applies the proper change notifications. If I add a setter (which I don't need here), that won't work because A property or indexer may not be passed as an out or ref parameter. So I'm unclear on what is going on when I do this.
Back on track here, so when the login is successful, I am able to update the IsLoggedIn property from the LoginViewModel like so:
_statusBar.IsLoggedIn = true;
I set up the binding in my MainWindow XAML like so:
<TabItem Header="Event" IsEnabled="{Binding IsLoggedIn}">
<views:Events/>
</TabItem>
The binding works correctly when the view is first loaded, but subsequent changes to the property don't trigger a change in IsEnabled. The StatusBar (view) however does update accordingly.
I had tossed around the idea of injecting references to both the StatusBarViewModel and the MainWindowViewModel in to my LoginViewModel (and then having to set two properties after login), but that made me think that I'm not approaching this correctly because I'm creating dependencies.
So basically the question is:
Is my approach correct, per the MVVM pattern?
Am I on the right track and just need to modify the code a bit?
If not, what is the (or a) standard pattern to handle this scenario?
Your guess is correct. The problem is here:
public bool IsLoggedIn => _statusBar.IsLoggedIn;
... because it's not going to generate the change notification. What you could do is just expose the IStatusBarViewModel via a public property and then bind to its own IsLoggedIn property directly.
In the ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public IStatusBarViewModel StatusBar => _statusBar;
public MainWindowViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
And in the View:
<TabItem Header="Event" IsEnabled="{Binding StatusBar.IsLoggedIn}">
<views:Events/>
</TabItem>
I have put the Bootstrapper in a separate assembly than ViewModels and Views.
However the DesignTime support seems to work for simple *ViewModel->*View visualizations when there is no other Screen embedded that needs to resolve the corresponding view. But I can't get it running for child ViewModels. I get an Exception instead...
Container with Child ViewModel:
<UserControl ...
d:DataContext="{d:DesignInstance Type=vm:DesignSomeContainerViewModel, IsDesignTimeCreatable=True}"
cal:Bind.AtDesignTime="True ... >
<ContentControl cal:View.Model={Binding PropertyWithChildModel} />
// where PropertyWithChildModel is another Screen
The Screen within the property is working correctly when displaying on it's own at DesignTime but not inside the ContentControl of another View... then the DesignTime support gets broken. I followed the guidance as suggested here Caliburn Micro Documentation. But there is nothing said about specialties when using nested resolving.
My project structure:
[Application.exe]
- MyNameSpace.ApplicationName
- Bootstrapper (knows assemblies with views)
[Satellite.dll]
- MyNameSpace.SomeWhere
- SomeContainerViewModel
- [DesignTime]/DesignSomeContainerViewModel (Folder is Namespaceprovider)
- SomeContainerView (SomeContainerView is set to look at DesignSomeContainerViewModel at DesignTime)
- [MyChilds]/SomeChildViewModel
- [DesignTime]/DesignSomeChildViewModel
- [MyChilds]/SomeChildView (SomeChildView is set to look at DesignSomeChildViewModel)
Class model:
public interface ISomeChildViewModel
{
string SomeValue {get;}
}
// should not be used during design time
public class SomeChildViewModel : Screen, ISomeChildViewModel
{
// real implementation
}
// this is used at design time
public class DesignSomeChildViewModel : Screen, ISomeChildViewModel
{
public DesignSomeChildViewModel()
{
this.SomeValue = "Hello World!";
// showing up correctly in the Xaml View at DesignTime
}
string SomeValue {get;set;}
}
public interface ISomeContainerViewModel
{
IScreen PropertyWithChildModel{get;}
}
// should not be used during design time
public class SomeContainerViewModel : Screen, ISomeContainerViewModel
{
// real implementation
}
// used during design time
public class DesignSomeContainerViewModel : Screen, ISomeContainerViewModel
{
public DesignSomeContainerViewModel ()
{
// this assignment leads to an exception that the IoC is not ready
// "IoC is not initialized."
this.PropertyWithChildModel = new DesignSomeChildViewModel();
}
IScreen PropertyWithChildModel
{
get { return propertyWithChildModel; }
set
{
this.propertyWithChildModel=value;
this.NotifyOfPropertyChange();
}
}
}
What I already tried/checked:
Bootstrapper: list of assemblies from SelectAssembly contains assembly with Views
ViewModelLocator: was set to match against DesignTime ViewModel instead of ViewModel (even if not, there should be a message that the View can't be resolved for the ViewModel)
used Conductor with ActiveItem instead of simple property
the container DesignSomeContainerViewModel and SomeContainerViewModel share same interface with IScreen PropertyWithChild {get;} and binding points to the right property
I am building a WPF application with the aid of MVVM light and Unity.
I have a ribbon control in the main form, with one tab without tab category, and two tabs in a tab category. User control 3 includes user control 2 and user control 1.
I would like to make visible the Tab Category and tabs 2 and 3 when I click in user control 2 and make them invisible when click in user control 3 or 1.
I confused a little bit with the event handling in mvvm structure.
I would like to do this properly with the MVVM rules. Could you give me an example or some really helpful directions how to do it?
Check how it looks like
Your Ribbon or tabs for that matter, should be backed up by a ViewModel, let's say TabCategoryViewModel or Tab2ViewModel and Tab3ViewModel. Inside these ViewModels you inject the IMessanger service (of course register it before if not already done) and create a POCO event message, like SelectedViewMessage
public class SelectedViewMessage
{
public string ViewName { get; set; }
}
Inside your TabCategoryViewModel you would register to listen to this message
public class TabCategoryViewModel : ViewModelBase
{
public readonly IMessanger messageService;
public TabCategoryViewModel(IMessanger messageService)
{
if(messageService == null)
{
throw ArgumentNullException("messageService");
}
this.messageService = messageService;
this.messageService.Register<GoToPageMessage>(this, OnSelectedViewChanged);
}
protected void OnSelectedViewChanged(SelectedViewMessage message)
{
this.IsVisible = message.ViewName == "UserControl2";
}
private bool isVisible;
public bool IsVisible
{
get { return isVisible; }
set
{
if(isVisible != value)
{
isVisible = value;
RaisePropertyChanged();
}
}
}
}
You inject the same IMessanger service into your ViewModel you use to Bind your UserControl2 and fire the message via
var message = new SelectedViewMessage {
ViewName = "UserControl2";
};
this.messangerService.Send<SelectedViewMessage>(message);
This code can be placed inside a ViewSelectedCommand or something similar, and you can use Blend Interactivity Triggers/Actions to bind this to certain events on the View/UserControl
This can be achieved by adding event onPropertyChange whenever user clicks on the tab and add that property in xaml under Visibility tag. Also look at Handling UI Control to understand that mapping in xaml and ti understand event handling from Here
Hope it helps.
I am new to Windows Phone development.
I am writing an application for Windows Phone 8 using MVVM Light Toolkit.
I have a MainPage with longlistselector navigating to the details page with the relaycommand and everything is good.
Now in the detail page I have to fill out the UI controls with the binding context received from the MailPage (selecteditem of the longlistselector). My problem is that I have in the detail page and which selecteditem should be bound to the data context received from the mainpage.
Just to give an example in the mainpage I have the lostlingselector bound to a list of task objects of the mainviewmodel; every task have its own category which could be selected from the availabe task categories. How could I approach this? Is it possible to bound the ItemSource of the ListPicker control in the detail page to a different viewmodel and the SelectedItem of the same control to the proprties Category of the default viewmodel (selected task object)?
Thank you.
You can create a new view with it's own viewmodel and pass the data between viewmodels using the MVVM Light Messenger class.
Something like:
public class DetailsViewModel
{
public DetailsViewModel()
{
Messenger.Default.Register<Item>(this, "ItemDetails", i=> ViewItemDetails(i));
}
public void ViewItemDetails(Item i)
{
//Now you can bind it to your UI
}
}
And pass the object from your main viewmodel just like this (ItemDetails it's just a token to identify the listeners)
Messenger.Default.Send<Item>(SelectedItem, "ItemDetails");
You shouldn't really mess up with bindings in between different ViewModel. To setup interaction between viewmodel's you can use Messnger from MvvmLight toolkit, inject it in both objects and define proper pub-sub relations.
public class FirstViewModel : INotifyPropertyChanged
{
private IMesseger messenger;
...
public FirstViewModel(IMessenger messenger)
{
this.messenger = messenger;
}
public Item SelectedItem
{
get
{
return this.selectedItem;
}
set
{
this.selectedItem = value;
this.messenger.Send(new GenericMessage<Item>(this.selectedItem));
this.OnPropertyChanged("SelectedItem");
}
}
}
public class SecondViewModel : INotifyPropertyChanged
{
private IMesseger messenger;
...
public SecondViewModel(IMessenger messenger)
{
this.messenger = messeger;
this.messenger.Register<GenericMessage<Item>>(this, this.HandleItemSelected);
}
...
}
So after sharing same instance of messenger between two VM's you'll have desired functionality with loosely coupled relations which is good from testing perspectives.
When I read this line:
The viewmodel exposes not only models, but other properties (such as state information, like the "is busy" indicator) and commands.
http://www.codeproject.com/Articles/100175/Model-View-ViewModel-MVVM-Explained
I am wondering whether current settings on the view should be considered a property of the view or part of the state and placed in a view model.
For example I currently have a preferences window bound to a view model containing the current colors with a bunch of color pickers bound to the view model. Commands then cancel changes and reapply the old settings if ok or cancel are clicked.
Should the current colors be properties on the view instead or is their current location ok?
public class PreferencesWindowViewModel:DependencyObject
{
private DelegateCommand updatePreferencesCommand;
public ICommand UpdatePreferencesCommand
{
get { return updatePreferencesCommand; }
}
private DelegateCommand cancelCommand;
public ICommand CancelCommand
{
get { return cancelCommand; }
}
public Color HighValuePenColor
{
get { return (Color)GetValue(HighValuePenColorProperty); }
set
{
SetValue(HighValuePenColorProperty, value);
}
}
I like them to be on the view model. For the simple reason that this functionality is testable in a unit testing harness. If they were on the view you would have to jump through so many more hoops to get it tested. In my opinion, the view model should contain most of the presentation logic. Colors and their relation to certain application states belong in that category of code.