I put the instance of one of the VMs in a resource dictionary like:
<ObjectDataProvider ObjectType="{x:Type WpfApplication1:MyViewModel}" x:Key="TheViewModel"/>
I bind the DataContext of some user controls to this:
<WpfApplication1:UserControl1 x:Name="UsrCtrl1" DataContext="{StaticResource TheViewModel}"/>
and it works fine at the runtime, because all connections and servers are available and a lot of logical objects are correctly initialized.
The problem is, in the design time I get a lot of exceptions (there are many such VMs), that make the work with very difficult.
Is it possible somehow to say in XAML if ComponentModel:DesignerProperties.IsInDesignMode (xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=PresentationFramework") is true then x:null, otherwise create my VM WpfApplication1:MyViewModel ???
I tried a lot, but was unable to get the right solution, but I cannot believe this is not possible. For any idea (maybe a tested example) thanks in advance.
The way I've dealt with this in the past involves providing an interface for your viewmodels and having the views ask for their viewmodel from a viewmodel locator class. For example, you'd have the following viewmodels:
public interface IMainViewModel
{
double Foo { get; }
double Bar { get; }
}
public class RealMainViewModel : IMainViewModel
{
// implementation of IMainViewModel, this one does your data access
// and is used at run time
}
public class FakeMainViewModel : IMainViewModel
{
// implementation of IMainViewModel, this one is fake
// and is used at design time
}
The viewmodel locator would look like the following:
public class ViewModelLocator
{
public static IMainViewModel MainViewModel
{
get
{
if (Designer.IsDesignMode)
{
return new FakeMainViewModel();
}
else
{
return new RealMainViewModel();
}
}
}
}
Finally, you'll include a reference to ViewModelLocator in App.xaml:
<Application.Resources>
<ResourceDictionary>
<yourNamespace:ViewModelLocator x:Key="ViewModelLocator" />
</ResourceDictionary>
</Application.Resources>
This way, you can bind to the viewmodel property in ViewModelLocator and have your code do the work for injecting the real and fake viewmodel when appropriate:
<WpfApplication1:UserControl1 x:Name="UsrCtrl1" DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}"/>
I also found an article that provides another example. Note that I wrote this code on-the-fly in Notepad so I apologize if there are any typos.
I believe you can use the following in your UserControl1 tag to define a Design-Time DataContext
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{x:Null}"
I haven't actually tested it since I usually don't use the designer window, but in theory it should work :)
Related
I'm using Caliburn.Micro (CM) in a WPF application with ViewModel-first approach. I'm composing the main view with a command bar and an active item. Main viewModel sets the property for the command bar viewModel, and navigates to active item correctly.
Everything looks fine at runtime, the issue is only related to design-time: the main view shows empty in designer and I cannot find how to set it correctly. I managed to having this working in other scenarios, e.g. when setting the datacontext at design time for a whole Window or UserControl, i.e. when that's the root UI element in XAML. But now I'm not able to to this for child ContentPresenter UI elements within a Window.
This is an excerpt of the main view I'm composing:
<Window x:Class="...MainView" ...>
<DockPanel ...>
<!-- this one binds to a property of type CommandBarViewModel -->
<ContentControl x:Name="CommandBar" ... />
<ContentControl x:Name="ActiveItem" ... />
</DockPanel>
</Window>
I've checked a number of related reads, but none of them seems to fit/solve my issue. This question is basically the same as mine, but has no answers. That has a reference to this other question which it seems to me is going for a View-first approach, judging by the cal:View.Model bindings.
I tried adding a design-time context like the following (fake namespace not shown for brevity):
<ContentControl x:Name="CommandBar" ...
d:DataContext="{d:DesignInstance Type=fake:DesignTimeCommandBarViewModel, IsDesignTimeCreatable=True}"
cal:Bind.AtDesignTime="True"/>
but then I incur in one of two cases:
if DesignTimeCommandBarViewModel inherits from the actual CommandBarViewModel, then I incur in somewhat the usual problem of design-time Vs dependency injection: the default constructor passes null for all injected dependencies, and base constructor or something else gives problem. I mean, it seems it would take some effort to find a workaround for this, and just for design-time support
if DesignTimeCommandBarViewModel does not inherit from the actual viewModel, then it seems that (correctly) the CommandBarView is not instantiated, as now there's no relationship anymore between the viewModel and that view.
Have you got any idea about this? Maybe this should be solved with a design-time version of the hosting MainViewModel?
Other references I checked: this answer, from Rob Eisenberg himself, this CM thread, this other SO
Edit
Following my last (auto-)hint, I'm trying also creating and instantiating a DesignTimeMainViewModel, not inheriting from MainViewModel, which exposes the same properties and sets a DesignTimeCommandBarViewModel in its default constructor. In this case, in place of the command bar the designer shows the classic CM complaint: cannot find view for the DesignTimeCommandBarViewModel.
What's next?
Well, here's the solution I found: I'd be glad to hear about better ways or other suggestions.
Host MainView XAML specifies a design-time data-context pointing to a design-time version of the Main view-model which, by the way, does not inherit from the runtime version MainViewModel. ContentControl items are left untouched.
<Window x:Class="...MainView" ...
d:DataContext="{d:DesignInstance Type=fake:DesignTimeMainPanelViewModel, IsDesignTimeCreatable=True}"
cal:Bind.AtDesignTime="True">
<DockPanel ...>
<ContentControl x:Name="CommandBar" ... />
<ContentControl x:Name="ActiveItem" ... />
</DockPanel>
</Window>
DesignTimeMainPanelViewModel has the same public properties as MainPanelViewModel, has a default c'tor without dependencies and its c'tor sets the CommandBar property to a new instance of DesignTimeCommandBarViewModel:
public class DesignTimeMainPanelViewModel
{
public DesignTimeMainPanelViewModel()
{
CommandBar = new DesignTimeCommandBarViewModel();
ActiveItem = ...some instance here as well...;
}
public DesignTimeCommandBarViewModel CommandBar { get; private set; }
public IScreen ActiveItem { get; private set; }
}
DesignTimeCommandBarViewModel class is decorated with a custom Attribute having only one required parameter, the System.Type of the view associated with that view-model.
During bootstrap the code adds a new ViewLocator strategy to get the view Type from the view-model Type, by setting a new ViewLocator.LocateTypeForModelType.
The new locator function will try to find a view Type if the standard locator function cannot find one. Granted, it will look for the custom attribute on view-model Type, and if found that would be the returned view Type. Here's the gist of that:
Type viewType = _previousLocate(viewModelType, displayLocation, context);
if (viewType == null)
{
FakeViewAttribute fakeViewAttr = Attribute.GetCustomAttribute(viewModelType, typeof(FakeViewAttribute)) as FakeViewAttribute;
if (fakeViewAttr != null) viewType = fakeViewAttr.ViewType;
}
return viewType;
I'm using Visual Studio 2013's designer to create my User Control in WPF, and I'm using a MVVM approach.
I'm trying to find the best way to have "Design-Time" setup of my viewmodel so that I immediatly see the effect in the designer of changing a value of a property for instance. I've used different designs and techniques to support this, but nothing is exactly what I want. I'm wondering if someone has better ideas...
Situation (simplified):
So I have a "Device" which I want a UserControl to show states and operations. From top to bottom:
I have a IDeviceModel which has a field bool IsConnected {get;} (and proper notification of state changes)
I have a FakeDeviceModel which implements IDeviceModel, and thus enables me to not rely on a real device for design-time and testing
A DeviceViewModel, which contains a IDeviceModel, and encapsulate the model's properties. (yes it has proper INotifyPropertyChanged notifications in it)
My UserControl which will have a DataContext of type DeviceViewModel, and would have a custom-styled CheckBox which is IsChecked={Binding IsConnected, Mode=OneWay
My Goal: I want to preview on design time how does the Model's IsConnected state affect my UserControl (So it could affect other things than just IsChecked)
Framework:
I use the idea of the MVVM Light ViewModelLocator, returning non-static fields (so new instances of ViewModels). At runtime, the real datacontext will be given by the one instanciating this UserControl
d:DataContext="{Binding DeviceViewModelDesignTime, Source={StaticResource ViewModelLocator}}"
public class ViewModelLocator
{
private static MainWindowViewModel _mainWindowViewModel;
public MainWindowViewModel MainWindowViewModelMainInstance
{
get
{
if (_mainWindowViewModel == null)
{
_mainWindowViewModel = new MainWindowViewModel();
}
return _mainWindowViewModel;
}
}
public DeviceViewModel DeviceViewModelDesignTime
{
get
{
//Custom initialization of the dependencies here
//Could be to create a FakeDeviceModel and assign to constructor
var deviceViewModel = new DeviceViewModel();
//Custom setup of the ViewModel possible here
//Could be: deviceViewModel.Model = new FakeDeviceModel();
return deviceViewModel;
}
}
Solutions I tried:
Compile-Time solution
Simply code the setup of the ViewModel in the ViewModelLocator.
var deviceViewModel = new DeviceViewModel(fakeDeviceModel);
var fakeDeviceModel = new FakeDeviceModel();
fakeDeviceModel.IsConnected = true;
deviceViewModel.AddDevice(fakeDeviceModel);
Pros: Simple
Cons: That's longer iterations of always going to change the value in code, recompile, go back to designer view, wait for result
Instance in resources and kept static in ViewModelLocator
So I create an instance in XAML and I try to push it in the current ViewModel used by the designer. Not the cleanest way, but worked for a while in simple situation (yes there's some wierdness with the collection, but was with the idea that I could have multiple devices and a current one)
XAML:
<UserControl x:Class="Views.StepExecuteView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DataContext="{Binding DeviceViewModelDesignTime, Source={StaticResource ViewModelLocator}}">
<UserControl.Resources>
<viewModels:DesignTimeDeviceManager x:Key="DesignTimeDeviceManager">
<viewModels:DesignTimeDeviceManager.DesignTimeDevices>
<device:FakeDeviceModel IsConnected="True"
IsBusy="False"
IsTrayOpen="True"
NumberOfChipSlots="4"
/>
</viewModels:DesignTimeDeviceManager.DesignTimeDevices>
[... CheckBox binding to datacontext and so on...]
And ViewModelLocator.cs:
public class ViewModelLocator
{
private static MainWindowViewModel _mainWindowViewModel;
public MainWindowViewModel MainWindowViewModelMainInstance
{
get
{
if (_mainWindowViewModel == null)
{
_mainWindowViewModel = new MainWindowViewModel();
}
return _mainWindowViewModel;
}
}
public static FakeDeviceModel DeviceModelToAddInDesignTime;
public DeviceViewModel DeviceViewModelDesignTime
{
get
{
var deviceViewModel = new DeviceViewModel();
if (DeviceModelToAddInDesignTime != null)
deviceViewModel.AddDevice(DeviceModelToAddInDesignTime );
return deviceViewModel;
}
}
}
public class DesignTimeDeviceManager
{
private ObservableCollection<FakeDeviceModel> _DesignTimeDevices;
public ObservableCollection<FakeDeviceModel> DesignTimeDevices
{
get { return _DesignTimeDevices; }
set
{
if (_DesignTimeDevices != value)
{
_DesignTimeDevices = value;
ViewModelLocator.DeviceModelToAddInDesignTime = value.FirstOrDefault();
}
}
}
}
Pros:
Worked super great on one project. the instance that I had in XAML, I could modify the booleans and I would get -immediate- feedback on how it affects my UserControl. So in the simple situation, the CheckBox's "Checked" state would change and I could modify my styling in real-time, without needing to recompile
Cons:
It stopped working in another project, and this by itself I couldn't find the reason why. But after recompiling and changing stuff, the designer would give me exceptions looking like "Cannot cast "FakeDeviceModel" to "FakeDeviceModel""!! My guess is that the Designer internally compiles and uses caches for those types (C:\Users\firstname.lastname\AppData\Local\Microsoft\VisualStudio\12.0\Designer\ShadowCache). And that in my solution, depending on the ordering of things, I was creating a "FakeDeviceModel" which was assigned to a static instances, and "later on", the next time the ViewModelLocator would be asked for a ViewModel, it would use that instance. However, if in the meantime he "recompiles" or uses a different cache, then it's not "exactly" the same type. So I had to kill the designer (XDescProc) and recompile for it to work, and then fail again a few minutes after. If someone can correct me on this it would be great.
Multi-Binding for d:DataContext and custom converter
The previous solution's problem was pointing me to the fact that the ViewModel and the FakeDeviceModel were created at different moment in time (giving the type/cast problem) and to solve it, I would need to create them at the same time
XAML:
<UserControl x:Class="MeltingControl.Views.DeviceTabView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d">
<d:UserControl.DataContext>
<MultiBinding Converter="{StaticResource DeviceDataContextConverter}">
<Binding Path="DeviceViewModelDesignTime" Source="{StaticResource ViewModelLocator}" />
<Binding>
<Binding.Source>
<device:FakeDeviceModel IsConnected="False"
IsBusy="False"
IsTrayOpen="False"
SerialNumber="DesignTimeSerie"
/>
</Binding.Source>
</Binding>
</MultiBinding>
</d:UserControl.DataContext>
public class DeviceDataContextConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null || values.Length == 0)
return null;
var vm = (DeviceViewModel)values[0];
if (values.Length >= 2)
{
var device = (IDeviceModel)values[1];
vm.AddDevice(device);
}
return vm;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Pros:
-Works super nice! When the binding for the DataContext asks for the ViewModel, I take advantage of the Converter to modify that ViewModel and inject my device before returning it
Cons:
We lose intelissense (with ReSharper), since he doesn't know what type is returned by the converter
Any other ideas or modifications I could make to solve this issue?
You may create a design time ViewModel that returns IsConnected = true based on your view mode (FakeDeviceViewModel) and then set it as a design-time data context:
d:DataContext="{d:DesignInstance viewModels:FakeDeviceViewModel,
IsDesignTimeCreatable=True}"
Where viewModels: is the xaml namespace to the actual view model.
I've documented exactly how I managed to get the perfect setup to see live Design Time data in Visual Studio.
Look at Hint 9 - Design Time DataContext on this page:
ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext"
I would like to propose an alternative solution.
You could use that same view model for design time data and normal runtime, and check in your (single) viewmodel whether the designer is active and then load the design time data there.
In your view model you would do something like this:
public class ExampleViewModel : ViewModelBase
{
public ExampleViewModel()
{
if (IsInDesignMode == true)
{
LoadDesignTimeData();
}
}
private void LoadDesignTimeData()
{
// Load design time data here
}
}
The IsInDesignMode property could be placed in your view model base class - if you have one - and looks like this:
DesignerProperties.GetIsInDesignMode(new DependencyObject());
Please take a look at my answer here
This is how I am doing it one of my projects using MVVMLight.
Create interface for every viewmodel for which you need separate design time and run time properties and behavior.
Make separate viewmodels for every view - one for run time and another for design time. Derive both viewmodels from the same interface defined above.
Create a static class that has two static methods - one for registering services for run time in the IOC container and another for registering services for design time in the IOC container. I use the same SimpleIOC.Default container. Register appropriate viewmodels in both the methods bound to their interfaces.
public static class MyServiceLocator()
{
public static void RegisterRunTimeServices()
{
ServiceLocator.SetLocatorProvider(() => SimpleIOC.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<IAboutViewModel, AboutViewModel>();
}
public static void RegisterDesignTimeServices()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<IAboutViewModel, DesignTimeAboutViewModel>();
}
In constructor of ViewModelLocator, check if the app is in DesignMode and accordingly call the static method to register services.
public ViewModelLocator()
{
if (ViewModelBase.IsInDesignModeStatic)
{
MyServiceLocator.RegisterDesignTimeServices();
}
else MyServiceLocator.RegisterRunTimeServices();
}
Now, your Views just have to set datacontext as corresponding viewmodel interface instead of viewmodel object. To do that, instead of exposing viewmodel objects to each view from the ViewModelLocator, expose viewmodel interface.
In ViewModelLocator.cs
public IAboutViewModel About
{
get
{
return ServiceLocator.Current.GetInstance<IAboutViewModel>();
}
}
In AboutView.xaml
DataContext="{Binding Source={StaticResource Locator}, Path=About}"
Wherever needed in code, cast the interface to ViewModelBase type to convert it to ViewModel object and use.
In MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private readonly IAboutViewModel _aboutViewModel;
public MainViewModel()
{
_aboutViewModel = ServiceLocator.Current.GetInstance<IAboutViewModel>();
CurrentViewModel = (ViewModelBase) _aboutViewModel;
}
}
So, basically I use DI to inject appropriate viewmodels to each view depending on whether the code is in run time or design time. The ViewModelLocator simply registers either design time or run time viewmodels in the SimpleIOC container. The benefit of this is that there is no mixing of code in viewmodel files and one can also setup the code for multiple design time data without much interference. If you want designtime data to show while the application runs, then also its possible with one line change in code.
I would create an instance of Fake View Model in a separate xaml e.g. DeviceViewModelDataSample.xaml (see example below)
Set Build Action to DesignData
Reference the file as such
<UserControl x:Class="YourNameSpace.YourControl"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DataContext="{d:DesignData Source=/DataSample/DeviceViewModelDataSample.xaml}">
<!-- Skiped details for brevity -->
</UserControl>
DeviceViewModelDataSample.xaml
<vm:DeviceViewModel xmlns:dm="clr-namespace:YourNameSpace.DataModel"
xmlns:vm="clr-namespace:YourNameSpace.ViewModel">
<vm:DeviceViewModel.DeviceManager> <!-- Assuming this is a collection -->
<dm:DeviceModel DeviceName="Fake Device" IsConnected ="true" /> <!-- This creates an instance at design time -->
</vm:DeviceViewModel.DeviceManager>
</vm:DeviceViewModel>
I am writing a WPF application using MEF and a third-party library called MEFedMVVM.
I am attempting to create a design whereby a parent view model has a collection of child view models, and I wish to use the view-model-first approach as this keeps the views outside of the view models thereby keeping the code more view model-centric and more unit testable.
I have read this discussion and this discussion regarding using DataTemplate for the view, and also Reed Copsy, Jr's suggestion here to use a generic view to view model mapping resource. But, I'm struggling to actually implement something that works.
My parent view is very simple:
<UserControl x:Class="MyParentView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:meffed="clr-namespace:MEFedMVVM.ViewModelLocator;assembly=MEFedMVVM.WPF"
meffed:ViewModelLocator.ViewModel="MyParentViewModel" />
The parent view model derives from a base type that implements IContextAware:
[ExportViewModel("MyParentViewModel")]
public class MyParentViewModel : ViewModelBase
{
[ImportingConstructor]
public MyParentViewModel()
{
var myChildVM = ServiceLocator.Current.GetInstance<MyChildViewModel>());
}
}
This is the child view model:
[Export(typeof(MyChildViewModel))]
[ExportViewModel("MyChildViewModel", true)]
public class MyChildViewModel : ViewModelBase
{
}
And this has a corresponding view:
<UserControl x:Class="MyChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:meffed="clr-namespace:MEFedMVVM.ViewModelLocator;assembly=MEFedMVVM.WPF"
meffed:ViewModelLocator.ViewModel="MyChildViewModel" />
Initially, I thought that specifying the second Boolean parameter on ExportViewModel attribute for MyChildViewModel would make everything work using a view-model-first approach as my views and view models are MEFed together in the views' XAML code. However, turns out this is not the case, and what I actually get passed in to IContextAware.InjectContext() when I instantiate a MyChildViewModel object in the MyParentViewModel constructor is a MyParentView object. Not a MyChildView object as I was expecting and hoping. Clearly, I need to add something to wire them up together. Could anyone provide an example on how to do this?
Thanks!
When you really want to use view-model-first then you should do:
[ExportViewModel("MyParentViewModel")]
public class MyParentViewModel : ViewModelBase
{
// Create property for your child vm
public MyChildViewModel Child {get; private set}
// If you do MEF use constructor injection instead of servicelocator
[ImportingConstructor]
public MyParentViewModel(MyChildViewModel child)
{
this.Child = child;
}
}
then define a datatemplate for your childvm
<DataTemplate DataType="{x:Type local:MyChildViewModel}">
<view:MyChildViewUserControl />
</DataTemplate>
in your MainView you know where you want to show the child data, otherwise you wouldn't need the child property ;) so simply put a ContentControl where the Child data should go and bind to your property.
e.g.
<TabControl>
<TabItem Header="MyChildData">
<ContentControl Content="{Binding Child}" />
</TabItem>
</TabControl>
PS: Code is written without IDE, so errors possible :)
I am getting a really weird XamlParseException, and I have no idea why.
The message is "Cannot set unknown member '{clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro}View.Model'."
In the view model, I have an ObservableCollection that I'm initializing in the constructor like this:
internal class EntityListScreenViewModel : Screen
{
public EntityListScreenViewModel()
{
var list = new List<Entity>() { new Entity() { Name = "Joe" } };
this.Entities = new ObservableCollection<Entity>(list);
}
public ObservableCollection<Entity> Entities { get; set; }
}
Here's the view:
<Window x:Class="WpfApp.EntityListScreenView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<Grid>
<ListBox x:Name="Entities"/>
</Grid>
</Window>
When I call WindowManager.ShowWindow() on an instance of EntityListScreenViewModel, I get the exception.
If I do not add an Entity to the list (using var list = new List<Entity>(); instead), I do not get the exception.
Does anybody have any ideas?
Update:
I tried changing the ObservableCollection to be of type string and added a single string, and I did not get the exception. My suspicion is that Caliburn.Micro is somehow looking for a view to represent the Entity in the ListBox. Could that perhaps be what's going on?
Update 2:
I finally figured out what was really going on... the DefaultItemTemplate in the ConventionManager had some parsed Xaml that was looking for the "Caliburn.Micro" assembly, but I had put the code in with another assembly. Changed the Xaml and the problem went away.
My suspicion is that Caliburn.Micro is
somehow looking for a view to
represent the Entity in the ListBox
Yes, this is what is going on. By default when you bind a list using a name convention, Caliburn Micro interprets this as you binding to a list of ViewModels; not a list of Entities. This allows you to bind to a list of ViewModels without the need to specify the specific view to use in the ItemTemplate of the ItemsControl and what you end up with is a list of views specific to that ViewModel.
To ensure this doesn't happen, you should be able to just bind manually to the ListBox. If Caliburn Micro sees an ItemsSource binding already, it will ignore the convention.
<ListBox x:Name="Entities" ItemsSource="{Binding Entities}"/>
I'm not sure, but you can try putting EntityListScreenView in Views namespace and EntityListScreenViewModel in ViewModels namespace - of course if you user "standard" Bootstrapper class.
I have my first WPF working fine with an ObjectDataProvider in the XAML:
<ObjectDataProvider x:Key="WaitingPatientDS" ObjectType="{x:Type local:clsPatients}">
<ObjectDataProvider.ConstructorParameters>
<sys:Boolean>True</sys:Boolean>
</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
However, I don't like using this because if there is a connection error, I can't trap it and the program just barfs out.
So, what I've been trying to do is to instantiate the collection object directly in the codebehind...
public partial class MainWindow : Window
{
ListBox _activeListBox;
clsPatients oPatients;
public MainWindow()
{
oPatients = new clsPatients(true);
...and then reference it in my databinding as so:
<StackPanel x:Name="stkWaitingPatients" Width="300" Margin="0,0,0,-3"
DataContext="{Binding Mode=OneWay, Source={StaticResource local:oPatients}}">
But, I'm getting "local:oPatients was not found".
So...what am I doing wrong in referencing this and/or how would someone else accomplish this data binding so that I can actually trap for connection errors and divert the main form to a user-friendly error form?
THANKS!
You're getting that error because oPatients is not a StaticResource. It would have to be defined in the ResourceDictionary the way your ObjectDataProvider was, but as a class member it is not. You could expose it as a public property:
public clsPatients Patients { get; set; }
Then bind to it directly:
<!-- MainWindowRoot is the x:Name of your Window element. -->
<StackPanel x:Name="stkWaitingPatients" Width="300" Margin="0,0,0,-3"
DataContext="{Binding Patients, ElementName=MainWindowRoot, Mode=OneWay}">
Assuming I haven't made some stupid mistake, that should work. However, it still doesn't solve your problem because you're loading the data in the constructor, which means that any exceptions will cause clsPatients construction to fail, which in turn will cause MainWindow construction to fail, which you cannot properly handle because it's a rats next of a stack trace that's indistinguishable from a legitimate construction failure.
Kent is 100% right: The data should come from an external provider.
You may have resource limitations, but you can still establish good design even if you can't implement a tiered architecture. At bare minimum, establish separation of concerns by loading the data in a separate data provider class then passing the fully-formed data into the window. That allows you to isolate failures where they occur, and keep your code somewhat more loosely coupled.
public class PatientDataProvider
{
public PatientDataProvider(WhatItNeedsToConnect whatItNeedsToConnect)
{
// this doesn't connect because the constructor shouldn't fail because of a connection failure
}
public clsPatients GetPatientData(bool yesOrNo)
{
// this can fail because of a connection error or some other data loading error
}
}
and invoke it as:
PatientDataProvider provider = new PatientDataProvider(whatItNeedsToConnect);
clsPatients patients = null;
try {
patients = provider.GetPatientData(true);
MainWindow w = new MainWindow { Patients = patients; };
w.Show();
}
catch (WhateverExceptionGetsThrownByProvider e)
{
MessageBox.Show("Could not load patients: " + e.Message);
}
Also, if clsPatients is self-refreshing, make sure it implements INotifyPropertyChanged or INotifyCollectionChanged as appropriate in order for the binding targets to update when the data changes.
I'd move the data access logic into a separate service, and perhaps into its own assembly entirely to enforce your intended separation of concerns. Then I'd have a view model use said service to retrieve data and expose it for the view. Then the view would simply bind to the view model and wouldn't care whether the data came from a database or whatever.
I would suggest reading up on separation of concerns, service locator/dependency injection, and MVVM.