Having a very difficult time trying to use pure DI (i.e. no framework) with WPF following MVVM. I have Mark Seemann's book; however, his solution to this seems pretty similar to what I've come up with:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
string connectionString = #"Server=(localdb)\MSSQLLocalDB;Database=RouteMiningDB;Trusted_Connection=True;";
RouteMiningDAL.RouteMiningDataContext db = new RouteMiningDAL.RouteMiningDataContext(new DbContextOptionsBuilder().UseSqlServer(connectionString).Options);
IZIPCodeInfoRepository zipCodeRepo = new RouteMiningDAL.SQLZIPCodeInfoRepository(db);
ZIPCodeInfoService zipCodeInfoService = new ZIPCodeInfoService(zipCodeRepo);
ZIPCodeInfoViewModel zipCodeInfoViewModel = new ZIPCodeInfoViewModel(zipCodeInfoService);
ZIPCodeInfoView zipCodeInfoView = new ZIPCodeInfoView(zipCodeInfoViewModel);
MainWindow mainWindow = new MainWindow();
mainWindow.Content = zipCodeInfoView;
mainWindow.Show();
}
}
Per other resources, as well as Mark's book, OnStartup is used as the Composition Root. All seems well above, however, I feel very limited as to what I can do. For example, I have set the ZIPCodeInfoView to the mainWindow.Content. Obviously with many child Windows such as:
This presents some challenges with layout because I can't really just set it to xxxx.Content (I can I guess, but I don't want to construct the layout in code). How do I go about this? Am I overlooking the ability to do this in XAML? It seems XAML needs a parameterless constructor which obviously does not work for DI's Constructor Injection. Thanks!
Disclaimer: I want to use pure DI.
Nice that you would like to use pure DI. Your question is good. I hoped that the 2nd edition of the book would answer it. It didn't have a plain example/answer for that if I recall it correctly.
However, the idea of Pure DI is that all the dependencies are visible (using construction injection) up front in the app entry point a.k.a. the composition root.
In your case I would chain it up in the following way:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
string connectionString = #"Server=(localdb)\MSSQLLocalDB;Database=RouteMiningDB;Trusted_Connection=True;";
RouteMiningDAL.RouteMiningDataContext db = new RouteMiningDAL.RouteMiningDataContext(new DbContextOptionsBuilder().UseSqlServer(connectionString).Options);
IZIPCodeInfoRepository zipCodeRepo = new RouteMiningDAL.SQLZIPCodeInfoRepository(db);
ZIPCodeInfoService zipCodeInfoService = new ZIPCodeInfoService(zipCodeRepo);
StockHistoryService stockHistoryService = StockHistoryService();
StockHistoryViewModel stockHistoryViewModel = new StockHistoryViewModel(stockHistoryService);
ZIPCodeInfoViewModel zipCodeInfoViewModel = new ZIPCodeInfoViewModel(zipCodeInfoService, stockHistoryViewModel);
ZIPCodeInfoView zipCodeInfoView = new ZIPCodeInfoView(zipCodeInfoViewModel);
MainWindow mainWindow = new MainWindow();
mainWindow.Content = zipCodeInfoView;
mainWindow.Show();
}
}
In that way the main window depends on the StockHistoryViewModel which depends on the StockHistoryService.
For some view model (pop-up/modal window etc) I would use factory pattern with DI so that view model would be created only when/if needed. But it hides the view model's dependecies...
Dependency Injection vs Factory Pattern
Related
I'm working through a project where I'm going to have multiple square size instances of the same set of form components.
I can either create 8 instances manually in my form UI or what I'd rather do is create a view (or Item Renderer) and then dynamically add instances of that view to my main view.
How do I add a create and add a custom view dynamically to the main view in my Xamarin form?
Note: Including Swift tag because you might know the answer if you know Swift or Objective C since the API wraps Apple API.
If IIUC:
Create a view in XCode Interface Builder
In ViewDidLoad create an instance of the custom instance views
Add each instance to the main view
I'd read a guide if there was one but I can't find anything specifically on this.
Some what related. I can create a new View in Xcode interface Builder pretty easily. Is there a way to export that as a class to my application?
Update:
I've found a textfield in Interface Builder where I can enter the name of a class. Back in Visual Studio my main View Controller can see the HelloWorld class. I've found a method named AddChildViewController. I try testing it. Nothing happens. Do I need to set the position and size? I can't find any API to do this.
Tomorrow I will scour the ancient texts again for example code. Maybe there is something I missed?
public override void ViewDidLoad()
{
base.ViewDidLoad();
var view = new HelloView(this.Handle);
var handle = view.Handle;
base.AddChildViewController(view);
var view2 = new HelloView(this.Handle);
handle = view.Handle;
base.AddChildViewController(view2);
}
I noticed a note in the console log:
<ViewController: 0x600000a94180> already have child of:
<ViewController: 0x600000a94180>
Update II:
This adds a new NSView and then a NSButton in that view to the main window:
var frame = new CoreGraphics.CGRect(0, 0, 100, 100);
var button = new NSButton(frame) {
Title = "My Button",
};
var view = new NSView(frame) {};
view.AddSubview(button);
View.AddSubview(view);
It doesn't add my custom class yet though.
Update III:
I'm able to add the custom HelloWorldView class but the controls are not visible. If I add a button to the form I see it but it is anchored to the bottom of the screen. I don't see the controls created from Interface Builder.
//var frame = this.View.Frame;
var frame = new CoreGraphics.CGRect(0, 0, 100, 20);
var button = new NSButton(frame) {
Title = "My Button"
};
var frame2 = new CoreGraphics.CGRect(0, 0, 100, 100);
var helloView = new HelloView() {
};
helloView.Frame = frame2;
helloView.AddSubview(button);
mainFrame.AddSubview(helloView);
HelloView.cs:
public partial class HelloView : NSView
{
public HelloView () : base ()
{
}
}
Note about the code above: I removed the handle parameter because it was causing a compiler error.
Setup: Visual Studio for Mac using Xamarin C# and XCode Interface Builder
--
Notes for the bounty.
To receive the bounty you must show how to do either step 1 or step 2 mentioned above in the Bounty Requirements section. I prefer step 1. If you are unsure ask.
So I have a small project I am working on. Basically what I want to do is create another class, with the SQL connection as well as the results. However, it is telling me:
The Name 'firstName' does not exist.
When I put it on the page that is created for the designer mode in visual studio, it goes away and works.
info.cs:
public void GetInfo(string accountNumber)
{
string source = helper.CnnVal("WorkflowConfiguration");
SqlConnection con = new SqlConnection(source);
con.Open();
string sqlSelectQuery = $"Select TOP 1 * From [Workflow Creation].[dbo].[ssFields] Where Field16 =" + int.Parse(accountNumber);
SqlCommand cmd = new SqlCommand(sqlSelectQuery, con);
SqlDataReader dr = cmd.ExecuteReader();
if (dr.Read())
{
firstName.Text = (dr["Field1"].ToString());
lastName.Text = (dr["Field2"].ToString());
dateOfbirth.Text = (dr["Field3"].ToString());
socialSecurity.Text = (dr["Field4"].ToString());
}
con.Close();
}
I would like to make a reference to the "designer" code page. So I can reference the results in the btn click below:
namespace WindowsFormsApp1
{
public partial class dataBase : Form
{
List<Information> people = new List<Information>();
private personalInfo personal = new personalInfo();
public dataBase()
{
InitializeComponent();
}
public void searchBtn_Click(object sender, EventArgs e)
{
dataAccess db = new dataAccess();
people = db.GetPeople(accountNumber.Text);
ListBoxPeople.DataSource = people;
ListBoxPeople.DisplayMember = "FullInfo";
//Would like to references here from info.cs
}
You seem to be dealing with Windows Forms here, as your main class is a Form. To start things off, I would like to recommend against giving Forms names that are completely unrelated to them. I have the habit of naming the main forms of my applications MainForm, but you could also use, for example, MyAppForm (the name of your application plus the word Form).
Setting that aside, if you need to access a control on your form (such as a TextBox), I recommend that you do so within the Form class itself, unless you have an excellent reason to do so. You will not be able to reference things from outside the form class (as controls are Private), and even if you write a method to pull the controls from your Form, you won't be able to access them (they will be on a different thread), unless you implement an algorithm to get around that.
Therefore, I suggest that you move your GetInfo method to your form class. Notice that that class is a partial class, that means you can create a new class file with the same class name and it will extend your form class, better organizing things (this is what Designer code generation does, hence why you're not supposed to alter things on the Designer file).
Edit: Additionally, as suggested above, if the context of your form doesn't suit your method, you can also pass the data required by a control via an extra public acessible method. That extra method can be called by your Form event, for example.
Note: Be sure to define the class as partial on the other file as well, if you intend to do this.
I have a list of objects that should be passed to another view but I don't know how I can do that in Xamarin.forms, I think I should to use setBinding, but its the only thing I say in this case.
Thank you.
List<Localizacao> localizacaoList = new List<Localizacao>(); ;
if (localizacao != null && lojaPerto != null)
{
localizacaoList = new List<Localizacao>();
Localizacao loc = new Localizacao();
loc.latitude = Double.Parse(lojaPerto.latitude);
loc.longitude = Double.Parse(lojaPerto.longitude);
localizacaoList.Add(loc);
localizacaoList.Add(localizacao);
}
var secondPage = new Views.ComoChegarView ();
secondPage.BindingContext = localizacaoList;
await Navigation.PushAsync(secondPage);
In fact, I sent, but I can't get it again in the other view
If you are not using any additional framework, maybe you can try using constructor parameters.
public partial class ComoChegarView
{
...
private List<Localizacao> Locals{get;set;}
public ComoChegarView(List<Localizacao> locals)
{
InitializeComponent(); //standard code that mix xaml and code behind
this.Locals = locals; //store the data in property
this.BindingContext = this; //Set the binding context
}
}
So you can pass the value when you construct the page.
List<Localizacao> localizacaoList = new List<Localizacao>(); ;
if (localizacao != null && lojaPerto != null)
{
localizacaoList = new List<Localizacao>();
Localizacao loc = new Localizacao();
loc.latitude = Double.Parse(lojaPerto.latitude);
loc.longitude = Double.Parse(lojaPerto.longitude);
localizacaoList.Add(loc);
localizacaoList.Add(localizacao);
}
var secondPage = new Views.ComoChegarView (localizacaoList);
await Navigation.PushAsync(secondPage);
Remember that update your binding in XAML to reflect property access (for example)
<ListView ItemsSource="{Binding Locals}">...</ListView>
What you want to achieve is perfectly supported by all serious MVVM libraries.
1) a view do not pass anything to another view, it is the job of the ViewModels
2) in a MVVM context you can use many techniques to send ou pass data from one ViewModel to another, the main being : MVVM messenger (there's one include in Xamarin.Forms) or dependency injection in the the ViewModel constructor (using an IoC container what most MVVM libraries are offering, using Unity, DryIoc, ...).
It is certainly hard to do if you do not master MVVM pattern but you should take a little time to study this pattern and some libraries as Prism. You will quickly see the benefit of such an approach and will be very happy to write code more efficiently (and find in a minute quick and clean solutions to problems like the one you're talking about here).
I have a small design question which I couldn't find relevant google hits for some reason.
I have a user control which I use in my application.
The main form opens a second form as a dialog. T
his second form is using the user control which includes a list box.
Naturally I want to preserve the list box items when the forms dispose so I am keeping a private list in the main form.
List<string> _listofFirstCoordinates = new List<string>();
Now the question is, should the dialog form be the one responsible for relaying the list to the main form or should the code be in the user control?
Should the one populating the list be the user control
lst_Coordinates.Items.AddRange(ListOfCoordinates.Cast<object>().ToArray());
or should the form using it populate it (The subform)
uc_EditCoordinates.ListOfCoordinates = ListOfCoordinates;
Also is it feasible to just have the user control be a public variable for the form holding it so it may be changed directly or would that be bad design?
Edit:
By the way, the data is saved for now in variables going back and forth between the forms as the user has to finish all subforms before submitting and finally saving it to the database. So it is a
var _listofFirstCoordinates = new List<string>();
going back and forth.
The "correct" solution is to abstract-away the View-level concern (in this case, anything to do with Form, UserControl, and UI controls) away from the Controller and Model-level concerns (in this case, your application's data).
Without completely rearchitecturing your system, you can still apply this separation-of-concerns within your example.
You can conceptually argue the "code-behind" of your MainForm class acts as a kind of Controller (purists would disagree). It will have to know about creating the child form, but it does not need to know about the user-control hosted within the child form - that would be the concern of the child form's.
I suggest defining a class that represents a ViewModel - albeit as we're using WinForms we will use it as a kind of crude "one-way" ViewModel, like so:
class MainForm : Form {
private void ShowChildFormModal() {
ChildViewModel vm = new ChildViewModel();
vm.CoordinatesList = ...
vm.OtherData = ...
ChildForm child = new ChildForm();
child.LoadFromViewModel( vm );
child.ShowDialog();
child.SaveToViewModel( vm );
SaveToDatabase( vm );
}
}
class ChildViewModel { // this is a POCO
public List<String> CoordinatesList;
public Int32 OtherData;
}
class ChildForm : Form {
public void LoadFromViewModel(ChildViewModel vm) {
// save time and trouble by using the List as a datasource directly, or you can manually populate the combobox as well
this.childUserControl.LoadFromViewModel( vm );
this.someOtherControl.Value = vm.OtherData;
}
public void SaveToViewModel(ChildViewModel vm) {
// completing this is an exercise for the reader
// but basically copy values from the controls on the form into the `vm` instance
}
}
class ChildUserControl : UserControl {
public void LoadFromViewModel(ChildViewModel vm) {
this.comboBox.DataSource = vm.CoordinatesList;
}
}
I want to write a test for a WPF application(Caliburn micro & autofac). The purpose of this test is to display a popup window from unit test case. I tried writing with stand alone WPF window and it worked:
But when I tried to integrate it to an existing solution which uses Caliburn micro & autofac and contains dictionaries and styles I got into some problems:
public MyViewModelTest()
{
this.repository = new MockRepository();
var eventAggregator = this.repository.Stub<IEventAggregator>();
//Other dependencies
viewModel = new MyViewModel(eventAggregator, other dependencies);
GenerateDummyData();
}
[Test]
public void OpenMyViewModelTest()
{
var uiThread = new Thread(Show);
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.Start();
// Wait for the UI thread to finish
uiThread.Join();
}
private void Show()
{
//TODO make a new window to test and uncomment it.
var fg = new MyView { Height = 500, Width = 500, DataContext = viewModel };
//var fg = new MainView { Height = 500, Width = 500, DataContext = viewModel };
fg.ShowDialog();
}
Now if I execute the test from separate test project solution(Not with in UI project where view, viewmodels are available) I get an exception that:
SetUp : System.BadImageFormatException : Could not load file or assembly 'ABC.UIAssemblyName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An attempt was made to load a program with an incorrect format.
If I copy and run test from WPF UI project an empty window get lanuched.
So, I am wondering if I am missing some thing.
do I need to explicitly provide style resources, dictionaries and images etc to the WPF window with in the test.
A link on how write a test case for WPF window with caliburn micro would be a plus.
Thanks