I've started a project usinjg MS Unity as my IOC container and have two questions regarding overriding parameters.
public interface ITab
{
bool AllowVisible {get;set;}
}
class Tab : ITab
{
IViewModel vm;
public Tab(IViewModel vm)
{
this.vm = vm;
}
public bool allowVisible = false;
public bool AllowVisible
{
get{ return allowVisible};
set{ allowVisible = vlaue};
}
}
public interface IViewModule
{
string Name;
}
public class ViewModel
{
public string Name;
}
1) How do i set up the Tab type in unity so that i can pass in true or false to the AllowVisible property as a paramater? I dont want to have to add the
additional line of tab.AllowVisible = true; as in the case below
void Main()
{
ITab tab = unityContainer.RegisterType<ITab, Tab>();
tab.AllowVisible = true;
}
2) If i already have an instance of the ViewModel, such as vm in the case below, how do i make the container resolve the Tab object while passing in the vm object into its constructor? Currently when i resolve for the tab object, the container creates another instance of the ViewModel. I want the vm instance to get used as the tab objects viewmodel?
void Main()
{
unityContainer.RegisterType<IViewModel, ViewModel>();
unityContainer.RegisterType<ITab, Tab>();
ViewModel vm = unityContainer.Resolve<IViewModel>();
ITab tab = unityContainer.RegisterType<ITab, Tab>();
}
If you automatically want to assign a value to the AllowVisible property of your ITab implementation then you can use the InjectionProperty type provided by Unity.
You can do this in a fluent way, for example:
IUnityContainer myContainer = new UnityContainer();
myContainer.Configure<InjectedMembers>()
.ConfigureInjectionFor<MyObject>(
new InjectionProperty("MyProperty"),
new InjectionProperty("MyStringProperty", "SomeText"))
);
A bit more concrete:
container.RegisterType<ITab, Tab>();
container.RegisterType<ITab, Tab>(
new InjectionProperty("AllowVisible", true)
);
For more information on how to inject constructor parameters, property values...etc, check out:
http://msdn.microsoft.com/en-us/library/ff650036.aspx
As for the second part of you question, you must pass constructor parameters (IViewModel) to Unity's Resolve(...) method when you resolve an implementation for an ITab.
This question has been asked before on SO, check out:
Can I pass constructor parameters to Unity's Resolve() method?
For completeness' sake:
var viewModel = container.Resolve<IViewModel>();
container.Resolve<ITab>(new ParameterOverrides<Tab> { { "vm", viewModel} });"
Related
What I want to do:
I want do change a background color of a button from anywhere in my code (other classes Xamarin Forms). For example a button A in Page A changes the color of button B in Page B
on Windows you can use the MethodInvoker Delegat which isn't available on Android/iOS.
Can you give me a hint?
I tried it with the text of the buttons before with the MVVM approach.
in my PageB.xaml:
<Button Name="Button_B" Text="{Binding MyText}"/>
in my PageB.cs in public PageB
BindingContext = new MVVMPageB();
in my MVVMPageB.cs
private string myText;
public string MyText
{
get => mytring;
set
{
mystring = value;
PropertyChanged?
.Invoke(this, new PropertyChangedEventArgs(MyText)));
}
if i call:
MyText("Test");
in my MVVMPageB.cs it works fine. but i dont know how to access this from anywhere else.
i tried:
var Testobjekt = new MVVMPageB() //pretty sure thats not correct
Testobjekt.MyText("Test"); //wont work
Technique 1
This is a Singleton pattern for MVVMPageB.
This works if you never have two "Page B"s. IF there is a Page B on the navigation stack (so you can "Go Back" to it), and you display ANOTHER Page B, THEN this will not work well, because both Page B's will refer to the SAME MVVMPageB instance.
public class MVVMPageB : ...
{
// "Singleton": This is the only instance of MVVMPageB.
private static MVVMPageB _It;
public static MVVMPageB It
{
if (_It == null)
_It = new MVVMPageB();
return _It;
}
// Your constructor.
// It is private; only used via "It" getter above.
private MVVMPageB()
{
...
}
}
Code in another class, to access a member of the MVVMPageB.
MVVMPageB.It.MyText("Test");
Replace this code:
BindingContext = new MVVMPageB();
With this code:
BindingContext = MVVMPageB.It;
NOTE: Because MVVMPageB.It is static, if you go to Page B a second time, it will show the values you had last time (within the same app session).
Technique 2
A more robust approach, which works even if you create another Page B, requires having some way to pass the current instance of MVVMPageB to MVVMPageA or to PageA.
A complete example depends on exactly how/where you create each page. But this shows the idea.
public class MVVMPageB : ...
{
// Your constructor. Add parameters as needed.
public MVVMPageB()
{
...
}
}
public partial class PageB : ...
{
// Convenience property - our BindingContext is type MVVMPageB.
public MVVMPageB VMb => (MVVMPageB)BindingContext;
...
}
public class MVVMPageA : ...
{
// This is here, so both MVVMPageA and PageA can find it.
public MVVMPageB VMb;
}
public partial class PageA : ...
{
// Convenience property - our BindingContext is type MVVMPageA.
public MVVMPageA VMa => (MVVMPageA)BindingContext;
...
}
Code that creates Page B and then Page A:
var pageB = new PageB();
var pageA = new PageA();
// Tell MVVMPageA about MVVMPageB.
pageA.VMa.VMb = pageB.VMb;
Methods in MVVMPageA can now access members of MVVMPageB:
VMb.MyText("Test");
Methods in PageA can now access members of MVVMPageB:
VMa.VMb.MyText("Test");
NOTE: In this dynamic technique, if you go to Page B a second time (in the same app session), it will have a new instance of MVVMPageB.
You need a singleton viewModel for this use. I usually use one for the navbar.
So every scoped page viewModel references the singleton global viewModel inside:
PageAViewModel has property NavBarModel
PageBViewModel has property NavBarModel
and so on..
So it's obvious your button will be bind as
BackgroundColor={Binding NavBarModel.ActionColor} on every different page.
Now to have a singleton and obtain its reference i can see two ways: dependency injection (DI) or single instance creation. You can read a lot about DI on the net, while for a simple case you can have a single instance model with a prop like:
private NavBarModel _current;
public NavBarModel Current
{
get
{
if (_current == null)
_current = new NavBarModel();
return _current;
}
}
then in pages viewModels constructor set NavBarModel = NavBarModel.Current;
You would need DI though to reference more models inside your singleton, or/and make your code more reusable. Good luck.
I'm learning MVVM and I get really rough times understanding some of his concept. My question is how to show a child window which his view model accept to constructors one for adding new object which initialize the object with new object and an other for modifie the object which passed with the second constructor. and I don't know if my way using Unity is the right way.
So basically I have two questions:
Is the way I use unity right?
How to open child window that his view model have tow constructors, one for edite mode and an other for adding mode?
this my code in app.xaml.cs:
public partial class App : Application
{
public static readonly UnityContainer container = new UnityContainer();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
container.RegisterType<IMemberRepository, MemberRepository>();
container.RegisterType<ShellViewModel>();
var window = new Shell
{
DataContext = container.Resolve<ShellViewModel>()
};
window.ShowDialog();
}
}
and this is my code in SellViewModel for open child window:
public ShellViewModel()
{
NewMember = new DelegateCommand(ShowMethod);
}
private void ShowMethod()
{
var child = new AddMemberView
{
DataContext = App.container.Resolve<AddMemberViewModel>()
};
child.ShowDialog();
}
And this constructor in my child window:
public AddMemberViewModel(IMemberRepository repo, Member member = null)
{
_repo = repo;
SaveCommand = new DelegateCommand(SaveMember, CanSave);
if (member == null)
_Member = new Member();
else
_Member = member;
}
Edit: her I using only one constructors but in both cases how to passed the Member object in my case?
I think there is a misunderstanding of the usage of unity container, I suspect that you expect the unity container to act like a Factory pattern. Actually it is more like the Singleton pattern (not exactly the same but closer than Facotry).
I propose in your context to change a little bit your way to instantiate the view (and view model)
private void ShowMethod()
{
AddMemberViewModel vm = App.container.Resolve<AddMemberViewModel>();
vm.Member = new Member(); // Replace here with your Member object you want
// to edit
var child = new AddMemberView
{
DataContext = vm
};
child.ShowDialog();
}
You can also forget about registering this specific view model in the container (as it seems that it should not have a unique instance across your application) and use it lile this
private void ShowMethod()
{
IMemberRepository repo = App.container.Resolve<IMemberRepository>();
// Replace 'new Member()' here with your Member object you want to edit
AddMemberViewModel vm = new AddMemberViewModel(repo, new Member())
var child = new AddMemberView
{
DataContext = vm
};
child.ShowDialog();
}
I am tasked with implementing a simple confirmation dialog for trying to close the main window of my application, but only while a specific process is running. Closing the application means aborting that process. If that process is not running no confirmation is needed.
At first I just created an interface to get Application.Current.MainWindow and used the Closing event of that, but my teacher came up with something else, I just can't find the right way to finish it.
There already is a CanClose method in the code base I use, which is empty by default. It's in the AppInit class where the MainWindow is opened. What I did in the constructor is this:
[ImportingConstructor]
public AppInit(MainWindow mainWindow, [ImportMany] IEnumerable<IClosingValidator> closingValidator)
{
this.mainWindow = mainWindow;
this.closingValidator = closingValidator;
}
That was the idea of my teacher. Now in the CanClose method I can iterate through these closing validations and return true or false, like so:
public Boolean CanClose()
{
foreach (var validation in this.closingValidator)
{
var result = validation.CanClose();
if (result == false)
{
return false;
}
}
return true;
}
The ClosingValidator currently looks like the following, which I think is wrong, but I need to build the dialog somewhere. The whole thing works, but the flag indicating whether the process in question is running is in the ViewModel of another project, meaning this confirmation dialog is always shown.
[Export(typeof(IClosingValidator))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ClosingValidator : IClosingValidator
{
private readonly IMessageDialog messageDialog;
private readonly IOverlayEffect overlayEffect;
[ImportingConstructor]
public ClosingValidator(IMessageDialog messageDialog, IOverlayEffect overlayEffect)
{
this.messageDialog = messageDialog;
this.overlayEffect = overlayEffect;
}
public Boolean CanClose()
{
using (this.overlayEffect.Apply())
{
var result = this.messageDialog.ShowDialog(
new MessageDialogArgs(Resources.Resources.ConfirmClosingApplicationTitle,
Resources.Resources.ConfirmClosingApplicationMessage,
MessageBoxButton.YesNo,
MessageBoxImage.SystemQuestion));
if (result == MessageBoxResult.Yes)
{
return true;
}
}
return false;
}
I think my question comes down to this:
Where do I build my dialog and how do I use a boolean flag from the ViewModel to determine whether to show the dialog in the first place? I'm relatively new to MEF, so sorry anything's unclear.
Edit: I think the idea was that I can use that interface to implement further closing validations at some point in the future.
EDIT 3:
Simplified ViewModel implementation (The actual viewModel has too many parameters):
[Export]
public class TocViewModel
{
[ImportingConstructor]
public TocViewModel(MeasurementSequenceExecution measurementSequenceExecution)
{
this.measurementSequenceExecution = measurementSequenceExecution;
}
public Boolean CanApplicationClose
{
get { return !this.measurementSequenceExecution.IsMeasurementSequenceRunning; }
set
{
this.measurementSequenceExecution.IsMeasurementSequenceRunning = !value;
}
}
The IClosingValidator implementation:
[Export(typeof(IClosingValidator))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ClosingValidator : IClosingValidator
{
[ImportingConstructor]
public ClosingValidator()
{
}
public Boolean CanApplicationClose { get; }
The IClosingValidator interface:
public interface IClosingValidator
{
Boolean CanApplicationClose { get; }
}
The class handling the closing:
[Export(typeof(IApp))]
public class AppInit : IApp
{
[ImportingConstructor]
public AppInit(MainWindow mainWindow,
[ImportMany(typeof(IClosingValidator))] IEnumerable<IClosingValidator> closingValidatorClients,
IOverlayEffect overlayEffect,
IMessageDialog messageDialog)
{
this.mainWindow = mainWindow;
this.closingValidatorClients = closingValidatorClients;
this.overlayEffect = overlayEffect;
this.messageDialog = messageDialog;
}
public Boolean CanClose()
{
if (this.closingValidatorClients != null)
{
foreach (var client in this.closingValidatorClients)
{
if (!client.CanApplicationClose)
{
using (this.overlayEffect.Apply())
{
var result = this.messageDialog.ShowDialog(
new MessageDialogArgs(Resources.Resources.ConfirmClosingApplicationTitle,
Resources.Resources.ConfirmClosingApplicationMessage,
MessageBoxButton.YesNo,
MessageBoxImage.SystemQuestion));
if (result == MessageBoxResult.Yes)
{
return true;
}
return false;
}
}
}
}
return true;
}
private readonly IEnumerable<IClosingValidator> closingValidatorClients;
private readonly MainWindow mainWindow;
private readonly IMessageDialog messageDialog;
private readonly IOverlayEffect overlayEffect;
UPDATE
I made it work, the suggestion to Export the ViewModel with typeof(IClosingValidator) was right, I just had to add a second Export, not replace the default one, didn't know you could have 2 of them. So now with 2 ExportAttributes it works!
I think your implementation of IClosingValidator is at the wrong place.
I have a similar project and what I do is the following:
I have a Interface like you IClosingValidator:
public interface IConfirmShellClosing
{
/// <summary>
/// Gets a value that indicates whether the shell window can be closed.
/// </summary>
bool CanShellClose { get; }
}
This interface is implemented by all ViewModels which should be asked, if the shell can be closed. In your case all ViewModels where the process can be running should implement this interface. So each ViewModel for itself will implement the CanShellClose Property and decide if the process is running in this context. Only if all ViewModels return true for this, the Window can be closed.
Then, in your instance of the window, you can subscribe to the WindowClosing Event and ask all registered ViewModels, if the Window can be closed. This implementation goes to your ViewModel of the Window (or the code behind file):
[ImportMany(typeof(Lazy<IConfirmShellClosing>))]
private IEnumerable<Lazy<IConfirmShellClosing>> _confirmShellClosingClients;
private void ExecuteWindowClosing(CancelEventArgs args)
{
if (_confirmShellClosingClients != null)
{
foreach (var client in _confirmShellClosingClients)
{
if (!client.Value.CanShellClose)
{
// Show your Dialog here and handle the answer
}
}
}
}
I hope this will help.
EDIT:
Okay, you still have a few mistakes in the implementation of your ViewModel and the Validator.
First of all, the Class ClosingValidator is not needed anymore. You want to give the responsibility to the ViewModels, and not to a central validator class.
After that, we need to change the ViewModel like this:
[Export]
public class TocViewModel : IClosingValidator
{
[ImportingConstructor]
public TocViewModel(MeasurementSequenceExecution measurementSequenceExecution)
{
this.measurementSequenceExecution = measurementSequenceExecution;
}
public Boolean CanApplicationClose
{
get { return !this.measurementSequenceExecution.IsMeasurementSequenceRunning; }
set
{
this.measurementSequenceExecution.IsMeasurementSequenceRunning = !value;
}
}
What is happening now? The ViewModel implements the IClosingValidator Interface, so it has to implement the CanShellClose Property. In this property, you can define the logic, in which this ViewModel can decide if the Shell can be closed or not. If you ever add an other ViewModel, it can implement the interface as well and has a different logic for the closing.
In the Importing in the Application itself, all classes which are implementing the IClosingValidator interface are imported and then asked, if the porperty is true or false.
EDIT 2:
I have 3 .dll (HelpingProject, InterfaceProject and ViewModelProject) All 3 should be in the directory where the compositionContainer is searching. In my case, I built the container like this:
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(#"C:\Projects\HelpingSolution\HelpingWpfProject\bin\Debug"));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
So the Debug folder will be scanned to find matching exports. I would recommend you search for the this code in your codebase and have a look at where your container is looking for the exports. This does not have to be only one folder, but can also be a plugin folder or different locations. You can find a pretty good introduction here.
Is it possible to modify properties of a ViewModel by using an instance of ViewModelLocator elsewhere in the code? When I try, any value I try to assign seems to be discarded.
For example, a ViewModel, an instance of which named "Game" is contained in my ViewModelLocator. It has a string property named "Test". When I try to modify it this way:
(App.Current.Resources["Locator"] as ViewModelLocator).Game.Test = "Testing";
System.Windows.MessageBox.Show((App.Current.Resources["Locator"] as ViewModelLocator).Game.Test);
or
ViewModelLocator _viewModelLocator = new ViewModelLocator();
_viewModelLocator.Game.Test = "Testing";
System.Windows.MessageBox.Show(_viewModelLocator.Game.Test);
The messageboxes display the value of the string declared in the ViewModel itself if there is one. If a value hasn't been assigned in the ViewModel, the messageboxes show up empty. Either way, they don't display "Testing".
How can I make this work? I'm using MVVM Light with Unity.
public class ViewModelLocator
{
private static Bootstrapper _bootstrapper;
static ViewModelLocator()
{
if (_bootstrapper == null)
_bootstrapper = new Bootstrapper();
}
public GameViewModel Game
{
get { return _bootstrapper.Container.Resolve<GameViewModel>(); }
}
}
public class Bootstrapper
{
public IUnityContainer Container { get; set; }
public Bootstrapper()
{
Container = new UnityContainer();
ConfigureContainer();
}
private void ConfigureContainer()
{
Container.RegisterType<GameViewModel>();
}
}
It looks like this is a problem with Unity. I switched back to MVVM Light's SimpleIoc and it works without a hitch.
When you call Container.RegisterType<GameViewModel>(); this registers the type GameViewModel with the default lifetime manager. The default lifetime manager for the RegisterType method is the TransientLifetimeManager which means that every time Resolve is called a new instance is returned.
So every time the Game property is called a new instance of GameViewModel is returned. Any modifications to the object will only be made to that object (and lost when the object is GC'd). The next time the Game property is called a new instance of GameViewModel is returned.
So, assuming you only want one GameViewModel you should register it as a singleton:
private void ConfigureContainer()
{
Container.RegisterType<GameViewModel>(new ContainerControlledLifetimeManager());
}
I am using MVC 2.
I have 5 view models and each has different message properties that i need to populate from the DB. The property names are different, per the message type.
In the view models, i have type available, for which i need to pull the messages.
If the type is Welcome, then i want to pull the 3 welcome messages.
I want to write a generic function that i will call from each action. This generic function will then look at the object being passed and its type property and then will fill the message properties specified in this view model. How can i accomplish this? From my actions, i don't want to call a separate function for each messages type.
I am trying to do some thing like following:
public void GetMessage(object viewModel, bool isCheckMessages)
{
viewModel = (AnnualReportWelComeViewModel)viewModel;
}
But the viewModel in this instance is not picking properties specified in AnnualReportWelComeViewModel.
Am i thinking straight here or just making it way over complicated than it needs to be?
The problem with your code is that you are reusing same variable when converting types. Your viewModel is of type object and even if you try to convert it to another type you will still see it as object. You should try something along these lines:
public void GetMessage(object viewModel, bool isCheckMessages)
{
var reportMessage = viewModel as AnnualReportWelComeViewModel;
if (reportMessage != null)
{
// viewModel passed was of type AnnualReportWelComeViewModel
}
}
If you want this function to check for many possible types of viewModel then you could do something like:
public void GetMessage(object viewModel, bool isCheckMessages)
{
if (viewModel is AnnualReportWelComeViewModel)
{
var reportMessage = viewModel as AnnualReportWelComeViewModel;
// ...
}
else if (viewModel is MonthlyReportWelComeViewModel)
{
var reportMessage = viewModel as MonthlyReportWelComeViewModel;
// ...
}
}
You should create interface IViewModelWithMessage and inherit all your viewmodel from it.
public interface IViewModelWithMessage
{
string Message { get; set; }
}
In your viewmodels you should map inherited property Message to this model messages:
public class AnnualReportWelComeViewModel : IViewModelWithMessage
{
public string ViewModelMessage { get; set; }
....
string IViewModelWithMessage.Message {
get { return ViewModelMessage; }
set { ViewModelMessage = value; }
}
}
Do what you want with Message property of interface and this value will be passed to other properties.
public void GetMessage(IViewModelWithMessage viewModel, bool isCheckMessages)
{
...
viewmodel.Message = ...
}