In this thread : Can anybody provide any simple working example of the Conductor<T>.Collection.AllActive usage? I've had part of an answer but I'm still a but confused.
I would simply like to reference all my view models into my ShellViewModel to be able to open/close ContentControls, but without injecting all of them in the constructor.
In the answer, it is suggested to inject an interface in the constructor of the ShellViewModel. If I do that, do I have to inject all my ViewModels in a class that implements that interface?
public MyViewModel(IMagicViewModelFactory factory)
{
FirstSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
SecondSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
ThirdSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
Items.Add(FirstSubViewModel);
Items.Add(SecondSubViewModel);
Items.Add(ThirdSubViewModel);
}
Also, I would like to avoid going through IoC.Get<> to get the instances of my view Models, I think it violates the principles of IoC if I am not mistaken.
In a few other examples, they create new viewModels when needed, but what's the point of using IoC in that case, especially when I need services injected inside those new ViewModels?
In my Shell view, I have a layout with 3 different areas, bound to my shell view model by :
<ContentControl x:Name="Header"
Grid.ColumnSpan="3"/>
<ContentControl x:Name="Menu"
Grid.Row="1"/>
<ContentControl x:Name="Main"
Grid.ColumnSpan="3"/>
In my ShellViewModel extending Conductor.Collection.AllActive, I reference the 3 areas like this:
public Screen Menu { get; private set; }
public Screen Header { get; private set; }
public Screen Main { get; private set; }
I would like to be able to change them like so:
Menu = Items.FirstOrDefault(x => x.DisplayName == "Menu");
Header = Items.FirstOrDefault(x => x.DisplayName == "Header");
Main = Items.FirstOrDefault(x => x.DisplayName == "Landing");
All my ViewModels have a DisplayName set in their constructor.
I have tried this but GetChildren() is empty
foreach (var screen in GetChildren())
{
Items.Add(screen);
}
Am I missing something obvious?
Thanks in Advance!
Finally, I managed to find an answer myself. It's all in the AppBootstrapper!
I ended up creating a ViewModelBase for my Screens so that they could all have an IShell property (so that the ViewModels could trigger a navigation in the ShellViewModel) like so:
public class ViewModelBase : Screen
{
private IShell _shell;
public IShell Shell
{
get { return _shell; }
set
{
_shell = value;
NotifyOfPropertyChange(() => Shell);
}
}
}
then in the AppBoostrapper registered them like this :
container.Singleton<ViewModelBase, SomeViewModel>();
container.Singleton<ViewModelBase, AnotherViewModel>();
container.Singleton<ViewModelBase, YetAnotherViewModel>();
then created an IEnumerable to pass as param to my ShellViewModel ctor:
IEnumerable<ViewModelBase> listScreens = GetAllScreenInstances();
container.Instance<IShell>(new ShellViewModel(listScreens));
then passing the IShell to each ViewModels
foreach (ViewModelBase screen in listScreens)
{
screen.Shell = GetShellViewModelInstance();
}
for the sake of completeness, here are my GetAllScreenInstances() and GetShellViewModelInstance() :
protected IEnumerable<ViewModelBase> GetAllScreenInstances()
{
return container.GetAllInstances<ViewModelBase>();
}
protected IShell GetShellViewModelInstance()
{
var instance = container.GetInstance<IShell>();
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
Here's what my ShellViewModel ctor looks like:
public ShellViewModel(IEnumerable<ViewModelBase> screens )
{
Items.AddRange(screens);
}
Hope this can help someone in the future!
Related
I have an error when I use Catel Framework together with Xceed.Wpf.Toolkit.PropertyGrid.
The error consists in the fact that the PropertyGrid is invisible custom attributes if I inherit from ViewModelBase
If I inherit from ModelBase that all is normal
This code work wery well
public class PersonViewModel : ModelBase
{
[DisplayName(#"Название")]
[Description(#"Название стратегии")]
[Category(#"Основные")]
[PropertyOrder(0)]
public string Person
{
get { return GetValue<string>(PersonProperty); }
set { SetValue(PersonProperty, value); }
}
public static readonly PropertyData PersonProperty = RegisterProperty("Person", typeof(string));
}
but this code didn't work
public class PersonViewModel : ViewModelBase
{
[DisplayName(#"Название")]
[Description(#"Название стратегии")]
[Category(#"Основные")]
[PropertyOrder(0)]
public string Person
{
get { return GetValue<string>(PersonProperty); }
set { SetValue(PersonProperty, value); }
}
public static readonly PropertyData PersonProperty = RegisterProperty("Person", typeof(string));
}
XAML
<xcad:LayoutAnchorable ContentId="alarms"
Title="Alarms"
>
<xctk:PropertyGrid BorderThickness="0"
SelectedObject="{Binding Path=SelectedObject}"
ShowSearchBox="False"
ShowSortOptions="False"
Width="Auto"
AutoGenerateProperties="False"
NameColumnWidth="150">
<xctk:PropertyGrid.PropertyDefinitions>
<xctk:PropertyDefinition Name="Person" />
</xctk:PropertyGrid.PropertyDefinitions>
</xctk:PropertyGrid>
</xcad:LayoutAnchorable>
When using a view model, it is important to add a view to it. You have created a PersonViewModel, but there is no PersonView.
If you don't want to create a separate view for Person, then there is no need for a PersonViewModel. We think it is not the right way to create sub-view models inside a view model. That's why we created the nested user controls solution in Catel.
You have 2 options here:
Create a custom PersonView (which will work dynamically with the
PersonViewModel)
Keep the PersonModel (which is what it is, a model of a person)
Hoping someone could clear things up. In the following ViewModel, does using Entity Framework as my model eliminate the need to use [Model] and [[ViewModelToModel(...)] attributes? The code runs the same with or without them, because the binding in the view ignores them and binds to the ObservableCollection.
Comments?
public class MainWindowViewModel : ViewModelBase
{
Models.OneHour_DataEntities ctx;
public MainWindowViewModel()
: base()
{
Save = new Command(OnSaveExecute, OnSaveCanExecute);
ctx = new Models.OneHour_DataEntities();
Customers = new ObservableCollection<Models.Customer>(ctx.Customers);
}
public ObservableCollection<Models.Customer> Customers
{
get { return GetValue<ObservableCollection<Models.Customer>>(CustomersProperty); }
set { SetValue(CustomersProperty, value); }
}
public static readonly PropertyData CustomersProperty = RegisterProperty("Customers", typeof(ObservableCollection<Models.Customer>), null);
public Command Save { get; private set; }
private bool OnSaveCanExecute()
{
return true;
}
private void OnSaveExecute()
{
ctx.SaveChanges();
}
}
Catel uses different interfaces to take advantage of the models. For example, it uses the following interfaces:
IEditableObject => undoing changes to model when user cancels
INotifyPropertyChanged => update view model when model updates
If your entity model implements these interfaces, you can define a property as a model.
In your example however, you use an ObservableCollection (thus a list of models) as a model. That is not supported (or, again, the collection must support IEditableObject and INotifyPropertyChanged).
I have already searched some tutorials and even looked pluralsite Introduction to PRISM. However, most examples based on using unity containers and the some lack of information on how to implement this feature with Mef container.
My simple helloworld module is based on web tutorial. My code is the same except I’m stuck only on HelloModule and using Mef, not Unity as tutorial shows:
The main my problem how to initialize my view with my view model. The only working way I have found via experimenting is to initialize view-model in View constructor:
HelloView.xaml.cs
namespace Hello.View
{
[Export]
public partial class HelloView : UserControl, IHelloView
{
public HelloView()
{
InitializeComponent();
Model = new HelloViewModel(this);
}
public IHelloViewModel Model
{
//get { return DataContext as IHelloViewModel; }
get { return (IHelloViewModel)DataContext; }
set { DataContext = value; }
}
}
}
And standard module initialization code:
[ModuleExport(typeof(HelloModule), InitializationMode=InitializationMode.WhenAvailable)]
public class HelloModule : IModule
{
IRegionManager _regionManager;
[ImportingConstructor]
public HelloModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void Initialize()
{
_regionManager.Regions[RegionNames.ContentRegion].Add(ServiceLocator.Current.GetInstance<HelloView>());
}
}
However, can someone tell the correct way how to this things, I this it must be done in Module initialization section.
MatthiasG shows the way to define modules in MEF. Note that the view itself does not implement IModule. However, the interesting part of using MEF with PRISM is how to import the modules into your UI at startup.
I can only explain the system in principle here, but it might point you in the right direction. There are always numerous approaches to everything, but this is what I understood to be best practice and what I have made very good experiences with:
Bootstrapping
As with Prism and Unity, it all starts with the Bootstrapper, which is derived from MefBootstrapper in Microsoft.Practices.Prism.MefExtensions. The bootstrapper sets up the MEF container and thus imports all types, including services, views, ViewModels and models.
Exporting Views (modules)
This is the part MatthiasG is referring to. My practice is the following structure for the GUI modules:
The model exports itself as its concrete type (can be an interface too, see MatthiasG), using [Export(typeof(MyModel)] attribute. Mark with [PartCreationPolicy(CreationPolicy.Shared)] to indicate, that only one instance is created (singleton behavior).
The ViewModel exports itself as its concrete type just like the model and imports the Model via constructor injection:
[ImportingConstructor]
public class MyViewModel(MyModel model)
{
_model = model;
}
The View imports the ViewModel via constructor injection, the same way the ViewModel imports the Model
And now, this is important: The View exports itself with a specific attribute, which is derived from the 'standard' [Export] attribute. Here is an example:
[ViewExport(RegionName = RegionNames.DataStorageRegion)]
public partial class DataStorageView
{
[ImportingConstructor]
public DataStorageView(DataStorageViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
The [ViewExport] attribute
The [ViewExport] attribute does two things: Because it derives from [Export] attribute, it tells the MEF container to import the View. As what? This is hidden in it's defintion: The constructor signature looks like this:
public ViewExportAttribute() : base(typeof(UserControl)) {}
By calling the constructor of [Export] with type of UserControl, every view gets registered as UserControl in the MEF container.
Secondly, it defines a property RegionName which will later be used to decide in which Region of your Shell UI the view should be plugged. The RegionName property is the only member of the interface IViewRegionRegistration. The attribute class:
/// <summary>
/// Marks a UserControl for exporting it to a region with a specified name
/// </summary>
[Export(typeof(IViewRegionRegistration))]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
[MetadataAttribute]
public sealed class ViewExportAttribute : ExportAttribute, IViewRegionRegistration
{
public ViewExportAttribute() : base(typeof(UserControl)) {}
/// <summary>
/// Name of the region to export the View to
/// </summary>
public string RegionName { get; set; }
}
Importing the Views
Now, the last crucial part of the system is a behavior, which you attach to the regions of your shell: AutoPopulateExportedViews behavior. This imports all of your module from the MEF container with this line:
[ImportMany]
private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;
This imports all types registered as UserControl from the container, if they have a metadata attribute, which implements IViewRegionRegistration. Because your [ViewExport] attribute does, this means that you import every type marked with [ViewExport(...)].
The last step is to plug the Views into the regions, which the bahvior does in it's OnAttach() property:
/// <summary>
/// A behavior to add Views to specified regions, if the View has been exported (MEF) and provides metadata
/// of the type IViewRegionRegistration.
/// </summary>
[Export(typeof(AutoPopulateExportedViewsBehavior))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class AutoPopulateExportedViewsBehavior : RegionBehavior, IPartImportsSatisfiedNotification
{
protected override void OnAttach()
{
AddRegisteredViews();
}
public void OnImportsSatisfied()
{
AddRegisteredViews();
}
/// <summary>
/// Add View to region if requirements are met
/// </summary>
private void AddRegisteredViews()
{
if (Region == null) return;
foreach (var view in _registeredViews
.Where(v => v.Metadata.RegionName == Region.Name)
.Select(v => v.Value)
.Where(v => !Region.Views.Contains(v)))
Region.Add(view);
}
[ImportMany()]
private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;
}
Notice .Where(v => v.Metadata.RegionName == Region.Name). This uses the RegionName property of the attribute to get only those Views that are exported for the specific region, you are attaching the behavior to.
The behavior gets attached to the regions of your shell in the bootstrapper:
protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
{
ViewModelInjectionBehavior.RegionsToAttachTo.Add(RegionNames.ElementViewRegion);
var behaviorFactory = base.ConfigureDefaultRegionBehaviors();
behaviorFactory.AddIfMissing("AutoPopulateExportedViewsBehavior", typeof(AutoPopulateExportedViewsBehavior));
}
We've come full circle, I hope, this gets you an idea of how the things fall into place with MEF and PRISM.
And, if you're still not bored: This is perfect:
Mike Taulty's screencast
The way you implemented HelloView means that the View has to know the exact implementation of IHelloViewModel which is in some scenarios fine, but means that you wouldn't need this interface.
For the examples I provide I'm using property injection, but constructor injection would also be fine.
If you want to use the interface you can implement it like this:
[Export(typeof(IHelloView)]
public partial class HelloView : UserControl, IHelloView
{
public HelloView()
{
InitializeComponent();
}
[Import]
public IHelloViewModel Model
{
get { return DataContext as IHelloViewModel; }
set { DataContext = value; }
}
}
[Export(typeof(IHelloViewModel))]
public class HelloViewModel : IHelloViewModel
{
}
Otherwise it would look like this:
[Export(typeof(IHelloView)]
public partial class HelloView : UserControl, IHelloView
{
public HelloView()
{
InitializeComponent();
}
[Import]
public HelloViewModel Model
{
get { return DataContext as HelloViewModel; }
set { DataContext = value; }
}
}
[Export]
public class HelloViewModel
{
}
One more thing: If you don't want to change your Views or provide several implementations of them, you don't need an interface for them.
Here is the setup:
I have an autocompletebox that is being populated by the viewmodel which gets data from a WCF service. So it's quite straightforward and simple so far.
Now, I am trying to follow the principles of MVVM by which the viewmodel doesn't know anything about the view itself. Which is good, because I bound the Populating event of the autocomplete box to a method of my viewmodel via triggers and commands.
So the view model is working on fetching the data, while the view is waiting. No problems yet.
Now, the view model got the data, and I passed the collection of results to a property bound to the ItemSource property of the control. Nothing happens on the screen.
I go to MSDN and to find the officially approved way on how this situation is supposed to be handled (http://msdn.microsoft.com/en-us/library/system.windows.controls.autocompletebox.populating(v=vs.95).aspx):
Set the MinimumPrefixLength and MinimumPopulateDelay properties to
values larger than the default to minimize calls to the Web service.
Handle the Populating event and set the PopulatingEventArgs.Cancel
property to true.
Do the necessary processing and set the ItemsSource property to the
desired item collection.
Call the PopulateComplete method to signal the AutoCompleteBox to show
the drop-down.
Now I see a big problem with the last step because I don't know how I can call a method on a view from the view model, provided they don't know (and are not supposed to know!) anything about each other.
So how on earth am I supposed to get that PopulateComplete method of view called from the view model without breaking MVVM principles?
If you use Blend's Interactivity library, one option is an attached Behavior<T> for the AutoCompleteBox:
public class AsyncAutoCompleteBehavior : Behavior<AutoCompleteBox>
{
public static readonly DependencyProperty SearchCommandProperty
= DependencyProperty.Register("SearchCommand", typeof(ICommand),
typeof(AsyncAutoCompleteBehavior), new PropertyMetadata(null));
public ICommand SearchCommand
{
get { return (ICommand)this.GetValue(SearchCommandProperty); }
set { this.SetValue(SearchCommandProperty, value); }
}
protected override void OnAttached()
{
this.AssociatedObject.Populating += this.PopulatingHook;
}
protected override void OnDetaching()
{
this.AssociatedObject.Populating -= this.PopulatingHook;
}
private void PopulatingHook(object sender, PopulatingEventArgs e)
{
var command = this.SearchCommand;
var parameter = new SearchCommandParameter(
() => this.AssociatedObject
.Dispatcher
.BeginInvoke(this.AssociatedObject.PopulateComplete),
e.Parameter);
if (command != null && command.CanExecute(parameter))
{
// Cancel the pop-up, execute our command which calls
// parameter.Complete when it finishes
e.Cancel = true;
this.SearchCommand.Execute(parameter);
}
}
}
Using the following parameter class:
public class SearchCommandParameter
{
public Action Complete
{
get;
private set;
}
public string SearchText
{
get;
private set;
}
public SearchCommandParameter(Action complete, string text)
{
this.Complete = complete;
this.SearchText = text;
}
}
At this point you need to do 2 things:
Wire up the Behavior
<sdk:AutoCompleteBox MinimumPopulateDelay="250" MinimumPrefixLength="2" FilterMode="None">
<i:Interaction.Behaviors>
<b:AsyncAutoCompleteBehavior SearchCommand="{Binding Search}" />
</i:Interaction.Behaviors>
</sdk:AutoCompleteBox>
Create a DelegateCommand which handles your aysnc searching.
public class MyViewModel : ViewModelBase
{
public ICommand Search
{
get;
private set;
}
private void InitializeCommands()
{
this.Search = new DelegateCommand<SearchCommandParamater>(DoSearch);
}
private void DoSearch(SearchCommandParameter parameter)
{
var client = new WebClient();
var uri = new Uri(
#"http://www.example.com/?q="
+ HttpUtility.UrlEncode(parameter.SearchText));
client.DownloadStringCompleted += Downloaded;
client.DownloadStringAsync(uri, parameter);
}
private void Downloaded(object sender, DownloadStringCompletedEventArgs e)
{
// Do Something with 'e.Result'
((SearchCommandParameter)e.UserState).Complete();
}
}
With FubuMVC, I'm not sure what the best way is to determine the current action's output model type. I see different objects that I could get the current request's URL from. But that doesn't lead to a very good solution.
What's the easiest way to get the current action's output model type from the behavior?
If this isn't a good practice, what's a better way?
First, I'm assuming you've already got your settings object(s) set up in StructureMap and have the ISettingsProvider stuff already wired up.
The best, simplest thing to do would be just to pull the settings in the view, like this:
<%: Get<YourSettingsObject>().SomeSettingProperty %>
If you insist on having these be a property on your output model, then continue reading:
Let's say you had a settings object like this:
public class OutputModelSettings
{
public string FavoriteAnimalName { get; set; }
public string BestSimpsonsCharacter { get; set; }
}
Then you had an output model like this:
public class OutputModelWithSettings
{
public string SomeOtherProperty { get; set; }
public OutputModelSettings Settings { get; set; }
}
You'll need to do a few things:
Wire up StructureMap so that it will do setter injection for Settings objects (so it will automatically inject the OutputModelSettings into your output model's "Settings" property.
Set up a setter injection policy in your StructureMap initialization code (a Registry, Global ASAX, your Bootstrapper, etc -- wherever you set up your container).
x.SetAllProperties(s => s.Matching(p => p.Name.EndsWith("Settings")));
Create your behavior to call StructureMap's "BuildUp()" on the output model to trigger the setter injection. The behavior will be an open type (i.e. on the end) so that it can support any kind of output model
public class OutputModelSettingBehavior<TOutputModel> : BasicBehavior
where TOutputModel : class
{
private readonly IFubuRequest _request;
private readonly IContainer _container;
public OutputModelSettingBehavior(IFubuRequest request, IContainer container)
: base(PartialBehavior.Executes)
{
_request = request;
_container = container;
}
protected override DoNext performInvoke()
{
BindSettingsProperties();
return DoNext.Continue;
}
public void BindSettingsProperties()
{
var viewModel = _request.Find<TOutputModel>().First();
_container.BuildUp(viewModel);
}
}
Create a convention to wire up the behavior
public class OutputModelSettingBehaviorConfiguration : IConfigurationAction
{
public void Configure(BehaviorGraph graph)
{
graph.Actions()
.Where(x => x.HasOutput &&
x.OutputType().GetProperties()
.Any(p => p.Name.EndsWith("Settings")))
.Each(x => x.AddAfter(new Wrapper(
typeof (OutputModelSettingBehavior<>)
.MakeGenericType(x.OutputType()))));
}
}
Wire the convention into your FubuRegistry after the Routes section:
ApplyConvention<OutputModelSettingBehaviorConfiguration>();
In your view, use the new settings object:
<%: Model.Settings.BestSimpsonsCharacter %>
NOTE: I have committed this as a working sample in the FubuMVC.HelloWorld project in the Fubu source. See this commit: https://github.com/DarthFubuMVC/fubumvc/commit/2e7ea30391eac0053300ec0f6f63136503b16cca