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
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.
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
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've custom control in shared project (resource dictionary in shared project).
Everything works fine in run time, xaml designer however throws exception:
Cannot locate resource 'mycontrol.xaml'.
The problem occurs when loading style for control:
public class MyControl: Control
{
public MyControl()
{
Resources = new ResourceDictionary() { Source = new Uri("pack://application:,,,/mycontrol.xaml") };
Style = (Style)Resources["somekey"];
}
}
Why does it works in run-time and doesn't during design time?
I can detect design time, but what to do then?
The WPF designer seems to have problem when loading xaml files from other projects. Could you try to load the xaml file using this annotation:
pack://application:,,,/PROJECTNAMESPACE;component/mycontrol.xaml
I would try
Uri res = new Uri("pack://siteoforigin:,,,/mycontrol.xaml", UriKind.Relative);
Resources.MergedDictionaries.Add(new ResourceDictionary() { Source = res });
I'm building an MVVM Light WPF app using Visual Studio 2015. The app needs to display some SQL Server Reporting Services reports locally.
The following two solutions exist:
Using MS ReportViewer in WPF
Walkthrough: Using ReportViewer in a WPF Application
Though the first is MVVM, it's mixing UI with the view model. The second is pure code-behind.
Here's what the first example suggests:
WindowsFormsHost windowsFormsHost = new WindowsFormsHost();
reportViewer = new ReportViewer();
windowsFormsHost.Child = reportViewer;
this.Viewer = windowsFormsHost
Note that ReportViewer is a UI control. The second solution uses a code-behind file:
private void ReportViewer_Load(object sender, EventArgs e)
{
//...
}
Is there a way to embed a local SSRS report into a WPF app and follow good MVVM practices? Thank you.
Update: No need to be fanatical! If some code-behind is needed, I'm okay with it.
We use a view to select the report from a ComboBox and a button to run it. In the viewmodel, we have the reports' ComboBox bound to an ObservableCollection of report names and IDs. We then employ the MVVM Light Toolkit's Messaging class to send/receive "messages." Note that the base viewmodel, MyAppViewModelBase, inherits from Light Toolkit's ViewModelBase, which has the RaisePropertyChanged() defined.
Also note that we could pass the selected report's VM instead of the view's VM; that would be more efficient but will require modifications to this code. Then we'd use a base class for all the report VMs and a pattern-matching switch in the code-behind to select which report to run.
Here's the pertinent code for the viewmodel:
using GalaSoft.MvvmLight.Messaging;
public class ReportsViewModel : MyAppViewModelBase
{
public ReportsViewModel()
{
// Register a listener that receives the enum of the
// report that's ready. The message it receives has
// name "SsrsReportReady" with handler SsrsReportReady.
Messenger.Default.Register<Constants.Report>(this, "SsrsReportReady", SsrsReportReady);
// Other logic...
}
// Bound to a button to run the selected report
public ICommand RunReportRelayCommand =>
new RelayCommand(RunReport);
// Backing field for the selected report.
private ReportViewModel _selectedReportVm;
public ReportViewModel SelectedReportVm
{
get { return _selectedReportVm; }
set
{
if (Equals(value, _selectedReportVm)) return;
_selectedReportVm = value;
// Built-in method from Light Toolkit to
// handle INotifyPropertyChanged
RaisePropertyChanged();
}
}
private void RunReport()
{
// Send a message called "RunSSRSReport" with this VM attached as its data.
Messenger.Default.Send(this, "RunSSRSReport");
}
// Handler for report-ready
private void SsrsReportReady(Constants.Report obj)
{
ShowReport = true;
IsRunReportButtonEnabled = true;
RunReportButtonContent = Constants.BtnGenerateReport;
// View uses Material Design's Expander control.
// We expand/collapse sections of the view.
ExpandReport = true;
ExpandParameters = false;
}
}
In the code-behind of the view:
using GalaSoft.MvvmLight.Messaging;
public partial class ReportsView : UserControl
{
public ReportsView()
{
InitializeComponent();
// Register a listener for the "RunSSRSReport"
// message, called from our viewmodel. Its
// handler is RunSsrsReport and its data type
// is ReportsViewModel.
Messenger.Default.Register<ReportsViewModel>(this, "RunSSRSReport", RunSsrsReport);
DataContext = new ReportsViewModel();
}
// Handler to run the selected report.
private void RunSsrsReport(ReportsViewModel obj)
{
// Basic validation
if (obj.SelectedReportVm == null || obj.SelectedReportVm.Id.Equals(-1))
{
return;
}
// Ugly switch to run the correct report.
// It can be re-written with pattern matching.
switch (obj.SelectedReportVm.Id)
{
case (int)Constants.Report.ReportA:
RunReportA(obj);
break;
case (int)Constants.Report.ReportB:
RunReportB(obj);
break;
// other reports....
}
}
// Run the report using dataset and tableadapter.
// Modify to use your code for running the report.
private void RunReportA(ReportsViewModel reportsViewModel)
{
var dataSet = new ReportADataSet();
dataSet.BeginInit();
// We reference the ReportViewer control in XAML.
ReportViewer.ProcessingMode = ProcessingMode.Local;
ReportViewer.LocalReport.ShowDetailedSubreportMessages = true;
ReportViewer.LocalReport.DataSources.Clear();
var dataSource = new ReportDataSource
{
Name = "ReportA_DS",
Value = dataSet.uspReportA // Uses a stored proc
};
ReportViewer.LocalReport.DataSources.Add(dataSource);
ReportViewer.LocalReport.ReportEmbeddedResource =
"MyApp.Reports.ReportA.rdlc";
dataSet.EndInit();
new reportATableAdapter { ClearBeforeFill = true }
.Fill(dataSet.uspReportA);
// Send message back to viewmodel that the report is ready.
Messenger.Default.Send(Constants.Report.ReportA, "SsrsReportReady");
}
}
The report view has a WindowsFormsHost with name ReportViewer, referenced in above code-behind:
<WindowsFormsHost Width="Auto" Height="500">
<rv:ReportViewer x:Name="ReportViewer" />
</WindowsFormsHost>