I read the documentation how can communicate with view models and I try it. But is can't changed. Like the examples from github I create CommunicationViewModel:
public abstract class CommunicationViewModel : ViewModelBase
{
public CommunicationViewModel()
{
ExampleCommand = new Command(OnExampleCommandExecute);
}
public string Property
{
get { return GetValue<string>(PropertyProperty); }
set { SetValue(PropertyProperty, value); }
}
public static readonly PropertyData PropertyProperty = RegisterProperty("Property", typeof(string), null);
public void UpdateProperty(string modifier)
{
Property = modifier.ToString();
}
public Command ExampleCommand { get; private set; }
private void OnExampleCommandExecute()
{
ExecuteCommand();
}
protected abstract void ExecuteCommand();
protected void AddPropertyChange(string propertyName, Type senderType)
{
Argument.IsNotNull(() => propertyName);
Argument.IsNotNull(() => senderType);
// do some stuff
}
}
Then from OpenViewModel I want to send information to MainWindowViewModel.
The OpenViewModel Source code:
public class OpenViewModel : CommunicationViewModel
{
private readonly IMessageService iMessage;
private readonly IPleaseWaitService iPleaseWait;
private readonly ILanguageService iLanguage;
public OpenViewModel(IMessageService iMessageService, IPleaseWaitService iPleaseWaitService, ILanguageService iLanguageService)
{
Argument.IsNotNull(() => iMessageService);
Argument.IsNotNull(() => iPleaseWaitService);
Argument.IsNotNull(() => iLanguageService);
iMessage = iMessageService;
iPleaseWait = iPleaseWaitService;
iLanguage = iLanguageService;
ChooseDatabaseForOpen = new Command(HandleChooseDatabaseForOpen);
}
public ObservableCollection<string> Databases
{
get { return new ObservableCollection<string>(SettingGenerator.ReadFewSameDatabaseSettings("Databases", "Database", "Value")); }
set { SetValue(DatabasesProperty, value); }
}
public static readonly PropertyData DatabasesProperty = RegisterProperty("Databases", typeof(ObservableCollection<string>), () => new ObservableCollection<string>());
public Command ChooseDatabaseForOpen { get; private set; }
protected override async Task InitializeAsync()
{
await base.InitializeAsync();
// TODO: subscribe to events here
}
protected override async Task CloseAsync()
{
// TODO: unsubscribe from events here
await base.CloseAsync();
}
private void HandleChooseDatabaseForOpen()
{
iPleaseWait.Show(iLanguage.GetString("PleaseWaitMessage"));
SettingGenerator.ChangeDatabaseSetting(null, "ActiveDatabase", this.Database);
iPleaseWait.Hide();
}
protected override void ExecuteCommand()
{
// InterestedIn does not required any custom logic
}
}
And the MainWindowViewModel
[InterestedIn(typeof(OpenViewModel))]
public class MainWindowViewModel : CommunicationViewModel
{
private const string PATH_TO_CHECKED_IMAGE = #"./../Resources/Images/Icons/Settings/Checked Checkbox.png";
private const string PATH_TO_UNCHECKED_IMAGE = #"./../Resources/Images/Icons/Settings/Checkbox.png";
private readonly IUIVisualizerService iUIVisualizer;
private readonly IPleaseWaitService iPleaseWait;
private readonly IMessageService iMessage;
private readonly IOpenFileService iOpenFile;
private readonly ILanguageService iLanguage;
private readonly ISplashScreenService iSplashScreen;
private readonly string settingFile = AppDomain.CurrentDomain.BaseDirectory + FileConstants.PATH_TO_SETTINGS_FILE;
public MainWindowViewModel(IUIVisualizerService iUIVisualizerService, IPleaseWaitService iPleaseWaitService, IMessageService iMessageService, IOpenFileService iOpenFileService, ILanguageService iLanguageService, ISplashScreenService iSplashScreenService)
{
Argument.IsNotNull(() => iOpenFileService);
Argument.IsNotNull(() => iUIVisualizerService);
Argument.IsNotNull(() => iMessageService);
Argument.IsNotNull(() => iPleaseWaitService);
Argument.IsNotNull(() => iLanguageService);
Argument.IsNotNull(() => iSplashScreenService);
this.iOpenFile = iOpenFileService;
this.iUIVisualizer = iUIVisualizerService;
this.iMessage = iMessageService;
this.iPleaseWait = iPleaseWaitService;
this.iLanguage = iLanguageService;
this.iSplashScreen = iSplashScreenService;
Minimize = new Command(HandleMinimizeCommand);
Restore = new Command(HandleRestoreCommand);
CloseApplication = new Command(HandleCloseApplicationCommand);
if (SettingGenerator.ReadDatabaseSetting(null, "Update") == "Automatic")
{
this.AutoUpgradeCheckbox = PATH_TO_CHECKED_IMAGE;
}
else if (SettingGenerator.ReadDatabaseSetting(null, "Update") == "Manual")
{
this.AutoUpgradeCheckbox = PATH_TO_UNCHECKED_IMAGE;
}
SelectIconForMainMenuItems();
//this.Property = this.Database;
}
protected override void OnViewModelPropertyChanged(IViewModel viewModel, string propertyName)
{
AddPropertyChange(propertyName, viewModel.GetType());
}
protected override void ExecuteCommand()
{
// InterestedIn does not required any custom logic
}
}
And from xaml
The OpenView
<ListView Name="DatabasesListView" MinHeight="150" Width="200" ItemsSource="{Binding Databases}" SelectedItem="{Binding Property}" />
The MainWindowView
<Label Content="{Binding Property}" HorizontalAlignment="Center" Margin="0 0 0 0" FontSize="14" />
How can communicate this two view models. In example is working in my case is not working. When I change Property everytime is null. Is not give me some exception just everytime is null.
EDIT: I used two DataWindows, not using UserControls
As the documentation explains, there are several ways to communicate between view models:
Services
InterestedIn (what you are using)
MessageMediator
It looks like what you are doing is correct. If you feel it is not working as expected, please provide a repro in the official tracker.
Related
So in this datagrid and then I want double click on a datarow and then pass the selecteditem to the next user control.
I'm using a navigation service which i have seen on SingletonSean.
However, I'm not sure how to combine those 2 things.
My DoubleClickCommand is for sending my selecteditem to the next screen.
The cmdNavigatePerson is for opening the screen.
View:
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding DoubleClickCommand}"
CommandParameter="{Binding ElementName=dgResults, Path=SelectedItem}" />
</DataGrid.InputBindings>
ViewModel:
public ICommand DoubleClickCommand { get; set; }
public ICommand cmdNavigatePerson { get; }
public clsPersonVM(IGenericNavigationService<clsPersonVM> personNavigationService)
{
cmdNavigatePerson = new cmdNavigation<clsPersonVM>(personNavigationService);
MijnService = new clsPersonDataService();
DoubleClickCommand = new clsCustomCommand(Execute_DoubleClickCommand, CanExecute_DoubleClickCommand);
LoadData();
clsMessenger.Default.Register<clsUpdateListMessages>(this, OnUpdateListMessageReceived);
}
private bool CanExecute_DoubleClickCommand(object obj)
{
return true;
}
private void Execute_DoubleClickCommand(object obj)
{
clsMessenger.Default.Send<clsPersonsM>(MijnSelectedItem);
}
private void OnUpdateListMessageReceived(clsUpdateListMessages obj)
{
LoadData();
}
CmdNavigation:
public class cmdNavigation<TViewModel> : clsCommandBase
where TViewModel : ViewModelBase
{
private readonly clsNavigationStore _clsNavigationStore;
private readonly Func<TViewModel> _createViewModel;
private readonly IGenericNavigationService<TViewModel> _navigationService;
public cmdNavigation(clsNavigationStore clsNavigationStore, Func<TViewModel> createViewModel)
{
_clsNavigationStore = clsNavigationStore;
_createViewModel = createViewModel;
}
public cmdNavigation(IGenericNavigationService<TViewModel> navigationService)
{
_navigationService = navigationService;
}
public override void Execute(object parameter)
{
_navigationService.Navigate();
}
}
In the XAML i've tried to put 2 mousebindings with the same MouseAction but it only performs the last one.
I use MvvmCross with Xamarin Form.
Therefore, I use RaisePropertyChanged to notify View.
However, RaisePropertyChanged does not fire propertyChanged in ViewA.
I do not know where to start to debug or check local variables...
Flow
If I change Data.Value somewhere, flow is like below.
event Data.ValueChanged invoked.
ModelA.OnValueChanged calls OnPropertyChanged
ViewModelA.OnModelPropertyChanged calls RaisePropertyChanged
expect ViewA.OnChanged called, but fail...
XAML
I run and check if XAML binding is working.
<DataTemplate x:Key="ViewB">
<ViewB Data="{Binding Data}" />
</DataTemplate>
View
I defined BindableProperty as below.
// this class is abstract!
public abstract class ViewA : MvxContentView
{
public static readonly BindableProperty DataProperty =
BindableProperty.Create(
propertyName: "Property",
returnType: typeof(Data),
declaringType: typeof(ViewA),
defaultValue: null,
propertyChanged: OnChanged);
static void OnChanged(BindableObject bindable, object oldValue, object newValue)
{
if (newValue is null) { return; }
// some codes
}
}
// actual class
public partial class ViewB : ViewA
{
public ViewB()
{
InitializeComponent();
}
}
ViewModel
// this is also abstract!
public abstract class ViewModelA<T> : MvxViewModel<T>
{
protected T _model;
public Data Data
{
get => _model.Data;
}
public T Model
{
get => _Model;
set
{
if (SetProperty(ref _model, value))
{
// Register event handler
_model.PropertyChanged += OnModelPropertyChanged;
}
}
}
private void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "DataChanged":
{
// I expect this will fire 'propertyChanged' of BindableProperty.
// But it is not fired...
RaisePropertyChanged(() => Data);
}
break;
}
}
}
// actual class
public class ViewModelB : ViewModelA<ModelA>
{
public ViewModelB() : base()
{
}
}
Model
public class LayerModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private Data _data;
public Data Data
{
get
{
return _data;
}
set
{
if (_data != value)
{
_data = value;
_data.ValueChanged += OnValueChanged;
OnPropertyChanged("DataChanged");
}
}
}
private void OnValueChanged(object sender, EventArgs e)
{
OnPropertyChanged(""DataChanged"");
}
}
Data
public class Data
{
private int _value;
public int Value
{
get => _value;
set
{
if(_value != value)
{
// 2020.07.06 Edited
var evetArg = new DataChangedArgs
{
OldData = _value;
NewData = value;
};
_value = value;
ValueChanged?.Invoke(this, evetArg);
}
}
}
public event EventHandler ValueChanged;
}
2020.07.06 Added
public class DataChangedArgs : EventArgs
{
public int OldData { get; set; }
public int NewData { get; set; }
}
I suggest you to simplify some classes. You created a custom event named ValueChanged, its basically the same INotifyPropertyChanged. It's recommend you to use the interface, there are a class MvxNotifyPropertyChanged (in MvvmCross) that implement the previous interface.
Here is an approximation (This ViewModel does not inherit from MvxViewModel but as I said, it's an approximation).
***EDIT Question update: The class Data can't be modified.
You are trying to bind Data with a BindableProperty, in this case the Data should implement the INotifyPropertyChanged to notify the changes. I recommend you to read about BindableObject.
In this case I propose the following solution:
Create a class inheriting from Data: it will implement the INotifyPropertyChanged to notify the value changes.
The structure should look similar to:
...
public class MainPageViewModel : MvxNotifyPropertyChanged
{
public Data Data => _model?.Data;
private LayerModel _model;
public LayerModel Model
{
get => _model;
set
{
SetProperty(ref _model, value, () =>
{
RaisePropertyChanged(nameof(Data));
});
}
}
}
public class LayerModel : MvxNotifyPropertyChanged
{
private Data _data;
public Data Data
{
get => _data;
set => SetProperty(ref _data, value);
}
}
public class Data
{
private int _value;
public int Value
{
get => _value;
set
{
if (_value == value)
return;
var eventArgs = new DataChangedEventArgs(_value, value);
_value = value;
ValueChanged?.Invoke(this, eventArgs);
}
}
public event EventHandler<EventArgs> ValueChanged;
}
public class DataChangedEventArgs : EventArgs
{
public DataChangedEventArgs(int oldData, int newData)
{
OldData = oldData;
NewData = newData;
}
public int OldData { get; }
public int NewData { get; }
}
public class NotificationData : Data, INotifyPropertyChanged
{
public static NotificationData FromData(Data data)
{
return new NotificationData {Value = data.Value};
}
public NotificationData()
{
ValueChanged += delegate
{
OnPropertyChanged(nameof(Value));
};
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
...
The following is an demo with an abstract view, and a child view consuming the MainPageViewModel.
The abstract view should look similar to
public abstract class ViewA : ContentPage
{
public Data ModelA
{
get => (Data)GetValue(ModelAProperty);
set => SetValue(ModelAProperty, value);
}
public static readonly BindableProperty ModelAProperty =
BindableProperty.Create(
propertyName: nameof(ModelA),
returnType: typeof(Data),
declaringType: typeof(ViewA),
defaultValue: null,
propertyChanged: OnChanged);
static void OnChanged(BindableObject bindable, object oldValue, object newValue)
{
if (newValue is null) { return; }
// some codes
}
}
The child view.
Code behind:
...
public partial class MainPage : ViewA
{
private int _counter = 10;
private MainPageViewModel ViewModel => (MainPageViewModel) BindingContext;
public MainPage()
{
InitializeComponent();
//--example initialization
var data = new Data
{
Value = _counter
};
ViewModel.Model = new LayerModel
{
Data = NotificationData.FromData(data)
};
}
private void Button_OnClicked(object sender, EventArgs e)
{
ViewModel.Model.Data.Value = ++_counter;
}
}
...
XAML:
<?xml version="1.0" encoding="utf-8" ?>
<xamstack:ViewA xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xamstack="clr-namespace:xamstack;assembly=xamstack"
mc:Ignorable="d"
x:Class="xamstack.MainPage"
ModelA="{Binding Data}">
<ContentPage.BindingContext>
<xamstack:MainPageViewModel/>
</ContentPage.BindingContext>
<StackLayout Padding="20">
<Button Text="Increment" Clicked="Button_OnClicked"/>
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="Value: "/>
<Span Text="{Binding Path=ModelA.Value, Mode=OneWay, Source={RelativeSource AncestorType={x:Type ContentPage}}}"
TextColor="Red"
/>
</FormattedString>
</Label.FormattedText>
</Label>
</StackLayout>
</xamstack:ViewA>
I hope it match your case.
experts! I'm new in learning WPF and MVVM. I decided to develop some small WPF application. So I have a LoginViewModel which interacts with Database. Once the login operation is successful I need to hide the LoginView and display the MainView. I'm using the Caliburn.Micro for these purposes. But I have got a problem - I don't know how can I hide the LoginView and show the MainView. I would be appreciated if someone would help me to solve this problem :(
I've already tried to use the following actions:
- Using the IEventAggregator _events: _events.PublishOnUIThread("message"), but no result :(
- Using the Conductor.Collection.OneActive and ActiveteItem()/DeactivateItem() methods, but still no result :(
Here is my Bootstrapper class:
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
public Bootstrapper()
{
Initialize();
ConventionManager.AddElementConvention<PasswordBox>(
PasswordBoxHelper.BoundPasswordProperty,
"Password",
"PasswordChanged");
}
protected override void Configure()
{
_container.Instance(_container);
_container
.Singleton<IWindowManager, WindowManager>()
.Singleton<IApiHelper, ApiHelper>()
.Singleton<IEventAggregator, EventAggregator>()
.Singleton<ILoggedInUserModel, LoggedInUserModel>();
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(
viewModelType, viewModelType.ToString(), viewModelType));
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
return _container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
Also here is my LoginViewModel:
public class LoginViewModel : Conductor<IScreen>.Collection.OneActive
{
private IWindowManager _windowManager;
private IApiHelper _apiHelper;
private ILoggedInUserModel _user;
private IEventAggregator _events;
private string _login = "test";
private string _password = "test";
private bool _isAdmin;
public ShellViewModel(IWindowManager windowManager, IApiHelper apiHelper, ILoggedInUserModel user, IEventAggregator events)
{
_windowManager = windowManager;
_apiHelper = apiHelper;
_user = user;
_events = events;
}
public string Login
{
get { return _login; }
set
{
_login = value;
NotifyOfPropertyChange(() => Login);
NotifyOfPropertyChange(() => CanPerformLogin);
}
}
public string Password
{
get { return _password; }
set
{
_password = value;
NotifyOfPropertyChange(() => Password);
NotifyOfPropertyChange(() => CanPerformLogin);
}
}
public bool IsAdmin
{
get { return _isAdmin; }
set
{
_isAdmin = value;
NotifyOfPropertyChange(() => IsAdmin);
NotifyOfPropertyChange(() => CanPerformLogin);
}
}
public bool CanPerformLogin
{
get
{
bool output = false;
if (Login?.Length > 0 && Password?.Length > 0)
{
output = true;
}
return output;
}
}
public ShellViewModel(IApiHelper apiHelper, IWindowManager windowManager)
{
_windowManager = windowManager;
_apiHelper = apiHelper;
}
public void OpenSignUpView()
{
_windowManager.ShowWindow(new SignUpViewModel());
}
public async Task PerformLogin()
{
//Here I need to hide my LoginView and show the ShellView
}
public void CloseForm()
{
TryClose();
}
And here is my ShellViewModel:
public class ShellViewModel : Screen, IHandle<LogOnEvent>
{
private IWindowManager _windowManager;
private IApiHelper _apiHelper;
private ILoggedInUserModel _user;
private IEventAggregator _events;
public SelectYourTreeViewModel(IWindowManager windowManager, IApiHelper apiHelper, ILoggedInUserModel user, IEventAggregator events)
{
_windowManager = windowManager;
_apiHelper = apiHelper;
_user = user;
_events = events;
_events.Subscribe(this);
}
public void Handle(LogOnEvent message) //Here I've tried to use events to show/or hide view
{
System.Windows.MessageBox.Show("Some text!");
}
Can you please show the structure of the WindowManager class. ? You can open a window in WPF like this:
new LoginView().Show();
And so close it again.
myAlreadyOpenedLoginView.Close().
Here LoginView must be a window and inherit from the class Window.
I have a class UserManagement and always want to get the same instance (like singleton pattern). So my problem is now, that I always get a new instance insted of the named "Singleton". I'm new to StructureMap. I have tried both version (http://structuremap.github.io/object-lifecycle/) (http://structuremap.github.io/glossary/) described on the website.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Container container = new Container(new UserManagement());
//UserManagement instance = container.GetInstance<UserManagement>("Singleton");
UserManagement instance = container.GetInstance<UserManagement>();
MessageBox.Show(instance.Test);
}
}
public interface IUser
{
void DoSomethingFancy();
}
public class User : IUser
{
public void DoSomethingFancy()
{
}
}
public interface IDatabase
{
void DoSomething();
}
public class Database : IDatabase
{
public void DoSomething()
{
}
}
public class UserManagement : Registry
{
public string Test;
private Container _Container;
private IUser _User;
private IDatabase _Database;
public UserManagement()
{
_Container = new Container(_ =>
{
_.For<IUser>().Use<User>();
_.For<IDatabase>().Use<Database>();
});
_User = _Container.GetInstance<IUser>();
_Database = _Container.GetInstance<IDatabase>();
Test = DateTime.Now.ToString();
//For<UserManagement>().AddInstances(x =>
//{
// x.Type<UserManagement>().Named("Singleton");
//});
For<UserManagement>().Singleton();
}
}
Update
My solution has 3 projects
BootstrapperLibrary (class library - Bootstrapper)
Gui (WpfApp - MainWindow.xaml)
Framework (class library - LoginAction - UserManagement)
BootstrapperLibrary
Bootstrapper.cs
public static class Bootstrapper
{
public static Container Container => _Container ?? (_Container = InitializeContainer());
private static Container _Container;
public static Func<Container> InitializeContainer;
}
Gui
App.xaml.cs
public partial class App : Application
{
public App()
{
Bootstrapper.InitializeContainer += InitializeContainer;
}
private Container InitializeContainer()
{
Container container = new Container(c => c.Scan(scanner =>
{
scanner.TheCallingAssembly();
scanner.WithDefaultConventions();
scanner.AssembliesFromApplicationBaseDirectory();
}));
container.Configure(c =>
{
c.ForSingletonOf<UserManagement>();
c.For<IUser>().Use<UserAdv>();
}
);
return container;
}
}
MainWindow.xaml
<Window x:Class="Gui.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:myFramework="clr-namespace:MyFramework;assembly=MyFramework"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="383,228,0,0" VerticalAlignment="Top" Width="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<myFramework:LoginAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</Window>
Framework
LoginAction.cs
public class LoginAction : TriggerAction<DependencyObject>
{
protected override void Invoke(object parameter)
{
UserManagement userManagement = Bootstrapper.Container.GetInstance<UserManagement>();
Console.WriteLine(userManagement.Test); //Just to see if it's still the same instance who holds for example the current user.
}
}
UserManagement.cs
public interface IUser
{
string Name { get; }
void DoSomethingFancy();
}
public class User : IUser
{
public string Name { get; } = "default";
public void DoSomethingFancy()
{
}
}
public class UserAdv : IUser
{
public string Name { get; } = "Advanced";
public void DoSomethingFancy()
{
}
}
public interface IDatabase
{
void DoSomething();
}
public class Database : IDatabase
{
public void DoSomething()
{
}
}
public class UserManagement
{
public string Test;
public IUser User;
private IDatabase _Database;
public UserManagement(IUser user, IDatabase database)
{
User = user;
_Database = database;
Test = DateTime.Now.ToString();
}
}
If I understood correctly, my main application now initializes and configures the Container within the application startup. The Bootstrapper class is holding a static instance there. MyFramework fetches an instance of UserManangement from the static Container instance.
Is this the correct way how to use StructureMap? Thank you for your patience!
You should declare:
ForConcreteType<UserManagement>().Singleton();
Edit:
You may implement a bootstrapper class and call the Initialize() method during program startup:
public sealed class Bootstrapper
{
private static StructureMap.Container _container;
public StructureMap.Container MyContainer
{
get { return _container; }
}
static Bootstrapper()
{
}
public static Initialize()
{
StructureMap.Configuration.DSL.Registry registry = new StructureMap.Configuration.DSL.Registry();
registry.For<IUser>().Use<User>();
registry.For<IDatabase>().Use<Database>();
registry.ForConcreteType<UserManagement>().Singleton();
_container = new Container(registry);
}
}
Remove the container initialization from the UserManagement class and let the DI framework inject the concrete types/objects.
public class UserManagement
{
public string Test;
private IUser _user;
private IDatabase _database;
public UserManagement(IUser user, IDatabase database)
{
_user = user;
_database = database;
Test = DateTime.Now.ToString();
}
}
Modify your WPF test window:
public partial class MainWindow : Window
{
static MainWindow()
{
// only for demonstration!!!
Bootstrapper.Initialize();
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Container container = Bootstrapper.MyContainer;
UserManagement instance = container.GetInstance<UserManagement>();
MessageBox.Show(instance.Test);
}
}
I've got a VM class below that's being used to wire up a view to my ADO.NET Entity Data Model, utilizing a P_BUDGET class. This works fine, gets my data, makes everything pretty. I have about 15-20 pages that are all going to be based on the same structure as this, and the code is going to be virtually identical except for the EntityType (P_BUDGET, P_ACCOUNT, P_CIRCUIT, etc etc). I feel like there should be a way to abstract this out, but I tried, and failed miserably! I also feel like I should be able to use one view, one viewmodel, and just swap out the entities binding to the GV... I just haven't been able to find a way to variablize the type, which permeates the entire viewmodel.
Appreciate your help,
Scott
public class TestViewModel : ViewModelBase
{
private readonly ODADomainContext _context = new ODADomainContext();
private readonly DomainCollectionView<P_BUDGET> _view;
private readonly DomainCollectionViewLoader<P_BUDGET> _loader;
private readonly EntityList<P_BUDGET> _source;
private bool _isGridEnabled;
/// <summary>
/// Initializes a new instance of the TestViewModel class.
/// </summary>
public TestViewModel()
{
this._source = new EntityList<P_BUDGET>(this._context.P_BUDGETs);
this._loader = new DomainCollectionViewLoader<P_BUDGET>(
this.LoadSampleEntities,
this.OnLoadSampleEntitiesCompleted);
this._view = new DomainCollectionView<P_BUDGET>(this._loader, this._source);
INotifyCollectionChanged notifyingSortDescriptions =
(INotifyCollectionChanged)this.CollectionView.SortDescriptions;
notifyingSortDescriptions.CollectionChanged +=
(sender, e) => this._view.MoveToFirstPage();
using (this.CollectionView.DeferRefresh())
{
this._view.PageSize = 10;
this._view.MoveToFirstPage();
}
}
#region View Properties
public bool IsGridEnabled
{
get
{
return this._isGridEnabled;
}
private set
{
if (this._isGridEnabled != value)
{
this._isGridEnabled = value;
this.RaisePropertyChanged("IsGridEnabled");
}
}
}
public ICollectionView CollectionView
{
get { return this._view; }
}
#endregion
private LoadOperation<P_BUDGET> LoadSampleEntities()
{
this.IsGridEnabled = false;
return this._context.Load(
this._context.GetBudgetsQuery());
}
private void OnLoadSampleEntitiesCompleted(LoadOperation<P_BUDGET> op)
{
this.IsGridEnabled = true;
if (op.HasError)
{
// TODO: handle errors
_view.PageSize = 0;
op.MarkErrorAsHandled();
}
else if (!op.IsCanceled)
{
this._source.Source = op.Entities;
_view.PageSize = 10;
this._view.MoveToFirstPage();
if (op.TotalEntityCount != -1)
{
this._view.SetTotalItemCount(op.TotalEntityCount);
}
}
}
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
}
Try something like this. This is not tested (obviously), not even complied. This also assumes that the EntityTypes (P_BUDGET, P_ACCOUNT, P_CIRCUIT etc.) are not POCOs.
public class TestViewModel<TEntity> : ViewModelBase
{
private readonly ODADomainContext _context = new ODADomainContext();
private readonly DomainCollectionView<TEntity> _view;
private readonly DomainCollectionViewLoader<TEntity> _loader;
private readonly EntityList<TEntity> _source;
private bool _isGridEnabled;
/// <summary>
/// Initializes a new instance of the TestViewModel class.
/// </summary>
public TestViewModel()
{
this._source = new EntityList<TEntity>(this._context.GetEntitySet<TEntity>);
this._loader = new DomainCollectionViewLoader<TEntity>(
this.LoadSampleEntities,
this.OnLoadSampleEntitiesCompleted);
this._view = new DomainCollectionView<TEntity>(this._loader, this._source);
INotifyCollectionChanged notifyingSortDescriptions =
(INotifyCollectionChanged)this.CollectionView.SortDescriptions;
notifyingSortDescriptions.CollectionChanged +=
(sender, e) => this._view.MoveToFirstPage();
using (this.CollectionView.DeferRefresh())
{
this._view.PageSize = 10;
this._view.MoveToFirstPage();
}
}
#region View Properties
public bool IsGridEnabled
{
get
{
return this._isGridEnabled;
}
private set
{
if (this._isGridEnabled != value)
{
this._isGridEnabled = value;
this.RaisePropertyChanged("IsGridEnabled");
}
}
}
public ICollectionView CollectionView
{
get { return this._view; }
}
#endregion
private LoadOperation<TEntity> LoadSampleEntities()
{
this.IsGridEnabled = false;
return this._context.Load(
this._context.GetBudgetsQuery());
}
private void OnLoadSampleEntitiesCompleted(LoadOperation<TEntity> op)
{
this.IsGridEnabled = true;
if (op.HasError)
{
// TODO: handle errors
_view.PageSize = 0;
op.MarkErrorAsHandled();
}
else if (!op.IsCanceled)
{
this._source.Source = op.Entities;
_view.PageSize = 10;
this._view.MoveToFirstPage();
if (op.TotalEntityCount != -1)
{
this._view.SetTotalItemCount(op.TotalEntityCount);
}
}
}
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
}
// http://blog.zoolutions.se/post/2010/04/05/Generic-Repository-for-Entity-Framework-for-Pluralized-Entity-Set.aspx
public static class ObjectContextExtensions
{
internal static EntitySetBase GetEntitySet<TEntity>(this ObjectContext context)
{
EntityContainer container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
Type baseType = GetBaseType(typeof(TEntity));
EntitySetBase entitySet = container.BaseEntitySets
.Where(item => item.ElementType.Name.Equals(baseType.Name))
.FirstOrDefault();
return entitySet;
}
private static Type GetBaseType(Type type)
{
var baseType = type.BaseType;
if (baseType != null && baseType != typeof(EntityObject))
{
return GetBaseType(type.BaseType);
}
return type;
}
}