I’m having a weird & frustrating problem passing an object between some of my classes. This stems from the fact I am a scripter and not a programmer, and am just bumbling along. So I’m sure I’m doing something dumb :)
I am trying to build a Wizard dialog which has multiple pages. I am using the “Internationalised WPF Wizard” tutorial from CodeProject as a starting point, and attempting to adapt it to my domain. I’m getting stuck because my wizard pages can’t seem to refer to the model.
I have done the following:
Created a class for my Model (let call this MyData)
Created a base class for my view models (ViewModelBase)
Created a view model class for each of my pages, inheriting from ViewModelBase (example below is WelcomePageViewModel)
Created a ‘controller’ style view model which drives the wizard. (WizardController)
When the wizard is launched, WizardController is instantiated. WizardController also instantiates MyData.Then, WizardController instantiates each of the view models for the remaining pages.
The actual GUI seems to work fine, and I can see that the view models for each of the pages are being loaded correctly. Here’s some code:
public class MyData
{
private string _someString;
public MyData(string someString)
{
_someString = someString;
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
bool _isCurrentPage;
readonly MyData _myData;
public ViewModelBase(MyData myData)
{
_myData = myData;
}
}
public class WizardController : INotifyPropertyChanged
{
MyData _myData;
public WizardController()
{
_myData = new MyData("The Widgets");
}
}
public class WelcomePageViewModel : ViewModelBase
{
private MyData _myData;
public WelcomePageViewModel(MyData myData)
: base(myData)
{
_myData = myData;
// accessing _myData fails :(
MyLogger.WriteLine("Grabbed an instance of myData: " + _myData.ToString());
}
}
However, my code fails when I try to access myData from WelcomePageViewModel. On the MyLogger line in WelcomePageViewModel, the error “Object reference not set to an instance of an object.” is thrown.
Basically, all I’m trying to achieve is WizardController setting up MyData, and each of the wizard pages being able to access (and manipulate) it. So any guidance would be greatly appreciated!
As Rob G suggested in a comment, you're re-declaring the variable _myData in the inherited classes. The correct way to organize your code is to let the _myData be a protected property declared only on the abstract base class, and use this property to access the variable from the inheriting classes.
public abstract class ViewModelBase : INotifyPropertyChanged
{
bool _isCurrentPage;
protected MyData MyData { get; private set; }
public ViewModelBase(MyData myData)
{
MyData = myData;
}
}
public class WelcomePageViewModel : ViewModelBase
{
public WelcomePageViewModel(MyData myData)
: base(myData)
{
// Access the protected property
MyLogger.WriteLine("Grabbed an instance of myData: " + MyData.ToString());
}
}
Edit: fixed a copy-paste error...
Related
I have two identical views View1.xaml and View2.xaml and they both have a button button1 and a textfield textfield1. The idea is that when you press the button, the corresponding textfield is filled with some information. Both views use the same method for filling in the textfield (the views are literally identical in that sense).
My question is: how to write generic code using OOP principles and not break the MVVM pattern? My current way of performing this with RelayCommand:
The same code for ViewModel1 and ViewModel2:
public RelayCommand ButtonCommand { get; private set; }
#Constructor
ButtonCommand = new RelayCommand(ExecuteButtonCommand, CanExecuteButtonCommand);
#EndConstructor
private void ExecuteButtonCommand(object message)
{
//Some method to fill the corresponding textfield
}
private bool CanExecuteButtonCommand(object message)
{
return true;
}
Binding for the button in View1.xaml and View2.xaml:
<Button Command="{Binding Path=ButtonCommand, Mode=OneWay}" />
This is bad, because I have to write the same code for both ViewModels. I was trying to make a class ButtonCommand which inherits from RelayCommand, but because not every view will have this functionality, I can't achieve it using this method.
Rather than having a "Base" view model and two derived view models, have your two view models both use the same code defined elsewhere (ideally, both calling the same interface, injected with dependency injection).
This is the Composition over Inheritance principle.
When you're writing your tests, test that both view models call the interface, and test that the implementation of that interface does what it is supposed to do once.
This way, not only can you avoid writing your code twice, you can also avoid testing it twice, and it also allows you to follow other principles like the single responsibility principle.
This could be an way to go:
1 - Create a base viewmodel class:
public class YourBaseViewModel
{
public Object YourBaseProperty{get; set;}
public RelayCommand ButtonCommand { get; private set; }
private void ExecuteButtonCommand(object message)
{
//Some method to fill the corresponding textfield
}
private bool CanExecuteButtonCommand(object message)
{
return true;
}
}
2 - Inherit from the base viewmodel:
public class ViewModel1:YourBaseViewModel
{
// ....
}
public class ViewModel2:YourBaseViewModel
{
// ....
}
EDIT:
If you have another base class you could do:
public class YourBaseViewModel:YourReallyBaseViewModel
{
// ....
}
public class ViewModel1:YourBaseViewModel
{
// ....
}
public class ViewModel2:YourBaseViewModel
{
// ....
}
This is an XY problem. You're asking for a way to solve Y (not duplicate the same ButtonCommand but in actuality), your problem is X (you already have duplication in your code)
I have two identical views View1.xaml and View2.xaml
I'd like to add, that you've also stated you don't have only two identical views, there's more.
The best way to resolve this is to have a parent ParentViewModel that can construct the child ViewModels
So first, we'll need an interface for the child view model
IMyViewModel
public interface IMyViewModel
{
void Load();
}
Next, the implementation
MyViewModel
public class MyViewModel : ViewModelBase, IMyViewModel
{
public MainViewModel()
{
ButtonCommand = new RelayCommand(ExecuteButtonCommand, CanExecuteButtonCommand);
}
public RelayCommand ButtonCommand { get; private set; }
public void Load()
{
//Example load logic
InvalidateCommands();
}
private void InvalidateCommands()
{
ButtonCommand.RaiseCanExecuteChanged();
}
private void ExecuteButtonCommand(object message)
{
//Some method to fill the corresponding textfield
}
private bool CanExecuteButtonCommand(object message)
{
return true;
}
}
And lastly the ParentViewModel which has the responsibility of creating the view models. Please note, I did not tell it WHEN to create the ViewModels, I will leave that up to you.
Parent View Model
public class ParentViewModel : ViewModelBase
{
private Func<IMyViewModel> _myVmCreator;
public ParentViewModel(Func<IMyViewModel> myVmCreator)
{
_friendEditVmCreator = friendEditVmCreator;
}
public ObservableCollection<IMyViewModel> MyViewModels { get; private set; }
private IMyViewModel CreateAndLoadMyViewModel()
{
var myVm = _myVmCreator();
MyViewModels.Add(myVm);
myVm.Load();
return myVm;
}
}
This will allow you to create any number of MyViewModels, or any other type of ViewModel as long as it implements IMyViewModel.
The above example is derived from this course : https://www.pluralsight.com/courses/wpf-mvvm-test-driven-development-viewmodels
I highly recommend it.
I am trying to build an mvvm app using mvvmcross. When I start the app a null reference exception occurs.
this is my codebehind file which uses MvvmCross.WindowsUWP.Views.
public sealed partial class MainView : MvxWindowsPage
{
public MainView()
{
this.InitializeComponent();
MainViewModel = (MainViewModel)ViewModel;
}
public MainViewModel MainViewModel { get; set; }
public PlayersViewModel PlayersViewModel { get; set; } = Mvx.IocConstruct<PlayersViewModel>();
}
This is my app.cs file.
public class App : MvxApplication
{
public override void Initialize()
{
RegisterAppStart<MainViewModel>();
}
}
When I set an break point at MainViewModel = (MainViewModel)ViewModel; the break point is hit and I can see that the ViewModel property is null. what I am doing wrong? Thanks in advance.
ViewModel property is not yet initialized in the constructor. That's why its value is still null.
You will need to move the assignment to a different method that gets called later, e.g. OnNavigatedTo:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
MainViewModel = (MainViewModel)ViewModel;
}
Even better, instead of assigning ViewModel to your own auto implemented property, rather have the MainViewModel getter perform the cast. Then you don't even need to do the assignment:
public MainViewModel MainViewModel => (MainViewModel)ViewModel;
Unfortunatelly UWP doesn't support generic base classes in XAML, otherwise you could use MvxWindowsPage<MainViewModel> as the base page, not needing to do the cast at all.
I am developing a ViewModel based on BindableBase.
This VM holds an instance of a domain model.
The VM exposes a property, say Name from which I want to not use local storage (i.e. storage in VM), but rather delegate to the model object's property.
I would like to use SetProperty(), but the storage reference cannot be a property.
Do I have to implement INotifyPropertyChanged my self ?
Is it at all a good idea to delegate to the model this way ?
Would it be possible to extend BindableBase (or team to add) to cover for this ?
So first you must chose how you will architect your VMs and Models. As you mentioned, there are a few options. The easiest and the way I recommend, is to just expose your Model as a property and then bind your View to the model properties:
public class MyViewModel : BindableBase
{
private Person _myPerson;
public Person Person
{
get { return _myPerson; }
set { SetProperty(ref _myPerson, value); }
}
}
If you don't want to do that and would rather wrap each individual model property, you would do it like this.
public class MyViewModel : BindableBase
{
private Person _myPerson;
private string _name;
public string Name
{
get { return _myPerson.Name; }
set { _myPerson.Name = value }
}
}
Keep in mind, your Person model object still has to implement INPC.
EDIT: If you don't have control over your models and need them to implement INPC, you could try to use IL weaving, or create a façade/decorator for your models and wrap them individually.
public class MyPersonFacade : BindableBase
{
private Person _myPerson;
private string _name;
public string Name
{
get { return _myPerson.Name; }
set
{
_myPerson.Name = value;
OnPropertyChanged();
}
}
}
Then use this as your Model in your VM.
In my prism application I want to make a single shared instance of a view. When I try to navigate the first time it works fine, but when I try to second time it's not working. If I change the PartCreationPolicy from Shared to NonShared it works but it's give me a new instance. Are there any options for another way to do this?
[Export(ViewNames.AppView)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class AppMain : UserControl
{
public AppMain()
{
InitializeComponent();
}
}
You might want to play around with Prism's KeepAlive value for your view. This value determines whether the view should be removed from the region when you navigate away from it. You have two ways of doing this:
Using the RegionMemberLifetime attribute
[RegionMemberLifetime(KeepAlive = false)]
[Export(ViewNames.AppView)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class AppMain : UserControl
{
public AppMain()
{
InitializeComponent();
}
}
Implementing the IRegionMemberLifetime interface
[Export(ViewNames.AppView)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class AppMain : UserControl, IRegionMemberLifetime
{
public AppMain()
{
InitializeComponent();
}
public bool KeepAlive
{
get { return false; }
}
}
You can read some more about the KeepAlive property here.
I have several UserControls that are sharing some common properties. Example:
private List<MyObject> Sample
{
get
{
return Session["MyObject"] as List<MyObject>;
}
set
{
Session["MyObject"] = value;
}
}
I want to share this to all user controls inside my project. (Not to other projects in a solution, of course). What I'm trying to do is create a separate class and inherit from that class. Something like:
public class SampleBase : Web.UI.UserControl
{
protected List<MyObject> Sample
{
get
{
return Session["MyObject"] as List<MyObject>;
}
set
{
Session["MyObject"] = value;
}
}
}
And then my control can inherit those values by deriving from that class:
partial class myControl : SampleBase
One problem I encounter is that I cannot derive from base if control already has something inherited:
partial class myControl : SomethingELSE
Otherwise it works fine, but I'm not sure if it is a good approach and I'm looking for suggestions.
If my understanding is correct, you only want to get rid of the inheritance hierarchy of your User Controls
Another approach would be using Extension Methods
For example:
Interface to mark your USerControls
public interface IMyUserControlMark { }
Extensions
public static class MyUserClassExtensions
{
public static List<object> GetSampleData(this IMyUserControlMark myUserControl)
{
if (HttpContext.Current.Session["MyObject"] == null)
{
return Enumerable.Empty<object>().ToList();
}
return HttpContext.Current.Session["MyObject"] as List<object>;
}
public static void SetSampleData(this IMyUserControlMark myUserControl, List<object> myObject)
{
HttpContext.Current.Session["MyObject"] = myObject;
}
}
User control
public partial class Content1 : System.Web.UI.UserControl, IMyUserControlMark
{
...
}
public partial class Content2 : System.Web.UI.UserControl, IMyUserControlMark
{
....
}
Now you will be able to call your extension methods from within your UserControl or from the ASPX code behind like this:
From the UserControl
var myObject = this.GetSampleData();
this.SetSampleData(myObject);
From the ASPX code behind
var myObject = this.uc1.GetSampleData();
this.uc1.SetSampleData(myObject);
This is a classic example where you need to "favor composition over inheritance".
Instead of inheriting from the class, you hold a reference to an instance of the class. Then you provide simple pass-through code to access the methods/properties of the class.
So, for your example:
public class SomeBehavior
{
public List<MyObject> Sample
{
get { return Session["MyObject"] as List<MyObject>; }
set { Session["MyObject"] = value; }
}
}
public class MyControl : UserControl
{
private SomeBehavior _someBehavior;
public MyControl()
{
_someBehavior = new SomeBehavior();
}
public List<MyObject> Sample
{
get { return _someBehavior.Sample; }
set { _someBehavior.Sample = value; }
}
}
Another option is to allow access to the behavior class directly:
public class MyControl : UserControl
{
public SomeBehavior SomeBehavior { get; private set; }
public MyControl()
{
SomeBehavior = new SomeBehavior();
}
}
The advantage of this is that you don't have to write the pass-through code. The disadvantage is that it violates the Law of Demeter, which says that you should "only talk to your immediate friends". If you do it this way, other classes that use MyControl need to know about SomeBehavior. Following the Law Of Demeter can improve maintainability and adaptability of your code, but it comes at a cost of lots of pass-through code.
Apart from previous solutions, maybe it's time for applying some MVC/MVP pattern?
For web forms there is a great framework called WebFormsMVP: link
In the library there is a mechanism called Cross Presenter Messaging thanks to which you can share a data between your controls using the publish/subscribe pattern.
For better explanation look here and here
I suggest to give the library a chance :)
In C# you can inherit from only one class and implement multiple interfaces.
This is allowed:
partial class myControl : SampleBase
partial class myControl : SampleBase, Interface1
partial class myControl : SampleBase, Interface1, Interface2, Interface3
This is NOT allowed:
partial class myControl : SomethingELSE, SampleBase
Try making SomethingELSE inherit from SampleBase if it satisfies your design. If not, then I suggest encapsulating SampleBase as a property of each control that needs it as it also suggested #DanM.