I am currently working on finding a good project structure for a 3-tier solution to avoid unnecessary work in a new project.
The project will consist of a core product which has
model project which holds the EF code first models
project which has the business logic and communication logic on the server
repository project on the client
silverlight project with views and view models (would like to use caliburn.micro here)
The problem is now that a customer could have some special requirements which could lead to changes in all the above projects. So my thought was that I could just use the base structure and create the same structure for a customer. If there are no changes I would just have empty classes that just extend the base class and add nothing.
Which brings me to the following problems:
Is it a problem in Entity Framework (code first) to have base classes in one project (which is already fully functional) and have another project which may extend the model classes with new field?
Is it a problem in XAML to change a user control? For example if I have a user control that consists of five textboxes in my core and I want to change the second box to a radio button, but nothing else.
I will also accept changes to the project structure, if there is a better way to handle customer specific changes.
Edit: I will probably use the following approach to solve the problem.
Entity Framework:
With code first it seems to be possible to have one model project extend another project. This means I could write something like:
public class CoreAddress{
[Key]
public int AdrId{get; set;}
public string Street {get;set;}
}
public class CustomerAddress : CoreAddress{
public string StreetNumber {get; set;}
}
Only thing that is needed in order to make that work is a line inside the DbContext:
(this as IObjectContextAdapter).ObjectContext.MetadataWorkspace.LoadFromAssembly(typeof(<entity from other assembly>).Assembly);
XAML
To get a similar behavior in XAML I had to use Caliburn.Micro (in combination with MEF) which is a great help here.
I would create UserControls that include ContentControl elements which are dynamically fetched by using MEF. That means I have again a core project with all the views and ViewModels. If I need to exchange a special control somewhere for a customer I change the control to a ContentControl and create a core view and ViewModel for it (which is the same as it was before the change request).
This ViewModel for the ContentControl is annoted with an export interface and ExportMetadata to set a priority of 1.
Now I create another project with another UserControl that has some other control instead of the core control and annotate it again as an export with the same interface but set my priority higher and so the customer specific control is loaded.
Short example for this:
Main user control and view model:
<UserControl x:Class="SilverlightApplication5.TestView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel>
<ContentControl x:Name="Item"/>
<TextBox x:Name="TextItem" Text="asdf"/>
</StackPanel>
</Grid>
</UserControl>
public class TestViewModel : Screen
{
private object viewModel;
private Lazy<IMyViewModel, IPluginMetadata>[] _orderEditorFactory;
[ImportMany(typeof(IMyViewModel), AllowRecomposition = true)]
public Lazy<IMyViewModel, IPluginMetadata>[] OrderEditorFactory
{
get { return _orderEditorFactory; }
set
{
_orderEditorFactory = value;
Item = _orderEditorFactory.OrderByDescending(lazy => lazy.Metadata.Priority).First().Value;
}
}
private object _item;
public object Item
{
get { return _item; }
set
{
_item = value;
NotifyOfPropertyChange(() => Item);
}
}
}
Core Control:
<UserControl x:Class="SilverlightClassLibrary2.MainControlView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<StackPanel>
<TextBlock x:Name="Test" Text="Text from Core control"/>
</StackPanel>
</UserControl>
[Export(typeof (IMyViewModel))]
[ExportMetadata("Name", "Pluginc")]
[ExportMetadata("Priority", 30)]
public class MainControlViewModel : Screen, IHarnessAware, IMyViewModel
{
}
Customer specific control:
<UserControl x:Class="SilverlightClassLibrary1.CustomView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<RadioButton x:Name="Test" Content="{Binding Path=Test}"/>
</Grid>
</UserControl>
[Export(typeof(IMyViewModel))]
[ExportMetadata("Name", "Plugind")]
[ExportMetadata("Priority", 2)]
public class CustomViewModel : MainControlViewModel, IHarnessAware, IMyViewModel
{
}
Export interface:
public interface IMyViewModel
{
}
ExportMetadata interface:
public interface
IPluginMetadata
{
string Name { get; }
[DefaultValue(0)]
int Priority { get; }
}
I did use this to answer the question, because I am still interested in input from other people who may have already solved a simlilar problem.
About project structure:
You can create a base project, with every concern layer you need. Model, business, views, repositories and so on.
Also create some basic flow, for example, a single view with its controller up to the repository. Save it in your codebase, then fork it whenever you need to create a new project.
Now instead of using time to set up, you'll just need some time to customize as your project require.
About XAML: IMHO, if you change the component, you should be sure it returns the same datatype as your control expects. If you swap a textbox for a checkbox, be sure the check returns a string to the controller.
Related
We have a GridControl and we are assigning the ItemsSource to a collection of interface items. The interface used in the collection inherits from another interface, and the problem we are running into is that only items directly defined in the top level interface are being shown in the GridControl
Below is a very simplified example of the behavior that we are seeing.
xaml code defining the GridControl
<Window x:Class="WpfThrowaway.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:local="clr-namespace:WpfThrowaway"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<dxg:GridControl x:Name="DataGrid" Background="#363636" Foreground="#FFFFFF" EnableSmartColumnsGeneration="True" AutoGenerateColumns="AddNew">
<dxg:GridControl.View >
<dxg:TableView x:Name="TableView" AllowEditing="False" />
</dxg:GridControl.View>
</dxg:GridControl>
</Grid>
</Window>
Concrete item implementation
class ConcreteItem : ILevel1
{
public string Level1String => "Level 1 string";
public double Level2Double => 2;
}
Level 1 interface (type used in the ItemsSource collection)
interface ILevel1 : ILevel2
{
string Level1String { get; }
}
Level 2 interface
interface ILevel2
{
double Level2Double { get; }
}
Code behind to initialize ItemsSource in the MainWindow
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var concreteItemCollection = new List<ILevel1>();
for(var i = 0; i < 100; i++)
{
concreteItemCollection.Add(new ConcreteItem());
}
DataGrid.ItemsSource = concreteItemCollection;
}
}
Resulting data grid
What we want and would expect is for the GridControl to show two columns Level1String and Level2Double, but only the item explicitly defined in the ILevel1 interface is showing up in the grid.
Is there any workaround for this? How do we get all properties from inherited interfaces to show up as well?
A bit of a hack that works is to cast the top level interface to an object. It will trick the grid control into auto generating the columns based on the concrete implementation, which will get you all of your properties.
DataGrid.ItemsSource = concreteItemCollection.Select(x => (object)x);
I'm trying to implement Unity in a WPF MVVM application, but I'm missing the big picture.
At this moment I have created a bootstrapper like this:
public class MainBootstrapper : Bootstrapper<MainViewModel>
{
private UnityContainer container;
protected override void Configure()
{
container = new UnityContainer();
container.RegisterType<IServiceLocator, UnityServiceLocator>(new ContainerControlledLifetimeManager());
container.RegisterType<IWindowManager, WindowManager>(new ContainerControlledLifetimeManager());
container.RegisterType<IEventAggregator, EventAggregator>(new ContainerControlledLifetimeManager());
}
protected override object GetInstance(Type service, string key)
{
if (service != null)
{
return container.Resolve(service);
}
if (!string.IsNullOrWhiteSpace(key))
{
return container.Resolve(Type.GetType(key));
}
return null;
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return container.ResolveAll(service);
}
protected override void BuildUp(object instance)
{
container.BuildUp(instance);
}
}
How what is the best way to use this?
This code currently works:
public class MainViewModel : PropertyChangedBase
{
public MainViewModel()
{ }
[Dependency]
public Sub1ViewModel Sub1VM { get; set; }
[Dependency]
public Sub2ViewModel Sub2VM { get; set; }
}
the MainView has this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Name="Sub1VM" />
<ContentControl Grid.Row="1" Name="Sub2VM" />
</Grid>
First of all: the code that I shared, is this the correct way of using Unity + Caliburn?
Now let's say that my Sub1VM uses a model 'M1', but Sub2VM needs to use the same model to display information but not by making another instance of model M1. (singleton)
How does this work now? Show I use a IServiceLocator in each viewmodel constructor? Could somebody share a code sample to explain it?
First of all i agree with McDonnellDean that you should read the article about the Screens, Conductors and Composition (if i were you i would read all the articles before that too to understand how Caliburn.Micro works.). Besides that, you implemented Unity correctly and you can check Unity as IoC Container for Caliburn.Micro for more information. On the other side you are mixing two concepts here, namely Dependency Injection and MVVM. Regarding your question about the model, i would also prefer constructor injection, and if you want a single instance of the model, perhaps you can inject a Factory that creates that model for you and wrap it into two different view models and expose it through the two different properties. At last i really encourage you to read the tutorials (start here), at least the basic topics.
I don't know Unity in particular but your configuration looks correct.
As for your injection points. I would say that rather than doing property injection you should do constructor injection. What you are doing is fine, however you may want to look up screens and conductors, these allow you to add life-cycle to your ViewModels. Typically it would look like this:
Bootstrapper opens ShellViewModel
ShellViewModel takes in MainViewModel via Ctor injection as an IConductorOneActive
MainViewModel takes a collection of IScreens.
ShellViewModel calls MainViewModels activate method on MainViewModel.
See Screens, Conductors and Composition. As I stated above, your way is fine but it is a little on the manual side and means you have to wire everything by hand.
I am developing project using MVVM pattern.In the project I have two viewmodel namely
CountryViewModel and 2. EmpViewModel
In countryviewmodel I have stored information about country,state,city etc.
In EmpViewModel I have a control which have combo box which displays country name and selected value is set to country id which are in CountryViewModel.
Here is code:
<ComboBox Grid.Row="0" Grid.Column="1" Margin="3"
ItemsSource="{Binding CountryViewModel.Countries}" SelectedValue="{Binding Title}"
SelectedItem="{Binding CountryViewModel.SelectedCountry,Mode=TwoWay}"
SelectedValuePath="Country_Id" DisplayMemberPath="Title">
</ComboBox>
This is working fine.
I have local property country id in EmpViewModel and want to bind it to SelectedValue property of Combobox which I can get if I remove CountryViewModel from CountryViewModel.SelectedCountry.
But problem is I have another combobox for state which is dependent on Country combo box.
Edit: i.e in Country ViewModel I have called method GetAllState() when SelectedCountry changes.
So can I bind SelectedValue property of Combobox to both CountryViewModel.SelectedCountry from CountryViewModel and Country_Id from EmpViewModel?
I found a workaround
I have write following method in an interface:
public interface IViewModel
{
T GetValue<T>(string propertyName);
}
In country view model I implement this method as:
public override T GetValue<T>(string propertyName)
{
T result = default(T);
result = (T)Convert.ChangeType(this.SelectedCountry, typeof(T), null); }
And in Emp View Model I added following line:
newEmp.Country_Id = this.CountryViewModel.GetValue<Country>("SelectedCountry").Country_Id;
I have a view controlled by a view model (using MEF) that allows a user to selected items from a drop down list. Each item that the user selects populates a tab control that is defined as a region. The view model instantiates a view, assigns it a view model, then adds it to the region:
ProjectDetailView view = new ProjectDetailView();
ProjectDetailViewModel viewModel = new ProjectDetailViewModel();
viewModel.CurrentProject = project;
view.DataContext = viewModel;
RegionManager.Regions["SelectedItemsRegion"].Add(view);
This all works fine from the UI perspective. The project detail view model, however, has [Import] statements on it to receive an EventAggregator for publishing events.
[Import]
public IEventAggregator EventAggregator { get; set; }
Because I'm only adding views to a region and not doing a request navigate to a specific URI, the composition never occurs (or at least it doesn't appear to) so EventAggregator is always null. How do I get these dynamically added views to go through the process of importing the requested classes? Is there a compose method I can call on a specific view so things get resolved?
I would suggest that you create a factory class to instantiate EventAggregator, like so:
public EventAggregatorFactory
{
[Export(typeof(IEventAggregator))]
public IEventAggregator Instance
{
get
{
return new EventAggregator();
}
}
}
Obviously, move the Export declaration into the factory class. This should allow proper instantiation of the Import of EventAggregator when the viewmodel is invoked.
Hi all I'm trying to get to grips with using MVVM, but I'm having a hard time :(, firstly for my question, I'm using the code provide in this MVVM article as a template for my learning.
My question is simple how do expose independent commands, in this case he has create a list of hyperlinks, but how do i create a single button that's fixed and does the same as the 'create new customer' link.
I created something like this(was added to the MainWindowViewModel.cs):
public CommandViewModel exposedCommand
{
get
{
return new CommandViewModel(
Strings.MainWindowViewModel_Command_CreateNewCustomer,
new RelayCommand(param => this.CreateNewCustomer())
);
}
}
and then in the xaml document i created a new button, this was added to the MainWindow.xaml
<Button
Content="Button"
Height="23"
HorizontalAlignment="Left"
Margin="6,303,0,0" Name="button1" VerticalAlignment="Top" Width="150"
Command="{Binding Path=exposedCommand}"
/>
I am not to sure if I'm missing something, or what I am where going wrong,
Soz if I sounding a bit naive I have only just started using MVVM and routed commands and so.
Oh another thing it does load the link it just doesn't create the tab, in other words if you would to add
Console.Writeline("HERE");
to the exposedCommand method
It would print out 'HERE' it just won't do anything when you click the button.
Thanks Any Help would be so appreciated.
Your XAML code is correct.
I also started off with Josh Smith's MVVM article.
Below is a stripped down example of how I implement Commands in my ViewModels:
public class ProjectViewModel : ViewModelBase
{
// Private variable for holding save command
private RelayCommand _saveCommand;
// Other fields here
// Constructors and properties and stuff here
// Command Property for Save button. Bind XAML to "SaveCommand"
public ICommand SaveCommand
{
get
{
if (_saveCommand == null) // Init command on first get
_saveCommand = new RelayCommand(param => this.SaveChanges(), param => this.CanSave);
return _saveCommand;
}
}
/// <summary>
/// Method called when save command is executed
/// </summary>
private void SaveChanges()
{
// Save logic here...
}
/// <summary>
/// Predicate for determining if SaveCommand is enabled
/// </summary>
private bool CanSave
{
get
{
return true; // Replace with SaveCommand predicate logic
}
}
}
If it still does not work, check you runtime output for BindingErrors. If there is a BindingError that implies that the View cannot find the SaveCommand, then your ViewModel is not correctly set to be the DataContext of the View. Let me know in the comments if this is the problem.
You can bind command to only those objects that has an implementation of ICommand interface.
In your example you are binding with view model object.
Instead of this create a property in view model that is a type of RelayCommand and bind this with button.
It should work.
The first thing that concerns me is your the code inside the getter of you property. You're returning a new object EVERY TIME the exposedCommand is accessed. That's not really recommended, you should store that in a backing property like so:
CommandViewModel _exposedCommand;
public CommandViewModel exposedCommand
{
get
{
if (_exposedCommand == null)
{
_exposedCommand = new CommandViewModel(
Strings.MainWindowViewModel_Command_CreateNewCustomer,
new RelayCommand(param => this.CreateNewCustomer()));
}
return _exposedCommand;
}
}
That being said the typical way to present your desired ICommand property is something like this:
RelayCommand _exposedCommand;
public ICommand exposedCommand
{
get
{
if (_exposedCommand == null)
_exposedCommand= new RelayCommand(param => this.CreateNewCustomer());
return _exposedCommand;
}
}