I'm asking this here because I'm at a loss for trying to figure this out. I've searched and all that comes up are things that make sense but also don't apply to my situation.
I'm using WPF with MVVM and Caliburn.Micro. I have a shell window with a corresponding view model which is a Conductor<Screen>.Collection.OnceActive and a screen which is inheriting from Screen. I'm calling ActivateItem within the Conductor's constructor to show the subsequent screen, it shows the screen correctly but never calls the Screen's override for OnActivate and the screen's IsActive property is set to False.
This only happens the very first time I call ActivateItem from the Conductor, all additional calls will properly call OnActivate and OnDeactivate.
This makes no sense to me and I have no idea what is going on. I cleaned the solution, rebuilt, and even rebooted but it still doesn't work properly. Below is the code:
Parent Conductor
[Export]
public sealed class ShellViewModel : Conductor<Screen>.Collection.OneActive, IHandle<SimpleMessage>
{
private readonly DashboardViewModel m_Dash;
private readonly LoginViewModel m_Login;
private readonly IEventAggregator m_MsgBus;
[ImportingConstructor]
public ShellViewModel(DashboardViewModel dash, LoginViewModel login, IEventAggregator msgBus)
{
this.m_MsgBus = msgBus;
this.m_Dash = dash;
this.m_Login = login;
this.ActivateItem(this.m_Login);
}
protected override void OnActivate()
{
this.m_MsgBus.Subscribe(this); //called correctly
}
protected override void OnDeactivate(bool close)
{
this.m_MsgBus.Unsubscribe(this); //called correctly
}
public void Handle(SimpleMessage message)
{
switch (message)
{
case SimpleMessage.LoginSuccess:
this.ActivateItem(this.m_Dash);
break;
case SimpleMessage.Logout:
this.ActivateItem(this.m_Login);
break;
}
}
}
Child Screen
[Export]
public sealed class LoginViewModel : Screen
{
private readonly IEventAggregator m_MsgBus;
[ImportingConstructor]
public LoginViewModel(IEventAggregator msgBus)
{
this.m_MsgBus = msgBus;
}
protected override void OnActivate()
{
//NOT called the first time, but is called every other time
MessageBox.Show("ACTIVATE TEST");
}
protected override void OnDeactivate(bool close)
{
//NOT called the first time, but is called every other time
MessageBox.Show("DEACTIVATE TEST");
}
public void CmdLogin(string password)
{
this.m_MsgBus.PublishOnUIThread(SimpleMessage.LoginSuccess);
}
public string Username { get; set; }
public string Password { get; set; }
}
UPDATE
I downloaded the Caliburn Micro source so I could step into the ActivateItem function and see what is going on. For some reason, when I first call ActivateItem from the Conductor the Conductor's IsActive property is set to false which causes Caliburn to skip calling the OnActivate override. I have no idea why the property would be false.
ConductorBaseWithActiveItem.cs
protected virtual void ChangeActiveItem(T newItem, bool closePrevious) {
ScreenExtensions.TryDeactivate(activeItem, closePrevious);
newItem = EnsureItem(newItem);
//Problem is here, IsActive is false the first time around in the conductor
if(IsActive)
ScreenExtensions.TryActivate(newItem);
activeItem = newItem;
NotifyOfPropertyChange("ActiveItem");
OnActivationProcessed(activeItem, true);
}
It looks like the reason IsActive is false in the Conductor is because my Conductor is the root view which is created using DisplayRootViewFor and it looks like that function does not set the IsActive property to true.
So, knowing that, am I simply just implementing this wrong and a Conductor can't/shouldn't be the root view? Do I need to have a 2nd child view which is the conductor (that seems like a bit much)?
I figured it out and it was basically me not thinking. Activating a view in the constructor of the conductor/root view doesn't work properly because it hasn't been activated yet. IsActive isn't set to true until the conductor's/root view's OnActivate is called.
This may be problematic at some point because the conductor isn't active even when OnInitialize is called and that is meant to be the one time init function and OnActivate could be called multiple times. In my case it will be fine because my conductor is the root view so OnActivate will only be called once.
Moral of the story is, don't call ActivateItem in a conductor's constructor when the conductor is a root view.
Related
I have a WPF MVVM App, but I want my ViewModel to be generic. What the app is suppose to do is take some Data and do CRUD operations on it without knowing the Type of the data it's getting at the compile-time. So I declared my ViewModel like this:
public class GenericViewModel<T> where T : class
{
private void ConstructorBase()
{
Type theType = typeof(T);
Properties = theType.GetProperties().ToList();
}
public GenericViewModel(DbContext _dbContextInsert) //pravi novi repository na osnovu DbContexta
{
ConstructorBase();
_R = new RepositoryGlobal<T>(_dbContextInsert);
}
public T newT { get; set; }
public T selectedT { get; set; }
public List<PropertyInfo> Properties { get; set; }
private RepositoryGlobal<T> _R;
}
Now, disregard almost everything you see inside it, the only important thing is that the Constructor is never reached. I set this ViewModel as the DataContext for the main window like this:
InitializeComponent();
this.DataContext = new GenericViewModel<Person>(new PersonDbContext());
But when I put a breakpoint inside the ViewModel's constructor, the program never gets stopped.
Any ideas?
Dependencies should be abstractions, not implementations.
Your generic view model should not create it's own repository, instead you should pass in an instance of this dependency via the constructor.
public class GenericViewModel<T> where T : class
{
protected readonly IRepository<T> _Repository;
public GenericViewModel(IRepository<T> repository)
{
_Repository = repository;
}
...
}
You would then create an instance of your repository like so:
DbContext context = new PersonDbContext();
IRepository<Person> personRepo = new PersonRepository(context);
GenericViewModel<Person> personViewModel = new GenericViewModel<Person>(personRepo);
There, your View Model's dependencies are no longer tied to a specific implementation, your code is now far more adaptable to changes. Not to mention massively easier to test.
I've started writing my first WPF/MVVM and as many other people have found, dealing with navigation between views is rather confusing.
I've been searching for a while and most of the topics either recommend using MVVM Light/PRISM, or come up with solutions similar to one from here.
I'm trying to approach an MVVM navigation mechanism in which I can switch directly from one view to another view (without using the datatemplate switch from the parent window). Let's say, I have an app with a main window loading dynamic content from different usercontrols (views).
The MainWindowViewModel would have a CurrentV property pointed to, say, UserListV and a CurrentVM property pointed to UserListVM. Now that I select one user from the list and click on the View button to view that user details in another screen of the same window. This should allow me to switch to the UserV with UserVM as data context.
I wonder how should I, while being on UserListVM, make a call to MainWindowViewModel to update the CurrentV and CurrentVM values, and switching the window to the UserV accordingly?
Any suggestion of a better idea is more than welcome!
Thank you very much!
I use a messaging service for communication between ViewModels, while still keeping them decoupled. If you are using MVVMLight, it comes with one. I prefer not to use an MVVM framework, and write my own messaging service. Here is an example of one from a recent project:
public class MessageService : IMessageService
{
private List<IMessageSubscription> subscribers; //list of subscription objects registered
public MessageService()
{
subscribers = new List<IMessageSubscription>();
}
public void Subscribe<T>(string message, Action<T> action)
{
subscribers.Add(new MessageSubscription<T>()
{
Message = message,
MessageActionWithArgs = action
});
}
public void Subscribe(string message, Action action)
{
subscribers.Add(new MessageSubscription<bool>()
{
Message = message,
MessageActionNoArgs = action
});
}
public void Send<T>(string message, T args)
{
IEnumerable<IMessageSubscription> matches = subscribers.Where(x => x.Message == message && x.PayLoadType == typeof(T));
foreach (IMessageSubscription sub in matches.ToList())
{
sub.InvokeMessageAction((T)args);
}
}
public void Send(string message)
{
IEnumerable<IMessageSubscription> matches = subscribers.Where(x => x.Message == message);
foreach (IMessageSubscription sub in matches.ToList())
{
sub.InvokeMessageAction();
}
}
}
So, for example, MainViewModel would listen for a message such as "ActiveViewModelChangeRequest", and other viewmodels would send that message when they need to become active. So, in MainViewModel you would have something like this:
public MainViewModel()
{
messageService.Register<ViewModelBase>("ActiveViewModelChangeRequest", UpdateActiveViewModel);
}
private void UpdateActiveViewModel(ViewModelBase viewModel)
{
this.CurrentVM = viewModel;
}
And then in UserListVM you would have:
private void OnUserSelect(object sender, UserSelectionEventArgs e)
{
UserVM viewModel = new UserVM(SelectedUser);
messageService.Send<ViewModelBase>("ActiveViewModelChangeRequest, viewModel);
}
There's a lot of reading material available on the messenger pattern for MVVM applications. I would suggest reading up on this.
I'm implementing an RelayCommand with an execute and an canExecute part. The RelayCommand works when it is without the canExecute part, however when I add the canExecute part, the command locks the button. The RelayCommand only checks whether or not the button can be executed as long as the CanExecute part is true. Once the canExecute part becomes false, the button can no longer be clicked, even if it is supposed to. How do I make sure that every time I click on the button it controls whether or not it can be executed, and doesn't lock it forever, once it cannot be executed?
RedoCommand = new RelayCommand(undoRedoController.Redo,undoRedoController.CanRedo);
public bool CanRedo()
{
redoStack.Count();
redoStack.Any();
return redoStack.Any();
}
public void Redo()
{
if (redoStack.Count() <= 0) throw new InvalidOperationException();
IUndoRedoCommand command = redoStack.Pop();
undoStack.Push(command);
command.Execute();
}
public class UndoRedoController
{
private static UndoRedoController controller = new UndoRedoController();
private readonly Stack<IUndoRedoCommand> undoStack = new Stack<IUndoRedoCommand>();
private readonly Stack<IUndoRedoCommand> redoStack = new Stack<IUndoRedoCommand>();
private UndoRedoController() { }
public static UndoRedoController GetInstance() { return controller; }
There has been a hiatus with MVVMLight due to the fact that after the .NET 4.5 update the CommandManager no longer fires the can execute check. This has since been solved. Instead of including the GalaSoft.MvvmLight.Command namespace you should use the GalaSoft.MvvmLight.CommandWpf namespace. The RelayCommand defined in that namespace is still checking the CanExecute function that you pass to the command.
Took me about a day to find out what the hell was going wrong in my application. I hope this will help some of you.
Here is a blog post by the developer explanining why this is necessary.
For some reason you have to do the following:
public RelayCommand RedoCommand{
get;
set;
}
you can also put private before set optional, depending on your access level. Then you do
RedoCommand = new RelayCommand(() => undoRedoController.Redo(), () => undoRedoController.CanRedo());
Now your able to call RedoCommand.RaiseCanExecuteChanged();
And everything works.
If you are using an unpatched .net 4.5. Microsoft broke the .CanExecute event.
http://connect.microsoft.com/VisualStudio/feedback/details/753666/net-4-0-application-commands-canexecute-not-updating-in-4-5
If you are using the RelayCommand from http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090030 and are not raising the CanExecuteChanged event when redoStack changes.
(Answering from a Silverlight perspective so assuming this will help you.)
Are you doing a RedoCommand.RaiseCanExecuteChanged() anywhere? Once whatever condition you are monitoring changes, you'll need to raise this command manually.
EDIT
Since you are using MVVM Light.. Heres sample code:
RedoCommand = new RelayCommand(undoRedoController.Redo,undoRedoController.CanRedo);
public bool CanRedo()
{
redoStack.Count();
redoStack.Any();
return redoStack.Any();
}
public void Redo()
{
if (redoStack.Count() <= 0) throw new InvalidOperationException();
IUndoRedoCommand command = redoStack.Pop();
undoStack.Push(command);
command.Execute();
// At this point, your stacks have changed; that is, the stacks
// may or may not contain items. Thus, raise the commands CanExecute part
// which will in turn enable/disable the commands based on the functions
// return value
RedoCommand.RaiseCanExecuteChanged();
// assuming you could possibly have an UndoCommand somewhere
UndoCommand.RaiseCanExecuteChanged();
}
I'm creating a WPF MVVM app using Caliburn Micro. I have a set of buttons in a menu (Ribbon) that live in the view for my shell view model, which is a ScreenConductor. Based on the currently active Screen view model, I would like to have the ribbon buttons be disabled/enabled if they are available for use with the active Screen, and call actions or commands on the active Screen.
This seems like a common scenario. Is there a pattern for creating this behavior?
Why don't you do the reverse thing, instead of checking which commands are supported by the current active screen, let the active screen populate the menu or ribbon tab with all the controls that it supports, (i would let it inject its own user control which might just be a complete menu or a ribbon tab all by itself), this will also enhance the user experience as it will only show the user the controls that he can work with for the current active screen.
EDIT: Just looking at your question again and I'm thinking that this is much simpler than it looks
The only issue I can see you having is that a lack of a handler (and guard) method on a child VM will mean that buttons that don't have an implementation on the currently active VM will still be enabled.
The default strategy for CM is to try and find a matching method name (after parsing the action text) and if one is not found, to leave the button alone. If you were to customise that behaviour so that the default is for buttons to be disabled, you could easily get it working by just implementing the command buttons in your shell, making sure to set the command target to the active item:
In the shell define your buttons, making sure they have a target that points to the active child VM
<Button cal:Message.Attach="Command1" cal:Action.TargetWithoutContext="{Binding ActiveItem}" />
Then just implement the method in your child VM as per usual
public void Command1() { }
and optionally a CanXX guard
public bool CanCommand1
{
get
{
if(someCondition) return false;
return true;
}
}
Assuming you don't get much more complex than this, it should work for you
I'm going to have a quick look at the CM source and see if I can come up with something that works for this
EDIT:
Ok you can customise the ActionMessage.ApplyAvailabilityEffect func to get the effect you want - in your bootstrapper.Configure() method (or somewhere at startup) use:
ActionMessage.ApplyAvailabilityEffect = context =>
{
var source = context.Source;
if (ConventionManager.HasBinding(source, UIElement.IsEnabledProperty))
{
return source.IsEnabled;
}
if (context.CanExecute != null)
{
source.IsEnabled = context.CanExecute();
}
// Added these 3 lines to get the effect you want
else if (context.Target == null)
{
source.IsEnabled = false;
}
// EDIT: Bugfix - need this to ensure the button is activated if it has a target but no guard
else
{
source.IsEnabled = true;
}
return source.IsEnabled;
};
This seems to work for me - there is no target for methods which couldn't be bound to a command, so in that case I just set IsEnabled to false. This activates buttons only when a method with a matching signature is found on the active child VM - obviously give it a good test before you use it :)
Create methods and accompanying boolean properties for each of your commands on your shell view model. (See code below for an example.) Caliburn.Micro's conventions will wire them up to the buttons for you automatically. Then simply raise property changed events for the boolean properties when you change views to have them be re-evaluated.
For example, let's say you have a Save button. The name of that button in your xaml would be Save, and in your view model, you would have a Save method along with a CanSave boolean property. See below:
public void Save()
{
var viewModelWithSave = ActiveItem as ISave;
if (viewModelWithSave != null) viewModelWithSave.Save();
}
public bool CanSave { get { return ActivateItem is ISave; } }
Then, in your conductor, whenever you change your active screen, you would call NotifyOfPropertyChange(() => CanSave);. Doing this will cause your button to be disabled or enabled depending upon if the active screen is capable of dealing with that command. In this example, if the active screen doesn't implement ISave, then the Save button would be disabled.
I would use the Caliburn.Micro event aggregation in this scenario, as follows:
Create a class named ScreenCapabilities with a bunch of Boolean attributes (e.g. CanSave, CanLoad, etc.)
Create a message named ScreenActivatedMessage with a property of type ScreenCapabilities
Create a view model for your ribbon that subscribes to (handles) the ScreenActivatedMessage
In the ribbon view model's Handle method, set the local CanXXX properties based on the supplied ScreenCapabilities.
It would look something like this (code typed by hand, not tested):
public class ScreenCapabilities
{
public bool CanSave { get; set; }
// ...
}
public class ScreenActivatedMessage
{
public ScreenCapabilities ScreenCapabilities { get; set; }
// ...
}
public class RibbonViewModel : PropertyChangedBase, IHandle<ScreenActivatedMessage>
{
private bool _canSave;
public bool CanSave
{
get { return _canSave; }
set { _canSave = value; NotifyPropertyChanged(() => CanSave); }
}
// ...
public void Handle(ScreenActivatedMessage message)
{
CanSave = message.ScreenCapabilities.CanSave;
// ...
}
}
Then, somewhere appropriate, when the screen changes, publish the message. See see Caliburn.Micro wiki for more info.
Define a property (let's say ActiveScreen) for the active screen in the shell view model.
And let's assume you have properties for the each button such as DeleteButton, AddButton.
Screen is a viewmodel for the screens.
private Screen activeScreen;
public Screen ActiveScreen
{
get
{
return activeScreen;
}
set
{
activeScreen= value;
if (activeScreen.Name.equals("Screen1"))
{
this.AddButton.IsEnabled = true;
this.DeleteButton.IsEnabled = false;
}
if else (activeScreen.Name.equals("Screen2"))
{
this.AddButton.IsEnabled = true;
this.DeleteButton.IsEnabled = true;
}
NotifyPropertyChanged("ActiveScreen");
}
}
I use prism v4 and MEF to load my modules. My modules contain a handful of views (MVVM) which are loaded in a ItemsControl/NavigationRegion automatically by MEF.
This works nicely, all items show up in the ItemControl. But I don't like the order in which they show. One module might contain several of the items, so changing the module load order is not enough by itself.
How can I sort the different views in the ItemsControl? Is there any way to sort them by some property?
I use prism V4, MEF and exploration due to attributes like in the StockTraderRI example.
This is actually baked into Prism4. Just apply the ViewSortHintAttribute to your views:
[ViewSortHint("100")]
class FirstView : UserControl { }
[ViewSortHint("200")]
class SecondView : UserControl { }
The default sort comparer on the regions will pick up this attribute and sort the views accordingly. You can put any string into the attribute but I tend to use medium sized numbers that allow me to easily put a new view in between existing ones.
Oh dang, this was way easier than I expected:
You can tell the region manager how to sort the views in a specific region. You just need to provide a compare function to the region.
This example sorts by a very stupid value, the function name:
private static int CompareViews(object x, object y)
{
return String.Compare(x.ToString(), y.ToString());
}
this._regionManager.Regions["MyRegion"].SortComparison = CompareViews;
Of course the region needs to be known to the region manager before you can set the SortComparison. So far the only workaround I found to achieve this was to defer to set the comparison function using the Dispatcher:
private readonly IRegionManager _regionManager;
[ImportingConstructor]
public ShellViewModel(IRegionManager regionManager)
{
this._regionManager = regionManager;
Dispatcher dp = Dispatcher.CurrentDispatcher;
dp.BeginInvoke(DispatcherPriority.ApplicationIdle, new ThreadStart(delegate
{
if (this._regionManager.Regions.ContainsRegionWithName("MyRegion"))
this._regionManager.Regions["MyRegion"].SortComparison = CompareViews;
}));
}
Of course one should use some more useful information than the class name for the sorting order, but this should be easy to solve (I'll just add an interface to all views which might be added to this region which provide a value to sort by).
I'm pretty sure you are looking for the CollectionViewSource. Bea provides some information on how to make use of it in the link.
From an MVVM stance this is how I use the ICollectionView within my ViewModel. The _scriptService.Scripts property is an ObservableCollection<T> getting wrapped in an ICollectionView which is returned to the View. The _view.Filter is being used to filter out items within the ICollection, thus changing the View. Similar to typing 'acc' and seeing all items that begin with 'acc' in your list.
public class ScriptRepositoryViewModel : AViewModel
{
private readonly IUnityContainer _container;
private readonly IScriptService _scriptService;
private readonly IEventAggregator _eventAggregator;
private ICollectionView _view;
public ScriptRepositoryViewModel(IUnityContainer container, IScriptService scriptService, IEventAggregator eventAggregator)
{
_container = container;
_scriptService = scriptService;
_eventAggregator = eventAggregator;
}
public ICollectionView Scripts
{
get
{
if (_view == null)
{
_view = CollectionViewSource.GetDefaultView(_scriptService.Scripts);
_view.Filter = Filter;
}
return _view;
}
}
}
Below is the code which takes care of the filtering, and is coming in via a DelegateCommand within Prism, this resides in the same ViewModel.
#region SearchCommand
public DelegateCommand<object> SearchCommand { get; private set; }
private String _search = String.Empty;
private void Search(object commandArg)
{
_search = commandArg as String;
_view.Refresh();
}
public bool Filter(object arg)
{
bool usingPrefix;
IScript script = arg as IScript;
if (script.FileType == ConvertPrefixToFileType(_search, out usingPrefix))
{
if (_search.Length == 2)
return true;
else
return CheckProperties(script, usingPrefix);
}
else
{
if (usingPrefix)
return false;
else
return CheckProperties(script, usingPrefix);
}
}
With the base functionality in place and making use of the ICollectionView you can apply your sorting as follows....
_view.SortDescriptions.Add(new SortDescription("PropertyName", direction));
More information on the sorting behavior can be found here, as there are some performance thoughts to keep in mind.
You could use either metadata or properties. It depends on whether you have control over the interface or not...
Views are displayed in the order they are added:
RegionManager.RegisterViewWithRegion("ListRegion", typeof(ListView));
RegionManager.RegisterViewWithRegion("ListRegion", typeof(ListView2));
RegionManager.RegisterViewWithRegion("ListRegion", typeof(ListView3));
will look like:
----region--|
| view3 |
| view2 |
| view |